2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="DataSetMapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
2017-08-21 15:34:15 +00:00
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
#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 ;
}
}
}