| File: System\Xml\DataSetMappper.cs | Web Access |
| Project: src\runtime\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; #pragma warning disable 0618 // ignore obsolete warning about XmlDataDocument namespace System.Xml { // // 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 { private 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.. private 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. private XmlDataDocument? _doc; // The document this mapper is related to private DataSet? _dataSet; // The dataset this mapper is related to internal const string strReservedXmlns = "http://www.w3.org/2000/xmlns/"; internal DataSetMapper() { Debug.Assert(_dataSet == null); _tableSchemaMap = new Hashtable(); _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()) { _tableSchemaMap = new Hashtable(); _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() => _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(a.NamespaceURI == (object)strReservedXmlns); } if (a.Prefix == "xmlns") { Debug.Assert(a.NamespaceURI == (object)strReservedXmlns); } if (a.NamespaceURI == strReservedXmlns) { Debug.Assert(a.NamespaceURI == (object)strReservedXmlns); } #endif // No namespace attribute found, so elem cannot be a potential DataColumn, therefore is a row-elem if (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 static DataTable? GetTableSchemaForElement(XmlElement elem) { XmlBoundElement? be = elem as XmlBoundElement; if (be == null) { return null; } return GetTableSchemaForElement(be); } internal static DataTable? GetTableSchemaForElement(XmlBoundElement be) => be.Row?.Table; internal static bool IsNotMapped(DataColumn c) => 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 being an instance of XmlBoundElement internal static DataRow? GetRowFromElement(XmlElement? e) => (e as XmlBoundElement)?.Row; internal static DataRow? GetRowFromElement(XmlBoundElement be) => 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 static bool GetRegion(XmlNode? node, [NotNullWhen(true)] 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) { return false; } // check to see if column is in order if (!IsNextColumn(columns, ref iColumn, schema)) { 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) { 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) { 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) { return false; } // check to see if column is in order if (!IsNextColumn(columns, ref iColumn, schema)) { 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) { 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) { return false; } // element's must be regions in order to be radially structured DataRow? row = GetRowFromElement((XmlElement?)n); if (row == null) { 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 static 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; } } }