|
//------------------------------------------------------------------------------
// <copyright file="DataSetMapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
#pragma warning disable 618 // ignore obsolete warning about XmlDataDocument
namespace System.Xml {
using System.Collections;
using System.Data;
using System.Diagnostics;
//
// Maps XML nodes to schema
//
// With the exception of some functions (the most important is SearchMatchingTableSchema) all functions expect that each region rowElem is already associated
// w/ it's DataRow (basically the test to determine a rowElem is based on a != null associated DataRow). As a result of this, some functions will NOT work properly
// when they are used on a tree for which rowElem's are not associated w/ a DataRow.
//
internal sealed class DataSetMapper {
Hashtable tableSchemaMap; // maps an string (currently this is localName:nsURI) to a DataTable. Used to quickly find if a bound-elem matches any data-table metadata..
Hashtable columnSchemaMap; // maps a string (table localName:nsURI) to a Hashtable. The 2nd hastable (the one that is stored as data in columnSchemaMap, maps a string to a DataColumn.
XmlDataDocument doc; // The document this mapper is related to
DataSet dataSet; // The dataset this mapper is related to
internal const string strReservedXmlns = "http://www.w3.org/2000/xmlns/";
internal DataSetMapper() {
Debug.Assert( this.dataSet == null );
this.tableSchemaMap = new Hashtable();
this.columnSchemaMap = new Hashtable();
}
internal void SetupMapping( XmlDataDocument xd, DataSet ds ) {
// If are already mapped, forget about our current mapping and re-do it again.
if ( IsMapped() ) {
this.tableSchemaMap = new Hashtable();
this.columnSchemaMap = new Hashtable();
}
doc = xd;
dataSet = ds;
foreach( DataTable t in dataSet.Tables ) {
AddTableSchema( t );
foreach( DataColumn c in t.Columns ) {
// don't include auto-generated PK & FK to be part of mapping
if ( ! IsNotMapped(c) ) {
AddColumnSchema( c );
}
}
}
}
internal bool IsMapped() {
return dataSet != null;
}
internal DataTable SearchMatchingTableSchema( string localName, string namespaceURI ) {
object tid = GetIdentity( localName, namespaceURI );
return (DataTable)(tableSchemaMap[ tid ]);
}
// SearchMatchingTableSchema function works only when the elem has not been bound to a DataRow. If you want to get the table associated w/ an element after
// it has been associated w/ a DataRow use GetTableSchemaForElement function.
// rowElem is the parent region rowElem or null if there is no parent region (in case elem is a row elem, then rowElem will be the parent region; if elem is not
// mapped to a DataRow, then rowElem is the region elem is part of)
//
// Those are the rules for determing if elem is a row element:
// 1. node is an element (already meet, since elem is of type XmlElement)
// 2. If the node is already associated w/ a DataRow, then the node is a row element - not applicable, b/c this function is intended to be called on a
// to find out if the node s/b associated w/ a DataRow (see XmlDataDocument.LoadRows)
// 3. If the node localName/ns matches a DataTable then
// 3.1 Take the parent region DataTable (in our case rowElem.Row.DataTable)
// 3.2 If no parent region, then the node is associated w/ a DataTable
// 3.3 If there is a parent region
// 3.3.1 If the node has no elem children and no attr other than namespace declaration, and the node can match
// a column from the parent region table, then the node is NOT associated w/ a DataTable (it is a potential DataColumn in the parent region)
// 3.3.2 Else the node is a row-element (and associated w/ a DataTable / DataRow )
//
internal DataTable SearchMatchingTableSchema( XmlBoundElement rowElem, XmlBoundElement elem ) {
Debug.Assert( elem != null );
DataTable t = SearchMatchingTableSchema( elem.LocalName, elem.NamespaceURI );
if ( t == null )
return null;
if ( rowElem == null )
return t;
// Currently we expect we map things from top of the tree to the bottom
Debug.Assert( rowElem.Row != null );
DataColumn col = GetColumnSchemaForNode( rowElem, elem );
if ( col == null )
return t;
foreach ( XmlAttribute a in elem.Attributes ) {
#if DEBUG
// Some sanity check to catch errors like namespace attributes have the right localName/namespace value, but a wrong atomized namespace value
if ( a.LocalName == "xmlns" ) {
Debug.Assert( a.Prefix != null && a.Prefix.Length == 0 );
Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
}
if ( a.Prefix == "xmlns" ) {
Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
}
if ( a.NamespaceURI == strReservedXmlns )
Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
#endif
// No namespace attribute found, so elem cannot be a potential DataColumn, therefore is a row-elem
if ( (object)(a.NamespaceURI) != (object)strReservedXmlns )
return t;
}
for ( XmlNode n = elem.FirstChild; n != null; n = n.NextSibling ) {
if ( n.NodeType == XmlNodeType.Element ) {
// elem has an element child, so elem cannot be a potential DataColumn, therefore is a row-elem
return t;
}
}
// Node is a potential DataColumn in rowElem region
return null;
}
internal DataColumn GetColumnSchemaForNode( XmlBoundElement rowElem, XmlNode node ) {
//
Debug.Assert( rowElem != null );
// The caller must make sure that node is not a row-element
Debug.Assert( (node is XmlBoundElement) ? ((XmlBoundElement)node).Row == null : true );
object tid = GetIdentity( rowElem.LocalName, rowElem.NamespaceURI );
object cid = GetIdentity( node.LocalName, node.NamespaceURI );
Hashtable columns = (Hashtable) columnSchemaMap[ tid ];
if ( columns != null ) {
DataColumn col = (DataColumn)(columns[ cid ]);
if ( col == null )
return null;
MappingType mt = col.ColumnMapping;
if ( node.NodeType == XmlNodeType.Attribute && mt == MappingType.Attribute )
return col;
if ( node.NodeType == XmlNodeType.Element && mt == MappingType.Element )
return col;
// node's (localName, ns) matches a column, but the MappingType is different (i.e. node is elem, MT is attr)
return null;
}
return null;
}
internal DataTable GetTableSchemaForElement( XmlElement elem ) {
//
XmlBoundElement be = elem as XmlBoundElement;
if ( be == null )
return null;
return GetTableSchemaForElement( be );
}
internal DataTable GetTableSchemaForElement( XmlBoundElement be ) {
// if bound to a row, must be a table.
DataRow row = be.Row;
if ( row != null )
return row.Table;
return null;
}
internal static bool IsNotMapped( DataColumn c ) {
return c.ColumnMapping == MappingType.Hidden;
}
// ATTENTION: GetRowFromElement( XmlElement ) and GetRowFromElement( XmlBoundElement ) should have the same functionality and side effects.
// See this code fragment for why:
// XmlBoundElement be = ...;
// XmlElement e = be;
// GetRowFromElement( be ); // Calls GetRowFromElement( XmlBoundElement )
// GetRowFromElement( e ); // Calls GetRowFromElement( XmlElement ), in spite of e beeing an instance of XmlBoundElement
internal DataRow GetRowFromElement( XmlElement e ) {
XmlBoundElement be = e as XmlBoundElement;
if ( be != null )
return be.Row;
return null;
}
internal DataRow GetRowFromElement( XmlBoundElement be ) {
return be.Row;
}
// Get the row-elem associatd w/ the region node is in.
// If node is in a region not mapped (like document element node) the function returns false and sets elem to null)
// This function does not work if the region is not associated w/ a DataRow (it uses DataRow association to know what is the row element associated w/ the region)
internal bool GetRegion( XmlNode node, out XmlBoundElement rowElem ) {
while ( node != null ) {
XmlBoundElement be = node as XmlBoundElement;
// Break if found a region
if ( be != null && GetRowFromElement( be ) != null ) {
rowElem = be;
return true;
}
if ( node.NodeType == XmlNodeType.Attribute )
node = ((XmlAttribute)node).OwnerElement;
else
node = node.ParentNode;
}
rowElem = null;
return false;
}
internal bool IsRegionRadical( XmlBoundElement rowElem ) {
// You must pass a row element (which s/b associated w/ a DataRow)
Debug.Assert( rowElem.Row != null );
if ( rowElem.ElementState == ElementState.Defoliated )
return true;
DataTable table = GetTableSchemaForElement( rowElem );
DataColumnCollection columns = table.Columns;
int iColumn = 0;
// check column attributes...
int cAttrs = rowElem.Attributes.Count;
for ( int iAttr = 0; iAttr < cAttrs; iAttr++ ) {
XmlAttribute attr = rowElem.Attributes[iAttr];
// only specified attributes are radical
if ( !attr.Specified )
return false;
// only mapped attrs are valid
DataColumn schema = GetColumnSchemaForNode( rowElem, attr );
if ( schema == null ) {
//Console.WriteLine("Region has unmapped attribute");
return false;
}
// check to see if column is in order
if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
//Console.WriteLine("Region has attribute columns out of order or duplicate");
return false;
}
// must have exactly one text node (XmlNodeType.Text) child
//
XmlNode fc = attr.FirstChild;
if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
//Console.WriteLine("column element has other than a single child text node");
return false;
}
}
// check column elements
iColumn = 0;
XmlNode n = rowElem.FirstChild;
for ( ; n != null; n = n.NextSibling ) {
// only elements can exist in radically structured data
if ( n.NodeType != XmlNodeType.Element ) {
//Console.WriteLine("Region has non-element child");
return false;
}
XmlElement e = n as XmlElement;
// only checking for column mappings in this loop
if ( GetRowFromElement( e ) != null )
break;
// element's must have schema to be radically structured
DataColumn schema = GetColumnSchemaForNode( rowElem, e );
if ( schema == null ) {
//Console.WriteLine("Region has unmapped child element");
return false;
}
// check to see if column is in order
if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
//Console.WriteLine("Region has element columns out of order or duplicate");
return false;
}
// must have no attributes
if ( e.HasAttributes )
return false;
// must have exactly one text node child
XmlNode fc = e.FirstChild;
if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
//Console.WriteLine("column element has other than a single child text node");
return false;
}
}
// check for remaining sub-regions
for (; n != null; n = n.NextSibling ) {
// only elements can exist in radically structured data
if ( n.NodeType != XmlNodeType.Element ) {
//Console.WriteLine("Region has non-element child");
return false;
}
// element's must be regions in order to be radially structured
DataRow row = GetRowFromElement( (XmlElement)n );
if ( row == null ) {
//Console.WriteLine("Region has unmapped element");
return false;
}
}
return true;
}
private void AddTableSchema( DataTable table ) {
object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
tableSchemaMap[ idTable ] = table;
}
private void AddColumnSchema( DataColumn col ) {
DataTable table = col.Table;
object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
object idColumn = GetIdentity( col.EncodedColumnName, col.Namespace );
Hashtable columns = (Hashtable) columnSchemaMap[ idTable ];
if ( columns == null ) {
columns = new Hashtable();
columnSchemaMap[ idTable ] = columns;
}
columns[ idColumn ] = col;
}
private static object GetIdentity( string localName, string namespaceURI ) {
// we need access to XmlName to make this faster
return localName+":"+namespaceURI;
}
private bool IsNextColumn( DataColumnCollection columns, ref int iColumn, DataColumn col ) {
for ( ; iColumn < columns.Count; iColumn++ ) {
if ( columns[iColumn] == col ) {
iColumn++; // advance before we return...
return true;
}
}
return false;
}
}
}
|