1 //------------------------------------------------------------------------------
2 // <copyright file="XmlToDatasetMap.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 // <owner current="false" primary="false">Microsoft</owner>
8 //------------------------------------------------------------------------------
10 namespace System
.Data
{
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
;
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
)
47 public object this[XmlNode node
] {
49 id
.LocalName
= node
.LocalName
;
50 id
.NamespaceURI
= node
.NamespaceURI
;
55 public object this[XmlReader dataReader
] {
57 id
.LocalName
= dataReader
.LocalName
;
58 id
.NamespaceURI
= dataReader
.NamespaceURI
;
63 public object this[DataTable table
] {
65 id
.LocalName
= table
.EncodedTableName
;
66 id
.NamespaceURI
= table
.Namespace
;
71 public object this[string name
] {
74 id
.NamespaceURI
= String
.Empty
;
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.
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
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
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) {
195 XmlNodeIdentety idColumn
= new XmlNodeIdentety(columnLocalName
, columnNamespace
);
197 columns
[idColumn
] = col
;
199 if (col
.ColumnName
.StartsWith("xml", StringComparison
.OrdinalIgnoreCase
)) {
200 HandleSpecialColumn(col
, nameTable
, columns
);
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
223 if (col
._columnUri
!= null ) // Update namespace
224 col
._columnUri
= columnNamespace
;
226 // Create XmlNodeIdentety
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
);
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
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
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();
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
);
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
;
410 if(nodeRegion
== null || nodeRegion
.NodeType
!= XmlNodeType
.Element
) {
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
];
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
;
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...
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
;