[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / XmlToDatasetMap.cs
blob7f46e7e6d5463c7acced663ad414264d8a710c67
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlToDatasetMap.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 // <owner current="false" primary="false">Microsoft</owner>
8 //------------------------------------------------------------------------------
10 namespace System.Data {
11 using System;
12 using System.Xml;
13 using System.Collections;
14 using System.Diagnostics;
15 using System.Globalization;
17 // This is an internal helper class used during Xml load to DataSet/DataDocument.
18 // XmlToDatasetMap class provides functionality for binding elemants/atributes
19 // to DataTable / DataColumn
20 internal sealed class XmlToDatasetMap {
22 private sealed class XmlNodeIdentety {
23 public string LocalName;
24 public string NamespaceURI;
25 public XmlNodeIdentety(string localName, string namespaceURI) {
26 this.LocalName = localName;
27 this.NamespaceURI = namespaceURI;
29 override public int GetHashCode() {
30 return ((object) LocalName).GetHashCode();
32 override public bool Equals(object obj) {
33 XmlNodeIdentety id = (XmlNodeIdentety) obj;
34 return (
35 (String.Compare(this.LocalName, id.LocalName, StringComparison.OrdinalIgnoreCase) == 0) &&
36 (String.Compare(this.NamespaceURI, id.NamespaceURI, StringComparison.OrdinalIgnoreCase) == 0)
41 // This class exist to avoid alocatin of XmlNodeIdentety to every acces to the hash table.
42 // Unfortunetely XmlNode doesn't export single identety object.
43 internal sealed class XmlNodeIdHashtable : Hashtable {
44 private XmlNodeIdentety id = new XmlNodeIdentety(string.Empty, string.Empty);
45 public XmlNodeIdHashtable(Int32 capacity)
46 : base(capacity) {}
47 public object this[XmlNode node] {
48 get {
49 id.LocalName = node.LocalName;
50 id.NamespaceURI = node.NamespaceURI;
51 return this[id];
55 public object this[XmlReader dataReader] {
56 get {
57 id.LocalName = dataReader.LocalName;
58 id.NamespaceURI = dataReader.NamespaceURI;
59 return this[id];
63 public object this[DataTable table] {
64 get {
65 id.LocalName = table.EncodedTableName;
66 id.NamespaceURI = table.Namespace;
67 return this[id];
71 public object this[string name] {
72 get {
73 id.LocalName = name;
74 id.NamespaceURI = String.Empty;
75 return this[id];
80 private sealed class TableSchemaInfo {
81 public DataTable TableSchema;
82 public XmlNodeIdHashtable ColumnsSchemaMap;
83 public TableSchemaInfo(DataTable tableSchema) {
84 this.TableSchema = tableSchema;
85 this.ColumnsSchemaMap = new XmlNodeIdHashtable(tableSchema.Columns.Count);
89 XmlNodeIdHashtable tableSchemaMap; // Holds all the tables information
91 TableSchemaInfo lastTableSchemaInfo = null;
93 // Used to infer schema
95 public XmlToDatasetMap(DataSet dataSet, XmlNameTable nameTable) {
96 Debug.Assert(dataSet != null, "DataSet can't be null");
97 Debug.Assert(nameTable != null, "NameTable can't be null");
98 BuildIdentityMap(dataSet, nameTable);
101 // Used to read data with known schema
103 public XmlToDatasetMap(XmlNameTable nameTable, DataSet dataSet) {
104 Debug.Assert(dataSet != null, "DataSet can't be null");
105 Debug.Assert(nameTable != null, "NameTable can't be null");
106 BuildIdentityMap(nameTable, dataSet);
109 // Used to infer schema
111 public XmlToDatasetMap(DataTable dataTable, XmlNameTable nameTable) {
112 Debug.Assert(dataTable != null, "DataTable can't be null");
113 Debug.Assert(nameTable != null, "NameTable can't be null");
114 BuildIdentityMap(dataTable, nameTable);
117 // Used to read data with known schema
119 public XmlToDatasetMap(XmlNameTable nameTable, DataTable dataTable) {
120 Debug.Assert(dataTable != null, "DataTable can't be null");
121 Debug.Assert(nameTable != null, "NameTable can't be null");
122 BuildIdentityMap(nameTable, dataTable);
124 static internal bool IsMappedColumn(DataColumn c) {
125 return (c.ColumnMapping != MappingType.Hidden);
128 // Used to infere schema
130 private TableSchemaInfo AddTableSchema(DataTable table, XmlNameTable nameTable) {
131 // Microsoft: Because in our case reader already read the document all names that we can meet in the
132 // document already has an entry in NameTable.
133 // If in future we will build identity map before reading XML we can replace Get() to Add()
134 // Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
135 // First case deals with decoded names; Second one with encoded names.
136 // We decided encoded names in first case (instead of decoding them in second)
137 // because it save us time in LoadRows(). We have, as usual, more data them schemas
138 string tableLocalName = nameTable.Get(table.EncodedTableName);
139 string tableNamespace = nameTable.Get(table.Namespace );
140 if(tableLocalName == null) {
141 // because name of this table isn't present in XML we don't need mapping for it.
142 // Less mapping faster we work.
143 return null;
145 TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
146 tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
147 return tableSchemaInfo;
150 private TableSchemaInfo AddTableSchema(XmlNameTable nameTable, DataTable table) {
151 // Microsoft:This is the opposite of the previous function:
152 // we populate the nametable so that the hash comparison can happen as
153 // object comparison instead of strings.
154 // Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
155 // First case deals with decoded names; Second one with encoded names.
156 // We decided encoded names in first case (instead of decoding them in second)
157 // because it save us time in LoadRows(). We have, as usual, more data them schemas
159 string _tableLocalName = table.EncodedTableName; // Table name
161 string tableLocalName = nameTable.Get(_tableLocalName); // Look it up in nametable
163 if(tableLocalName == null) { // If not found
164 tableLocalName = nameTable.Add(_tableLocalName); // Add it
167 table.encodedTableName = tableLocalName; // And set it back
169 string tableNamespace = nameTable.Get(table.Namespace); // Look ip table namespace
171 if (tableNamespace == null) { // If not found
172 tableNamespace = nameTable.Add(table.Namespace); // Add it
174 else {
175 if (table.tableNamespace != null) // Update table namespace
176 table.tableNamespace = tableNamespace;
180 TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
181 // Create new table schema info
182 tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
183 // And add it to the hashtable
184 return tableSchemaInfo; // Return it as we have to populate
185 // Column schema map and Child table
186 // schema map in it
189 private bool AddColumnSchema(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
190 string columnLocalName = nameTable.Get(col.EncodedColumnName );
191 string columnNamespace = nameTable.Get(col.Namespace );
192 if(columnLocalName == null) {
193 return false;
195 XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
197 columns[idColumn] = col;
199 if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
200 HandleSpecialColumn(col, nameTable, columns);
204 return true;
207 private bool AddColumnSchema(XmlNameTable nameTable, DataColumn col, XmlNodeIdHashtable columns) {
208 string _columnLocalName = XmlConvert.EncodeLocalName(col.ColumnName);
209 string columnLocalName = nameTable.Get(_columnLocalName); // Look it up in a name table
211 if(columnLocalName == null) { // Not found?
212 columnLocalName = nameTable.Add(_columnLocalName); // Add it
215 col.encodedColumnName = columnLocalName; // And set it back
217 string columnNamespace = nameTable.Get(col.Namespace ); // Get column namespace from nametable
219 if(columnNamespace == null) { // Not found ?
220 columnNamespace = nameTable.Add(col.Namespace); // Add it
222 else {
223 if (col._columnUri != null ) // Update namespace
224 col._columnUri = columnNamespace;
226 // Create XmlNodeIdentety
227 // for this column
228 XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
229 columns[idColumn] = col; // And add it to hashtable
231 if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
232 HandleSpecialColumn(col, nameTable, columns);
235 return true;
238 private void BuildIdentityMap(DataSet dataSet, XmlNameTable nameTable) {
240 this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
242 foreach(DataTable t in dataSet.Tables) {
243 TableSchemaInfo tableSchemaInfo = AddTableSchema(t, nameTable);
244 if(tableSchemaInfo != null) {
245 foreach( DataColumn c in t.Columns ) {
246 // don't include auto-generated PK, FK and any hidden columns to be part of mapping
247 if (IsMappedColumn(c)) {
248 AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
255 // This one is used while reading data with preloaded schema
257 private void BuildIdentityMap(XmlNameTable nameTable, DataSet dataSet) {
258 this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
259 // This hash table contains
260 // tables schemas as TableSchemaInfo objects
261 // These objects holds reference to the table.
262 // Hash tables with columns schema maps
263 // and child tables schema maps
265 string dsNamespace = nameTable.Get(dataSet.Namespace); // Attept to look up DataSet namespace
266 // in the name table
268 if (dsNamespace == null) { // Found ?
269 dsNamespace = nameTable.Add(dataSet.Namespace); // Nope. Add it
271 dataSet.namespaceURI = dsNamespace; // Set a DataSet namespace URI
274 foreach(DataTable t in dataSet.Tables) { // For each table
276 TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t);
277 // Add table schema info to hash table
279 if(tableSchemaInfo != null) {
280 foreach( DataColumn c in t.Columns ) { // Add column schema map
281 // don't include auto-generated PK, FK and any hidden columns to be part of mapping
282 if (IsMappedColumn(c)) { // If mapped column
283 AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
284 } // Add it to the map
287 // Add child nested tables to the schema
289 foreach( DataRelation r in t.ChildRelations ) { // Do we have a child tables ?
290 if (r.Nested) { // Is it nested?
291 // don't include non nested tables
293 // Handle namespaces and names as usuall
295 string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
296 string tableLocalName = nameTable.Get(_tableLocalName);
298 if(tableLocalName == null) {
299 tableLocalName = nameTable.Add(_tableLocalName);
302 string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
304 if(tableNamespace == null) {
305 tableNamespace = nameTable.Add(r.ChildTable.Namespace);
308 XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
309 tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
317 // Used for inference
319 private void BuildIdentityMap(DataTable dataTable, XmlNameTable nameTable) {
320 this.tableSchemaMap = new XmlNodeIdHashtable(1);
322 TableSchemaInfo tableSchemaInfo = AddTableSchema(dataTable, nameTable);
323 if(tableSchemaInfo != null) {
324 foreach( DataColumn c in dataTable.Columns ) {
325 // don't include auto-generated PK, FK and any hidden columns to be part of mapping
326 if (IsMappedColumn(c)) {
327 AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
334 // This one is used while reading data with preloaded schema
336 private void BuildIdentityMap(XmlNameTable nameTable, DataTable dataTable) {
338 ArrayList tableList = GetSelfAndDescendants(dataTable); // Get list of tables we're loading
339 // This includes our table and
340 // related tables tree
342 this.tableSchemaMap = new XmlNodeIdHashtable( tableList.Count );
343 // Create hash table to hold all
344 // tables to load.
346 foreach (DataTable t in tableList) { // For each table
348 TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t );
349 // Create schema info
350 if(tableSchemaInfo != null) {
351 foreach( DataColumn c in t.Columns ) { // Add column information
352 // don't include auto-generated PK, FK and any hidden columns to be part of mapping
353 if (IsMappedColumn(c)) {
354 AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
358 foreach( DataRelation r in t.ChildRelations ) { // Add nested tables information
359 if (r.Nested) { // Is it nested?
360 // don't include non nested tables
362 // Handle namespaces and names as usuall
364 string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
365 string tableLocalName = nameTable.Get(_tableLocalName);
367 if(tableLocalName == null) {
368 tableLocalName = nameTable.Add(_tableLocalName);
371 string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
373 if(tableNamespace == null) {
374 tableNamespace = nameTable.Add(r.ChildTable.Namespace);
377 XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
378 tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
386 private ArrayList GetSelfAndDescendants(DataTable dt) { // breadth-first
387 ArrayList tableList = new ArrayList();
388 tableList.Add(dt);
389 int nCounter = 0;
391 while (nCounter < tableList.Count) {
392 foreach(DataRelation childRelations in ((DataTable)tableList[nCounter]).ChildRelations) {
393 if (!tableList.Contains(childRelations.ChildTable))
394 tableList.Add(childRelations.ChildTable);
396 nCounter++;
399 return tableList;
401 // Used to infer schema and top most node
403 public object GetColumnSchema(XmlNode node, bool fIgnoreNamespace) {
404 Debug.Assert(node != null, "Argument validation");
405 TableSchemaInfo tableSchemaInfo = null;
407 XmlNode nodeRegion = (node.NodeType == XmlNodeType.Attribute) ? ((XmlAttribute)node).OwnerElement : node.ParentNode;
409 do {
410 if(nodeRegion == null || nodeRegion.NodeType != XmlNodeType.Element) {
411 return null;
413 tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[nodeRegion.LocalName] : tableSchemaMap[nodeRegion]);
415 nodeRegion = nodeRegion.ParentNode;
416 } while(tableSchemaInfo == null);
418 if (fIgnoreNamespace)
419 return tableSchemaInfo.ColumnsSchemaMap[node.LocalName];
420 else
421 return tableSchemaInfo.ColumnsSchemaMap[node];
426 public object GetColumnSchema(DataTable table, XmlReader dataReader, bool fIgnoreNamespace){
427 if ((lastTableSchemaInfo == null) || (lastTableSchemaInfo.TableSchema != table)) {
428 lastTableSchemaInfo = (TableSchemaInfo)(fIgnoreNamespace ? tableSchemaMap[table.EncodedTableName] : tableSchemaMap[table]);
431 if (fIgnoreNamespace)
432 return lastTableSchemaInfo.ColumnsSchemaMap[dataReader.LocalName];
433 return lastTableSchemaInfo.ColumnsSchemaMap[dataReader];
436 // Used to infer schema
438 public object GetSchemaForNode(XmlNode node, bool fIgnoreNamespace) {
439 TableSchemaInfo tableSchemaInfo = null;
441 if (node.NodeType == XmlNodeType.Element) { // If element
442 tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
443 } // Look up table schema info for it
445 if (tableSchemaInfo != null) { // Got info ?
446 return tableSchemaInfo.TableSchema; // Yes, Return table
449 return GetColumnSchema(node, fIgnoreNamespace); // Attempt to locate column
452 public DataTable GetTableForNode(XmlReader node, bool fIgnoreNamespace) {
453 TableSchemaInfo tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
454 if (tableSchemaInfo != null) {
455 lastTableSchemaInfo = tableSchemaInfo;
456 return lastTableSchemaInfo.TableSchema;
458 return null;
461 private void HandleSpecialColumn(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
462 // if column name starts with xml, we encode it manualy and add it for look up
463 Debug.Assert(col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase), "column name should start with xml");
464 string tempColumnName;
466 if ('x' == col.ColumnName[0]) {
467 tempColumnName = "_x0078_"; // lower case xml... -> _x0078_ml...
469 else {
470 tempColumnName = "_x0058_"; // upper case Xml... -> _x0058_ml...
473 tempColumnName += col.ColumnName.Substring(1);
475 if(nameTable.Get(tempColumnName) == null) {
476 nameTable.Add(tempColumnName);
478 string columnNamespace = nameTable.Get(col.Namespace);
479 XmlNodeIdentety idColumn = new XmlNodeIdentety(tempColumnName, columnNamespace);
480 columns[idColumn] = col;