1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
//------------------------------------------------------------------------------
// <copyright file="DataSetMapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
#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;
}
}
}
|