File: fx\src\data\System\Data\XmlToDatasetMap.cs
Project: ndp\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="XmlToDatasetMap.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>
// <owner current="false" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data {
    using System;
    using System.Xml;
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
 
    // This is an internal helper class used during Xml load to DataSet/DataDocument.
    // XmlToDatasetMap class provides functionality for binding elemants/atributes
    // to DataTable / DataColumn 
    internal sealed class XmlToDatasetMap {
 
        private sealed class XmlNodeIdentety {
            public string LocalName;
            public string NamespaceURI;
            public XmlNodeIdentety(string localName, string namespaceURI) {
                this.LocalName    = localName;
                this.NamespaceURI = namespaceURI;
            }
            override public int GetHashCode() {
                return ((object) LocalName).GetHashCode();
            }
            override public bool Equals(object obj) {
                XmlNodeIdentety id = (XmlNodeIdentety) obj;
                return (
                  (String.Compare(this.LocalName, id.LocalName, StringComparison.OrdinalIgnoreCase) == 0) &&
                  (String.Compare(this.NamespaceURI, id.NamespaceURI, StringComparison.OrdinalIgnoreCase) == 0)              
                );
            }
        }
 
        // This class exist to avoid alocatin of XmlNodeIdentety to every acces to the hash table.
        // Unfortunetely XmlNode doesn't export single identety object.
        internal sealed class XmlNodeIdHashtable : Hashtable {
            private XmlNodeIdentety id = new XmlNodeIdentety(string.Empty, string.Empty);
            public XmlNodeIdHashtable(Int32 capacity) 
                : base(capacity) {}
            public object this[XmlNode node] {
                get {
                    id.LocalName    = node.LocalName;
                    id.NamespaceURI = node.NamespaceURI;
                    return this[id];
                }
            }
            
            public object this[XmlReader dataReader] {
                get {
                    id.LocalName    = dataReader.LocalName;
                    id.NamespaceURI = dataReader.NamespaceURI;
                    return this[id];
                }
            }
 
            public object this[DataTable table] {
                get {
                    id.LocalName    = table.EncodedTableName;
                    id.NamespaceURI = table.Namespace;
                    return this[id];
                }
            }
 
            public object this[string name] {
                get {
                    id.LocalName    = name;
                    id.NamespaceURI = String.Empty;
                    return this[id];
                }
            }
        }
 
        private sealed class TableSchemaInfo {
            public DataTable          TableSchema;
            public XmlNodeIdHashtable ColumnsSchemaMap;
            public TableSchemaInfo(DataTable tableSchema) {
                this.TableSchema      = tableSchema;
                this.ColumnsSchemaMap = new XmlNodeIdHashtable(tableSchema.Columns.Count);
            }
        }
 
        XmlNodeIdHashtable tableSchemaMap;              // Holds all the tables information
 
        TableSchemaInfo lastTableSchemaInfo = null;
 
        // Used to infer schema
 
        public XmlToDatasetMap(DataSet dataSet, XmlNameTable nameTable) {
            Debug.Assert(dataSet   != null, "DataSet can't be null");
            Debug.Assert(nameTable != null, "NameTable can't be null");
            BuildIdentityMap(dataSet, nameTable);
        }
 
        // Used to read data with known schema
 
        public XmlToDatasetMap(XmlNameTable nameTable, DataSet dataSet) {
            Debug.Assert(dataSet   != null, "DataSet can't be null");
            Debug.Assert(nameTable != null, "NameTable can't be null");
            BuildIdentityMap(nameTable, dataSet);
        }
 
        // Used to infer schema
 
        public XmlToDatasetMap(DataTable dataTable, XmlNameTable nameTable) {
            Debug.Assert(dataTable != null, "DataTable can't be null");
            Debug.Assert(nameTable != null, "NameTable can't be null");
            BuildIdentityMap(dataTable, nameTable);
        }
 
        // Used to read data with known schema
 
        public XmlToDatasetMap(XmlNameTable nameTable, DataTable dataTable) {
            Debug.Assert(dataTable  != null, "DataTable can't be null");
            Debug.Assert(nameTable != null, "NameTable can't be null");
            BuildIdentityMap(nameTable, dataTable);
        }
        static internal bool IsMappedColumn(DataColumn c) {
            return (c.ColumnMapping != MappingType.Hidden);
        }
 
        // Used to infere schema
 
        private TableSchemaInfo AddTableSchema(DataTable table, XmlNameTable nameTable) {
            // Microsoft: Because in our case reader already read the document all names that we can meet in the
            //       document already has an entry in NameTable.
            //       If in future we will build identity map before reading XML we can replace Get() to Add()
            // Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
            //       First case deals with decoded names; Second one with encoded names.
            //       We decided encoded names in first case (instead of decoding them in second) 
            //       because it save us time in LoadRows(). We have, as usual, more data them schemas
            string tableLocalName = nameTable.Get(table.EncodedTableName);
            string tableNamespace = nameTable.Get(table.Namespace );
            if(tableLocalName == null) {
                // because name of this table isn't present in XML we don't need mapping for it.
                // Less mapping faster we work.
                return null;
            }
            TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
            tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
            return tableSchemaInfo;
        }
 
        private TableSchemaInfo AddTableSchema(XmlNameTable nameTable, DataTable table) {
            // Microsoft:This is the opposite of the previous function:
            //       we populate the nametable so that the hash comparison can happen as
            //       object comparison instead of strings.
            // Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
            //       First case deals with decoded names; Second one with encoded names.
            //       We decided encoded names in first case (instead of decoding them in second) 
            //       because it save us time in LoadRows(). We have, as usual, more data them schemas
 
            string _tableLocalName = table.EncodedTableName;            // Table name
 
            string tableLocalName = nameTable.Get(_tableLocalName);     // Look it up in nametable
 
            if(tableLocalName == null) {                                // If not found
                tableLocalName = nameTable.Add(_tableLocalName);        // Add it
            }
 
            table.encodedTableName = tableLocalName;                    // And set it back
 
            string tableNamespace = nameTable.Get(table.Namespace);     // Look ip table namespace
 
            if (tableNamespace == null) {                               // If not found
                tableNamespace = nameTable.Add(table.Namespace);        // Add it
            }
            else {
                if (table.tableNamespace != null)                       // Update table namespace
                    table.tableNamespace = tableNamespace;
            }
 
 
            TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
                                                                        // Create new table schema info
            tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
                                                                        // And add it to the hashtable
            return tableSchemaInfo;                                     // Return it as we have to populate
                                                                        // Column schema map and Child table 
                                                                        // schema map in it
        }
 
        private bool AddColumnSchema(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
            string columnLocalName = nameTable.Get(col.EncodedColumnName );
            string columnNamespace = nameTable.Get(col.Namespace   );
            if(columnLocalName == null) {
                return false;
            }
            XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
 
            columns[idColumn] = col;
            
            if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
                HandleSpecialColumn(col, nameTable, columns);
            }
 
            
            return true;
        }
 
        private bool AddColumnSchema(XmlNameTable nameTable, DataColumn col, XmlNodeIdHashtable columns) {
            string _columnLocalName = XmlConvert.EncodeLocalName(col.ColumnName);
            string columnLocalName = nameTable.Get(_columnLocalName);           // Look it up in a name table
 
            if(columnLocalName == null) {                                       // Not found?
                columnLocalName = nameTable.Add(_columnLocalName);              // Add it
            }
 
            col.encodedColumnName = columnLocalName;                            // And set it back
 
            string columnNamespace = nameTable.Get(col.Namespace );             // Get column namespace from nametable
 
            if(columnNamespace == null) {                                       // Not found ?
                columnNamespace = nameTable.Add(col.Namespace);                 // Add it
            }
            else {
                if (col._columnUri != null )                                    // Update namespace
                    col._columnUri = columnNamespace;
            }
                                                                                // Create XmlNodeIdentety 
                                                                                // for this column
            XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
            columns[idColumn] = col;                                            // And add it to hashtable
 
            if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
                HandleSpecialColumn(col, nameTable, columns);
            }
            
            return true;
        }
 
        private void BuildIdentityMap(DataSet dataSet, XmlNameTable nameTable) {
 
            this.tableSchemaMap    = new XmlNodeIdHashtable(dataSet.Tables.Count);
 
            foreach(DataTable t in dataSet.Tables) {
                TableSchemaInfo tableSchemaInfo = AddTableSchema(t, nameTable);
                if(tableSchemaInfo != null) {
                    foreach( DataColumn c in t.Columns ) {
                        // don't include auto-generated PK, FK and any hidden columns to be part of mapping
                        if (IsMappedColumn(c)) {
                            AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
                        }
                    }
                }
            }
        }
 
        // This one is used while reading data with preloaded schema
 
        private void BuildIdentityMap(XmlNameTable nameTable, DataSet dataSet) {
            this.tableSchemaMap    = new XmlNodeIdHashtable(dataSet.Tables.Count);
                                                                        // This hash table contains
                                                                        // tables schemas as TableSchemaInfo objects
                                                                        // These objects holds reference to the table.
                                                                        // Hash tables with columns schema maps
                                                                        // and child tables schema maps
 
            string dsNamespace = nameTable.Get(dataSet.Namespace);      // Attept to look up DataSet namespace
                                                                        // in the name table
 
            if (dsNamespace == null) {                                  // Found ?
                dsNamespace = nameTable.Add(dataSet.Namespace);         // Nope. Add it
            }
            dataSet.namespaceURI = dsNamespace;                         // Set a DataSet namespace URI
 
 
            foreach(DataTable t in dataSet.Tables) {                    // For each table
 
                TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t);
                                                                        // Add table schema info to hash table
 
                if(tableSchemaInfo != null) {                          
                    foreach( DataColumn c in t.Columns ) {              // Add column schema map
                        // don't include auto-generated PK, FK and any hidden columns to be part of mapping
                        if (IsMappedColumn(c)) {                        // If mapped column
                            AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
                        }                                               // Add it to the map
                    }
 
                    // Add child nested tables to the schema
 
                    foreach( DataRelation r in t.ChildRelations ) {     // Do we have a child tables ?
                        if (r.Nested) {                                 // Is it nested?
                            // don't include non nested tables
 
                            // Handle namespaces and names as usuall
 
                            string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
                            string tableLocalName = nameTable.Get(_tableLocalName);
 
                            if(tableLocalName == null) {
                                tableLocalName = nameTable.Add(_tableLocalName);
                            }
 
                            string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
 
                            if(tableNamespace == null) {
                                tableNamespace = nameTable.Add(r.ChildTable.Namespace);
                            }
 
                            XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
                            tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
 
                        }
                    }
                }
            }
        }
 
        // Used for inference
 
        private void BuildIdentityMap(DataTable dataTable, XmlNameTable nameTable) {
            this.tableSchemaMap    = new XmlNodeIdHashtable(1);
 
            TableSchemaInfo tableSchemaInfo = AddTableSchema(dataTable, nameTable);
            if(tableSchemaInfo != null) {
                foreach( DataColumn c in dataTable.Columns ) {
                      // don't include auto-generated PK, FK and any hidden columns to be part of mapping
                      if (IsMappedColumn(c)) {
                          AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
                      }
                  }
            }
            
        }
 
        // This one is used while reading data with preloaded schema
 
        private void BuildIdentityMap(XmlNameTable nameTable, DataTable dataTable) {
 
            ArrayList tableList = GetSelfAndDescendants(dataTable);     // Get list of tables we're loading
                                                                        // This includes our table and 
                                                                        // related tables tree
 
            this.tableSchemaMap = new XmlNodeIdHashtable( tableList.Count );
                                                                        // Create hash table to hold all
                                                                        // tables to load.
            
            foreach (DataTable t in tableList) {                        // For each table
 
                TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t );
                                                                        // Create schema info
                if(tableSchemaInfo != null) {
                    foreach( DataColumn c in t.Columns ) {              // Add column information
                        // don't include auto-generated PK, FK and any hidden columns to be part of mapping
                        if (IsMappedColumn(c)) {
                            AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
                        }
                    }
 
                    foreach( DataRelation r in t.ChildRelations ) {     // Add nested tables information
                        if (r.Nested) {                                 // Is it nested?
                            // don't include non nested tables
 
                            // Handle namespaces and names as usuall
 
                            string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
                            string tableLocalName = nameTable.Get(_tableLocalName);
 
                            if(tableLocalName == null) {
                                tableLocalName = nameTable.Add(_tableLocalName);
                            }
 
                            string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
 
                            if(tableNamespace == null) {
                                tableNamespace = nameTable.Add(r.ChildTable.Namespace);
                            }
 
                            XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
                            tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
 
                        }
                    }
                }
            }
        }
 
        private ArrayList GetSelfAndDescendants(DataTable dt) { // breadth-first
             ArrayList tableList = new ArrayList();
             tableList.Add(dt);
             int nCounter = 0;
             
             while (nCounter < tableList.Count) {
                foreach(DataRelation childRelations in ((DataTable)tableList[nCounter]).ChildRelations) {
                    if (!tableList.Contains(childRelations.ChildTable))
                        tableList.Add(childRelations.ChildTable);
                }
                nCounter++;
             }
         
            return tableList;
        }
        // Used to infer schema and top most node
 
        public object GetColumnSchema(XmlNode node, bool fIgnoreNamespace) {
            Debug.Assert(node != null, "Argument validation");
            TableSchemaInfo tableSchemaInfo = null;
 
            XmlNode nodeRegion = (node.NodeType == XmlNodeType.Attribute) ? ((XmlAttribute)node).OwnerElement : node.ParentNode;
 
            do {
                if(nodeRegion == null || nodeRegion.NodeType != XmlNodeType.Element) {
                    return null;
                }
                tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[nodeRegion.LocalName] : tableSchemaMap[nodeRegion]);
 
                nodeRegion = nodeRegion.ParentNode;
            } while(tableSchemaInfo == null);
 
            if (fIgnoreNamespace)
                return tableSchemaInfo.ColumnsSchemaMap[node.LocalName];
            else
                return tableSchemaInfo.ColumnsSchemaMap[node];
 
        }
 
 
        public object GetColumnSchema(DataTable table, XmlReader dataReader, bool fIgnoreNamespace){
            if ((lastTableSchemaInfo == null) || (lastTableSchemaInfo.TableSchema != table)) {
                lastTableSchemaInfo = (TableSchemaInfo)(fIgnoreNamespace ? tableSchemaMap[table.EncodedTableName] : tableSchemaMap[table]);
            }
 
            if (fIgnoreNamespace)
                return lastTableSchemaInfo.ColumnsSchemaMap[dataReader.LocalName];
            return lastTableSchemaInfo.ColumnsSchemaMap[dataReader];
        }
 
        // Used to infer schema
 
        public object GetSchemaForNode(XmlNode node, bool fIgnoreNamespace) {
            TableSchemaInfo tableSchemaInfo = null;
 
            if (node.NodeType == XmlNodeType.Element) {         // If element
                tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
            }                                                   // Look up table schema info for it
 
            if (tableSchemaInfo != null) {                      // Got info ?
                return tableSchemaInfo.TableSchema;             // Yes, Return table
            }
 
            return GetColumnSchema(node, fIgnoreNamespace);     // Attempt to locate column
        }        
 
        public DataTable GetTableForNode(XmlReader node, bool fIgnoreNamespace) {
            TableSchemaInfo tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
            if (tableSchemaInfo != null) {
                lastTableSchemaInfo = tableSchemaInfo;
                return lastTableSchemaInfo.TableSchema;
            }
            return null;
        }
 
        private void HandleSpecialColumn(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
            // if column name starts with xml, we encode it manualy and add it for look up
            Debug.Assert(col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase), "column name should start with xml");
            string tempColumnName;
 
            if ('x' == col.ColumnName[0]) {
                tempColumnName = "_x0078_"; // lower case xml... -> _x0078_ml...
            }
            else {
                tempColumnName = "_x0058_"; // upper case Xml... -> _x0058_ml...
            }
            
            tempColumnName += col.ColumnName.Substring(1);
            
            if(nameTable.Get(tempColumnName) == null) {
                nameTable.Add(tempColumnName);
            }
            string columnNamespace = nameTable.Get(col.Namespace);
            XmlNodeIdentety idColumn = new XmlNodeIdentety(tempColumnName, columnNamespace);
            columns[idColumn] = col;
        }
 
    }
}