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;
}
}
}
|