Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / NewXml / DataSetMappper.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DataSetMapper.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
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
9 namespace System.Xml {
10
11     using System.Collections;
12     using System.Data;
13     using System.Diagnostics;
14
15
16     //
17     // Maps XML nodes to schema
18     //
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.
22     //
23     
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.
27
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/";
31
32
33         internal DataSetMapper() {
34             Debug.Assert( this.dataSet == null );
35             this.tableSchemaMap = new Hashtable();
36             this.columnSchemaMap = new Hashtable();
37         }
38
39         internal void SetupMapping( XmlDataDocument xd, DataSet ds ) {
40             // If are already mapped, forget about our current mapping and re-do it again.
41             if ( IsMapped() ) {
42                 this.tableSchemaMap = new Hashtable();
43                 this.columnSchemaMap = new Hashtable();
44             }
45             doc = xd;
46             dataSet = ds;
47             foreach( DataTable t in dataSet.Tables ) {
48                 AddTableSchema( t );
49
50                 foreach( DataColumn c in t.Columns ) {
51                     // don't include auto-generated PK & FK to be part of mapping
52                     if ( ! IsNotMapped(c) ) {
53                         AddColumnSchema( c );
54                     }
55                 }
56             }
57         }
58
59         internal bool IsMapped() {
60             return dataSet != null;
61         }
62
63         internal DataTable SearchMatchingTableSchema( string localName, string namespaceURI ) {
64             object tid = GetIdentity( localName, namespaceURI );
65             return (DataTable)(tableSchemaMap[ tid ]);
66             
67         }
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)
72         //
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 )
84         //
85         internal DataTable SearchMatchingTableSchema( XmlBoundElement rowElem, XmlBoundElement elem ) {
86             Debug.Assert( elem != null );
87
88             DataTable t = SearchMatchingTableSchema( elem.LocalName, elem.NamespaceURI );
89             if ( t == null )
90                 return null;
91
92             if ( rowElem == null )
93                 return t;
94             // Currently we expect we map things from top of the tree to the bottom
95             Debug.Assert( rowElem.Row != null );
96
97             DataColumn col = GetColumnSchemaForNode( rowElem, elem );
98             if ( col == null )
99                 return t;
100
101             foreach ( XmlAttribute a in elem.Attributes ) {
102 #if DEBUG
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 );
107                 }
108                 if ( a.Prefix == "xmlns" ) {
109                     Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
110                 }
111                 if ( a.NamespaceURI == strReservedXmlns )
112                     Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
113 #endif
114                 // No namespace attribute found, so elem cannot be a potential DataColumn, therefore is a row-elem
115                 if ( (object)(a.NamespaceURI) != (object)strReservedXmlns )
116                     return t;
117             }
118
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
122                     return t;
123                 }
124             }
125             // Node is a potential DataColumn in rowElem region
126             return null;
127         }
128
129         internal DataColumn GetColumnSchemaForNode( XmlBoundElement rowElem, XmlNode node ) {
130             // 
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 );
134
135             object tid = GetIdentity( rowElem.LocalName, rowElem.NamespaceURI );
136             object cid = GetIdentity( node.LocalName, node.NamespaceURI );
137
138             Hashtable columns = (Hashtable) columnSchemaMap[ tid ];
139             if ( columns != null ) {
140                 DataColumn col = (DataColumn)(columns[ cid ]);
141                 if ( col == null )
142                     return null;
143
144                 MappingType mt = col.ColumnMapping;
145
146                 if ( node.NodeType == XmlNodeType.Attribute && mt == MappingType.Attribute )
147                     return col;
148                 if ( node.NodeType == XmlNodeType.Element && mt == MappingType.Element )
149                     return col;
150                 // node's (localName, ns) matches a column, but the MappingType is different (i.e. node is elem, MT is attr)
151                 return null;
152             }
153             return null;
154         }
155         internal DataTable GetTableSchemaForElement( XmlElement elem ) {
156             //
157             XmlBoundElement be = elem as XmlBoundElement;
158             if ( be == null )
159                 return null;
160
161             return GetTableSchemaForElement( be );
162         }
163
164         internal DataTable GetTableSchemaForElement( XmlBoundElement be ) {
165             // if bound to a row, must be a table.
166             DataRow row = be.Row;
167             if ( row != null )
168                 return row.Table;
169
170             return null;
171         }
172
173         internal static bool IsNotMapped( DataColumn c ) {
174             return c.ColumnMapping == MappingType.Hidden;
175         }
176
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;
185             if ( be != null )
186                 return be.Row;
187             return null;
188         }
189         internal DataRow GetRowFromElement( XmlBoundElement be ) {
190             return be.Row;
191         }
192
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 ) {
201                     rowElem = be;
202                     return true;
203                 }
204
205                 if ( node.NodeType == XmlNodeType.Attribute )
206                     node = ((XmlAttribute)node).OwnerElement;
207                 else
208                     node = node.ParentNode;
209             }
210
211             rowElem = null;
212             return false;
213         }
214
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 );
218
219             if ( rowElem.ElementState == ElementState.Defoliated )
220                 return true;
221
222             DataTable table = GetTableSchemaForElement( rowElem );
223             DataColumnCollection columns = table.Columns;
224             int iColumn = 0;
225
226             // check column attributes...
227             int cAttrs = rowElem.Attributes.Count;
228             for ( int iAttr = 0; iAttr < cAttrs; iAttr++ ) {
229                 XmlAttribute attr = rowElem.Attributes[iAttr];
230
231                 // only specified attributes are radical
232                 if ( !attr.Specified )
233                     return false;
234
235                 // only mapped attrs are valid
236                 DataColumn schema = GetColumnSchemaForNode( rowElem, attr );
237                 if ( schema == null ) {
238                     //Console.WriteLine("Region has unmapped attribute");
239                     return false;
240                 }
241
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");
245                     return false;
246                 }
247
248                 // must have exactly one text node (XmlNodeType.Text) child
249                 // 
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");
253                     return false;
254                 }
255             }
256
257             // check column elements
258             iColumn = 0;
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");
264                     return false;
265                 }
266                 XmlElement e = n as XmlElement;
267
268                 // only checking for column mappings in this loop
269                 if ( GetRowFromElement( e ) != null )
270                     break;
271
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");
276                     return false;
277                 }
278
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");
282                     return false;
283                 }
284
285                 // must have no attributes
286                 if ( e.HasAttributes )
287                     return false;
288
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");
293                     return false;
294                 }
295             }
296
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");
302                     return false;
303                 }
304
305                 // element's must be regions in order to be radially structured
306                 DataRow row = GetRowFromElement( (XmlElement)n );
307                 if ( row == null ) {
308                     //Console.WriteLine("Region has unmapped element");
309                     return false;
310                 }
311             }
312
313             return true;
314         }
315
316         private void AddTableSchema( DataTable table ) {
317             object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
318             tableSchemaMap[ idTable ] = table;
319         }
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 );
324
325             Hashtable columns = (Hashtable) columnSchemaMap[ idTable ];
326             if ( columns == null ) {
327                 columns = new Hashtable();
328                 columnSchemaMap[ idTable ] = columns;
329             }
330             columns[ idColumn ] = col;
331         }
332         private static object GetIdentity( string localName, string namespaceURI ) {
333             // we need access to XmlName to make this faster
334             return localName+":"+namespaceURI;
335         }
336
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...
341                     return true;
342                 }
343             }
344
345             return false;
346         }
347
348     }
349 }
350