1 //------------------------------------------------------------------------------
2 // <copyright file="DataSetMapper.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8 #pragma warning disable 618 // ignore obsolete warning about XmlDataDocument
11 using System.Collections;
13 using System.Diagnostics;
17 // Maps XML nodes to schema
19 // With the exception of some functions (the most important is SearchMatchingTableSchema) all functions expect that each region rowElem is already associated
20 // 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
21 // when they are used on a tree for which rowElem's are not associated w/ a DataRow.
24 internal sealed class DataSetMapper {
25 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..
26 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.
28 XmlDataDocument doc; // The document this mapper is related to
29 DataSet dataSet; // The dataset this mapper is related to
30 internal const string strReservedXmlns = "http://www.w3.org/2000/xmlns/";
33 internal DataSetMapper() {
34 Debug.Assert( this.dataSet == null );
35 this.tableSchemaMap = new Hashtable();
36 this.columnSchemaMap = new Hashtable();
39 internal void SetupMapping( XmlDataDocument xd, DataSet ds ) {
40 // If are already mapped, forget about our current mapping and re-do it again.
42 this.tableSchemaMap = new Hashtable();
43 this.columnSchemaMap = new Hashtable();
47 foreach( DataTable t in dataSet.Tables ) {
50 foreach( DataColumn c in t.Columns ) {
51 // don't include auto-generated PK & FK to be part of mapping
52 if ( ! IsNotMapped(c) ) {
59 internal bool IsMapped() {
60 return dataSet != null;
63 internal DataTable SearchMatchingTableSchema( string localName, string namespaceURI ) {
64 object tid = GetIdentity( localName, namespaceURI );
65 return (DataTable)(tableSchemaMap[ tid ]);
68 // 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
69 // it has been associated w/ a DataRow use GetTableSchemaForElement function.
70 // 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
71 // mapped to a DataRow, then rowElem is the region elem is part of)
73 // Those are the rules for determing if elem is a row element:
74 // 1. node is an element (already meet, since elem is of type XmlElement)
75 // 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
76 // to find out if the node s/b associated w/ a DataRow (see XmlDataDocument.LoadRows)
77 // 3. If the node localName/ns matches a DataTable then
78 // 3.1 Take the parent region DataTable (in our case rowElem.Row.DataTable)
79 // 3.2 If no parent region, then the node is associated w/ a DataTable
80 // 3.3 If there is a parent region
81 // 3.3.1 If the node has no elem children and no attr other than namespace declaration, and the node can match
82 // 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)
83 // 3.3.2 Else the node is a row-element (and associated w/ a DataTable / DataRow )
85 internal DataTable SearchMatchingTableSchema( XmlBoundElement rowElem, XmlBoundElement elem ) {
86 Debug.Assert( elem != null );
88 DataTable t = SearchMatchingTableSchema( elem.LocalName, elem.NamespaceURI );
92 if ( rowElem == null )
94 // Currently we expect we map things from top of the tree to the bottom
95 Debug.Assert( rowElem.Row != null );
97 DataColumn col = GetColumnSchemaForNode( rowElem, elem );
101 foreach ( XmlAttribute a in elem.Attributes ) {
103 // Some sanity check to catch errors like namespace attributes have the right localName/namespace value, but a wrong atomized namespace value
104 if ( a.LocalName == "xmlns" ) {
105 Debug.Assert( a.Prefix != null && a.Prefix.Length == 0 );
106 Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
108 if ( a.Prefix == "xmlns" ) {
109 Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
111 if ( a.NamespaceURI == strReservedXmlns )
112 Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
114 // No namespace attribute found, so elem cannot be a potential DataColumn, therefore is a row-elem
115 if ( (object)(a.NamespaceURI) != (object)strReservedXmlns )
119 for ( XmlNode n = elem.FirstChild; n != null; n = n.NextSibling ) {
120 if ( n.NodeType == XmlNodeType.Element ) {
121 // elem has an element child, so elem cannot be a potential DataColumn, therefore is a row-elem
125 // Node is a potential DataColumn in rowElem region
129 internal DataColumn GetColumnSchemaForNode( XmlBoundElement rowElem, XmlNode node ) {
131 Debug.Assert( rowElem != null );
132 // The caller must make sure that node is not a row-element
133 Debug.Assert( (node is XmlBoundElement) ? ((XmlBoundElement)node).Row == null : true );
135 object tid = GetIdentity( rowElem.LocalName, rowElem.NamespaceURI );
136 object cid = GetIdentity( node.LocalName, node.NamespaceURI );
138 Hashtable columns = (Hashtable) columnSchemaMap[ tid ];
139 if ( columns != null ) {
140 DataColumn col = (DataColumn)(columns[ cid ]);
144 MappingType mt = col.ColumnMapping;
146 if ( node.NodeType == XmlNodeType.Attribute && mt == MappingType.Attribute )
148 if ( node.NodeType == XmlNodeType.Element && mt == MappingType.Element )
150 // node's (localName, ns) matches a column, but the MappingType is different (i.e. node is elem, MT is attr)
155 internal DataTable GetTableSchemaForElement( XmlElement elem ) {
157 XmlBoundElement be = elem as XmlBoundElement;
161 return GetTableSchemaForElement( be );
164 internal DataTable GetTableSchemaForElement( XmlBoundElement be ) {
165 // if bound to a row, must be a table.
166 DataRow row = be.Row;
173 internal static bool IsNotMapped( DataColumn c ) {
174 return c.ColumnMapping == MappingType.Hidden;
177 // ATTENTION: GetRowFromElement( XmlElement ) and GetRowFromElement( XmlBoundElement ) should have the same functionality and side effects.
178 // See this code fragment for why:
179 // XmlBoundElement be = ...;
180 // XmlElement e = be;
181 // GetRowFromElement( be ); // Calls GetRowFromElement( XmlBoundElement )
182 // GetRowFromElement( e ); // Calls GetRowFromElement( XmlElement ), in spite of e beeing an instance of XmlBoundElement
183 internal DataRow GetRowFromElement( XmlElement e ) {
184 XmlBoundElement be = e as XmlBoundElement;
189 internal DataRow GetRowFromElement( XmlBoundElement be ) {
193 // Get the row-elem associatd w/ the region node is in.
194 // If node is in a region not mapped (like document element node) the function returns false and sets elem to null)
195 // 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)
196 internal bool GetRegion( XmlNode node, out XmlBoundElement rowElem ) {
197 while ( node != null ) {
198 XmlBoundElement be = node as XmlBoundElement;
199 // Break if found a region
200 if ( be != null && GetRowFromElement( be ) != null ) {
205 if ( node.NodeType == XmlNodeType.Attribute )
206 node = ((XmlAttribute)node).OwnerElement;
208 node = node.ParentNode;
215 internal bool IsRegionRadical( XmlBoundElement rowElem ) {
216 // You must pass a row element (which s/b associated w/ a DataRow)
217 Debug.Assert( rowElem.Row != null );
219 if ( rowElem.ElementState == ElementState.Defoliated )
222 DataTable table = GetTableSchemaForElement( rowElem );
223 DataColumnCollection columns = table.Columns;
226 // check column attributes...
227 int cAttrs = rowElem.Attributes.Count;
228 for ( int iAttr = 0; iAttr < cAttrs; iAttr++ ) {
229 XmlAttribute attr = rowElem.Attributes[iAttr];
231 // only specified attributes are radical
232 if ( !attr.Specified )
235 // only mapped attrs are valid
236 DataColumn schema = GetColumnSchemaForNode( rowElem, attr );
237 if ( schema == null ) {
238 //Console.WriteLine("Region has unmapped attribute");
242 // check to see if column is in order
243 if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
244 //Console.WriteLine("Region has attribute columns out of order or duplicate");
248 // must have exactly one text node (XmlNodeType.Text) child
250 XmlNode fc = attr.FirstChild;
251 if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
252 //Console.WriteLine("column element has other than a single child text node");
257 // check column elements
259 XmlNode n = rowElem.FirstChild;
260 for ( ; n != null; n = n.NextSibling ) {
261 // only elements can exist in radically structured data
262 if ( n.NodeType != XmlNodeType.Element ) {
263 //Console.WriteLine("Region has non-element child");
266 XmlElement e = n as XmlElement;
268 // only checking for column mappings in this loop
269 if ( GetRowFromElement( e ) != null )
272 // element's must have schema to be radically structured
273 DataColumn schema = GetColumnSchemaForNode( rowElem, e );
274 if ( schema == null ) {
275 //Console.WriteLine("Region has unmapped child element");
279 // check to see if column is in order
280 if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
281 //Console.WriteLine("Region has element columns out of order or duplicate");
285 // must have no attributes
286 if ( e.HasAttributes )
289 // must have exactly one text node child
290 XmlNode fc = e.FirstChild;
291 if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
292 //Console.WriteLine("column element has other than a single child text node");
297 // check for remaining sub-regions
298 for (; n != null; n = n.NextSibling ) {
299 // only elements can exist in radically structured data
300 if ( n.NodeType != XmlNodeType.Element ) {
301 //Console.WriteLine("Region has non-element child");
305 // element's must be regions in order to be radially structured
306 DataRow row = GetRowFromElement( (XmlElement)n );
308 //Console.WriteLine("Region has unmapped element");
316 private void AddTableSchema( DataTable table ) {
317 object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
318 tableSchemaMap[ idTable ] = table;
320 private void AddColumnSchema( DataColumn col ) {
321 DataTable table = col.Table;
322 object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
323 object idColumn = GetIdentity( col.EncodedColumnName, col.Namespace );
325 Hashtable columns = (Hashtable) columnSchemaMap[ idTable ];
326 if ( columns == null ) {
327 columns = new Hashtable();
328 columnSchemaMap[ idTable ] = columns;
330 columns[ idColumn ] = col;
332 private static object GetIdentity( string localName, string namespaceURI ) {
333 // we need access to XmlName to make this faster
334 return localName+":"+namespaceURI;
337 private bool IsNextColumn( DataColumnCollection columns, ref int iColumn, DataColumn col ) {
338 for ( ; iColumn < columns.Count; iColumn++ ) {
339 if ( columns[iColumn] == col ) {
340 iColumn++; // advance before we return...