536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
351 lines
16 KiB
C#
351 lines
16 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|