File: System\Data\XmlDataLoader.cs
Web Access
Project: src\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.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
 
namespace System.Data
{
    internal sealed class XmlDataLoader
    {
        private readonly DataSet? _dataSet;
        private XmlToDatasetMap? _nodeToSchemaMap;
        private readonly Hashtable _nodeToRowMap;
        private readonly Stack<DataRow>? _childRowsStack;
        private readonly bool _fIsXdr;
        internal bool _isDiffgram;
 
        private XmlElement? _topMostNode;
        private readonly bool _ignoreSchema;
 
        private readonly DataTable? _dataTable;
        private readonly bool _isTableLevel;
        private bool _fromInference;
 
        internal XmlDataLoader(DataSet dataset, bool IsXdr, bool ignoreSchema)
        {
            // Initialization
            _dataSet = dataset;
            _nodeToRowMap = new Hashtable();
            _fIsXdr = IsXdr;
            _ignoreSchema = ignoreSchema;
        }
 
        internal XmlDataLoader(DataSet dataset, bool IsXdr, XmlElement topNode, bool ignoreSchema)
        {
            // Initialization
            _dataSet = dataset;
            _nodeToRowMap = new Hashtable();
            _fIsXdr = IsXdr;
 
            // Allocate the stack and create the mappings
            _childRowsStack = new Stack<DataRow>(50);
 
            _topMostNode = topNode;
            _ignoreSchema = ignoreSchema;
        }
 
        internal XmlDataLoader(DataTable datatable, bool IsXdr, bool ignoreSchema)
        {
            // Initialization
            _dataSet = null;
            _dataTable = datatable;
            _isTableLevel = true;
            _nodeToRowMap = new Hashtable();
            _fIsXdr = IsXdr;
            _ignoreSchema = ignoreSchema;
        }
 
        internal XmlDataLoader(DataTable datatable, bool IsXdr, XmlElement topNode, bool ignoreSchema)
        {
            // Initialization
            _dataSet = null;
            _dataTable = datatable;
            _isTableLevel = true;
            _nodeToRowMap = new Hashtable();
            _fIsXdr = IsXdr;
 
            // Allocate the stack and create the mappings
 
            _childRowsStack = new Stack<DataRow>(50);
            _topMostNode = topNode;
            _ignoreSchema = ignoreSchema;
        }
 
        internal bool FromInference
        {
            get
            {
                return _fromInference;
            }
            set
            {
                _fromInference = value;
            }
        }
 
        // after loading, all detached DataRows are attached to their tables
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void AttachRows(DataRow? parentRow, XmlNode parentElement)
        {
            if (parentElement == null)
                return;
 
            for (XmlNode? n = parentElement.FirstChild; n != null; n = n.NextSibling)
            {
                if (n.NodeType == XmlNodeType.Element)
                {
                    XmlElement e = (XmlElement)n;
                    DataRow? r = GetRowFromElement(e);
                    if (r != null && r.RowState == DataRowState.Detached)
                    {
                        if (parentRow != null)
                            r.SetNestedParentRow(parentRow, /*setNonNested*/ false);
 
                        r.Table.Rows.Add(r);
                    }
                    else if (r == null)
                    {
                        // n is a 'sugar element'
                        AttachRows(parentRow, n);
                    }
 
                    // attach all detached rows
                    AttachRows(r, n);
                }
            }
        }
 
        private static int CountNonNSAttributes(XmlNode node)
        {
            int count = 0;
            for (int i = 0; i < node.Attributes!.Count; i++)
            {
                if (!FExcludedNamespace(node.Attributes[i].NamespaceURI))
                    count++;
            }
            return count;
        }
 
        private static string GetValueForTextOnlyColumns(XmlNode? n)
        {
            string? value = null;
 
            // don't consider whitespace
            while (n != null && (n.NodeType == XmlNodeType.Whitespace || !IsTextLikeNode(n.NodeType)))
            {
                n = n.NextSibling;
            }
 
            if (n != null)
            {
                if (IsTextLikeNode(n.NodeType) && (n.NextSibling == null || !IsTextLikeNode(n.NodeType)))
                {
                    // don't use string builder if only one text node exists
                    value = n.Value;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    while (n != null && IsTextLikeNode(n.NodeType))
                    {
                        sb.Append(n.Value);
                        n = n.NextSibling;
                    }
                    value = sb.ToString();
                }
            }
 
            return value ?? string.Empty;
        }
 
        private static string GetInitialTextFromNodes(ref XmlNode? n)
        {
            string? value = null;
 
            if (n != null)
            {
                // don't consider whitespace
                while (n.NodeType == XmlNodeType.Whitespace)
                    n = n.NextSibling!;
 
                if (IsTextLikeNode(n.NodeType) && (n.NextSibling == null || !IsTextLikeNode(n.NodeType)))
                {
                    // don't use string builder if only one text node exists
                    value = n.Value;
                    n = n.NextSibling;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    while (n != null && IsTextLikeNode(n.NodeType))
                    {
                        sb.Append(n.Value);
                        n = n.NextSibling;
                    }
                    value = sb.ToString();
                }
            }
 
            return value ?? string.Empty;
        }
 
        private static DataColumn? GetTextOnlyColumn(DataRow row)
        {
            DataColumnCollection columns = row.Table.Columns;
            int cCols = columns.Count;
            for (int iCol = 0; iCol < cCols; iCol++)
            {
                DataColumn c = columns[iCol];
                if (IsTextOnly(c))
                    return c;
            }
            return null;
        }
 
        internal DataRow? GetRowFromElement(XmlElement e)
        {
            return (DataRow?)_nodeToRowMap[e];
        }
 
        internal bool FColumnElement(XmlElement e)
        {
            if (_nodeToSchemaMap!.GetColumnSchema(e, FIgnoreNamespace(e)) == null)
                return false;
 
            if (CountNonNSAttributes(e) > 0)
                return false;
 
            for (XmlNode? tabNode = e.FirstChild; tabNode != null; tabNode = tabNode.NextSibling)
                if (tabNode is XmlElement)
                    return false;
 
            return true;
        }
 
        private static bool FExcludedNamespace(string ns)
        {
            return ns.Equals(Keywords.XSD_XMLNS_NS);
        }
 
        private bool FIgnoreNamespace(XmlNode node)
        {
            XmlNode? ownerNode;
            if (!_fIsXdr)
                return false;
            if (node is XmlAttribute)
                ownerNode = ((XmlAttribute)node).OwnerElement!;
            else
                ownerNode = node;
            if (ownerNode.NamespaceURI.StartsWith("x-schema:#", StringComparison.Ordinal))
                return true;
            else
                return false;
        }
 
        private bool FIgnoreNamespace(XmlReader node)
        {
            if (_fIsXdr && node.NamespaceURI.StartsWith("x-schema:#", StringComparison.Ordinal))
                return true;
            else
                return false;
        }
 
        internal static bool IsTextLikeNode(XmlNodeType n)
        {
            switch (n)
            {
                case XmlNodeType.EntityReference:
                    throw ExceptionBuilder.FoundEntity();
 
                case XmlNodeType.Text:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.CDATA:
                    return true;
 
                default:
                    return false;
            }
        }
 
        internal static bool IsTextOnly(DataColumn c)
        {
            if (c.ColumnMapping != MappingType.SimpleContent)
                return false;
            else
                return true;
        }
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        internal void LoadData(XmlDocument xdoc)
        {
            if (xdoc.DocumentElement == null)
                return;
 
            bool saveEnforce;
 
            if (_isTableLevel)
            {
                saveEnforce = _dataTable!.EnforceConstraints;
                _dataTable.EnforceConstraints = false;
            }
            else
            {
                saveEnforce = _dataSet!.EnforceConstraints;
                _dataSet.EnforceConstraints = false;
                _dataSet._fInReadXml = true;
            }
 
            if (_isTableLevel)
            {
                _nodeToSchemaMap = new XmlToDatasetMap(_dataTable!, xdoc.NameTable);
            }
            else
            {
                _nodeToSchemaMap = new XmlToDatasetMap(_dataSet!, xdoc.NameTable);
            }
            /*
                        // Top level table or dataset ?
                        XmlElement rootElement = xdoc.DocumentElement;
                        Hashtable tableAtoms = new Hashtable();
                        XmlNode tabNode;
                        if (CountNonNSAttributes (rootElement) > 0)
                            dataSet.fTopLevelTable = true;
                        else {
                            for (tabNode = rootElement.FirstChild; tabNode != null; tabNode = tabNode.NextSibling) {
                                if (tabNode is XmlElement && tabNode.LocalName != Keywords.XSD_SCHEMA) {
                                    object value = tableAtoms[QualifiedName (tabNode.LocalName, tabNode.NamespaceURI)];
                                    if (value == null || (bool)value == false) {
                                        dataSet.fTopLevelTable = true;
                                        break;
                                    }
                                }
                            }
                        }
            */
            DataRow? topRow = null;
            if (_isTableLevel || (_dataSet != null && _dataSet._fTopLevelTable))
            {
                XmlElement e = xdoc.DocumentElement;
                DataTable? topTable = (DataTable?)_nodeToSchemaMap.GetSchemaForNode(e, FIgnoreNamespace(e));
                if (topTable != null)
                {
                    topRow = topTable.CreateEmptyRow(); //enzol perf
                    _nodeToRowMap[e] = topRow;
 
                    // get all field values.
                    LoadRowData(topRow, e);
                    topTable.Rows.Add(topRow);
                }
            }
 
            LoadRows(topRow, xdoc.DocumentElement);
            AttachRows(topRow, xdoc.DocumentElement);
 
 
            if (_isTableLevel)
            {
                _dataTable!.EnforceConstraints = saveEnforce;
            }
            else
            {
                _dataSet!._fInReadXml = false;
                _dataSet.EnforceConstraints = saveEnforce;
            }
        }
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void LoadRowData(DataRow row, XmlElement rowElement)
        {
            XmlNode? n;
            DataTable table = row.Table;
            if (FromInference)
                table.Prefix = rowElement.Prefix;
 
            // keep a list of all columns that get updated
            Hashtable foundColumns = new Hashtable();
 
            row.BeginEdit();
 
            // examine all children first
            n = rowElement.FirstChild;
 
            // Look for data to fill the TextOnly column
            DataColumn? column = GetTextOnlyColumn(row);
            if (column != null)
            {
                foundColumns[column] = column;
                string text = GetValueForTextOnlyColumns(n);
                if (XMLSchema.GetBooleanAttribute(rowElement, Keywords.XSI_NIL, Keywords.XSINS, false) && string.IsNullOrEmpty(text))
                    row[column] = DBNull.Value;
                else
                    SetRowValueFromXmlText(row, column, text);
            }
 
            // Walk the region to find elements that map to columns
            while (n != null && n != rowElement)
            {
                if (n.NodeType == XmlNodeType.Element)
                {
                    XmlElement e = (XmlElement)n;
 
                    object? schema = _nodeToSchemaMap!.GetSchemaForNode(e, FIgnoreNamespace(e));
                    if (schema is DataTable)
                    {
                        if (FColumnElement(e))
                            schema = _nodeToSchemaMap.GetColumnSchema(e, FIgnoreNamespace(e));
                    }
 
                    // if element has its own table mapping, it is a separate region
                    if (schema == null || schema is DataColumn)
                    {
                        // descend to examine child elements
                        n = e.FirstChild;
 
                        if (schema != null && schema is DataColumn)
                        {
                            DataColumn c = (DataColumn)schema;
 
                            if (c.Table == row.Table && c.ColumnMapping != MappingType.Attribute && foundColumns[c] == null)
                            {
                                foundColumns[c] = c;
                                string text = GetValueForTextOnlyColumns(n);
                                if (XMLSchema.GetBooleanAttribute(e, Keywords.XSI_NIL, Keywords.XSINS, false) && string.IsNullOrEmpty(text))
                                    row[c] = DBNull.Value;
                                else
                                    SetRowValueFromXmlText(row, c, text);
                            }
                        }
                        else if ((schema == null) && (n != null))
                        {
                            continue;
                        }
 
 
                        // nothing left down here, continue from element
                        n ??= e;
                    }
                }
 
                // if no more siblings, ascend back toward original element (rowElement)
                while (n != rowElement && n!.NextSibling == null)
                {
                    n = n.ParentNode;
                }
 
                if (n != rowElement)
                    n = n.NextSibling;
            }
 
            //
            // Walk the attributes to find attributes that map to columns.
            //
            foreach (XmlAttribute attr in rowElement.Attributes)
            {
                object? schema = _nodeToSchemaMap!.GetColumnSchema(attr, FIgnoreNamespace(attr));
                if (schema != null && schema is DataColumn)
                {
                    DataColumn c = (DataColumn)schema;
 
                    if (c.ColumnMapping == MappingType.Attribute && foundColumns[c] == null)
                    {
                        foundColumns[c] = c;
                        n = attr.FirstChild;
                        SetRowValueFromXmlText(row, c, GetInitialTextFromNodes(ref n));
                    }
                }
            }
 
            // Null all columns values that aren't represented in the tree
            foreach (DataColumn c in row.Table.Columns)
            {
                if (foundColumns[c] == null && XmlToDatasetMap.IsMappedColumn(c))
                {
                    if (!c.AutoIncrement)
                    {
                        if (c.AllowDBNull)
                        {
                            row[c] = DBNull.Value;
                        }
                        else
                        {
                            row[c] = c.DefaultValue;
                        }
                    }
                    else
                    {
                        c.Init(row._tempRecord);
                    }
                }
            }
 
            row.EndEdit();
        }
 
 
        // load all data from tree structure into datarows
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void LoadRows(DataRow? parentRow, XmlNode parentElement)
        {
            if (parentElement == null)
                return;
 
            // Skip schema node as well
            if (parentElement.LocalName == Keywords.XSD_SCHEMA && parentElement.NamespaceURI == Keywords.XSDNS ||
                parentElement.LocalName == Keywords.SQL_SYNC && parentElement.NamespaceURI == Keywords.UPDGNS ||
                parentElement.LocalName == Keywords.XDR_SCHEMA && parentElement.NamespaceURI == Keywords.XDRNS)
                return;
 
            for (XmlNode? n = parentElement.FirstChild; n != null; n = n.NextSibling)
            {
                if (n is XmlElement e)
                {
                    object? schema = _nodeToSchemaMap!.GetSchemaForNode(e, FIgnoreNamespace(e));
 
                    if (schema != null && schema is DataTable)
                    {
                        DataRow? r = GetRowFromElement(e);
                        if (r == null)
                        {
                            // skip columns which has the same name as another table
                            if (parentRow != null && FColumnElement(e))
                                continue;
 
                            r = ((DataTable)schema).CreateEmptyRow();
                            _nodeToRowMap[e] = r;
 
                            // get all field values.
                            LoadRowData(r, e);
                        }
 
                        // recurse down to inner elements
                        LoadRows(r, n);
                    }
                    else
                    {
                        // recurse down to inner elements
                        LoadRows(null, n);
                    }
                }
            }
        }
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private static void SetRowValueFromXmlText(DataRow row, DataColumn col, string xmlText)
        {
            row[col] = col.ConvertXmlToObject(xmlText);
        }
 
        private XmlReader? _dataReader;
        private object? _XSD_XMLNS_NS;
        private object? _XDR_SCHEMA;
        private object? _XDRNS;
        private object? _SQL_SYNC;
        private object? _UPDGNS;
        private object? _XSD_SCHEMA;
        private object? _XSDNS;
 
        private object? _DFFNS;
        private object? _MSDNS;
        private object? _DIFFID;
        private object? _HASCHANGES;
        private object? _ROWORDER;
 
        private void InitNameTable()
        {
            XmlNameTable nameTable = _dataReader!.NameTable;
 
            _XSD_XMLNS_NS = nameTable.Add(Keywords.XSD_XMLNS_NS);
            _XDR_SCHEMA = nameTable.Add(Keywords.XDR_SCHEMA);
            _XDRNS = nameTable.Add(Keywords.XDRNS);
            _SQL_SYNC = nameTable.Add(Keywords.SQL_SYNC);
            _UPDGNS = nameTable.Add(Keywords.UPDGNS);
            _XSD_SCHEMA = nameTable.Add(Keywords.XSD_SCHEMA);
            _XSDNS = nameTable.Add(Keywords.XSDNS);
 
            _DFFNS = nameTable.Add(Keywords.DFFNS);
            _MSDNS = nameTable.Add(Keywords.MSDNS);
            _DIFFID = nameTable.Add(Keywords.DIFFID);
            _HASCHANGES = nameTable.Add(Keywords.HASCHANGES);
            _ROWORDER = nameTable.Add(Keywords.ROWORDER);
        }
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        internal void LoadData(XmlReader reader)
        {
            _dataReader = DataTextReader.CreateReader(reader);
 
            int entryDepth = _dataReader.Depth;                  // Store current XML element depth so we'll read
                                                                 // correct portion of the XML and no more
            bool fEnforce = _isTableLevel ? _dataTable!.EnforceConstraints : _dataSet!.EnforceConstraints;
            // Keep constraints status for datataset/table
            InitNameTable();                                    // Adds DataSet namespaces to reader's nametable
 
            // Create XML to dataset map
            _nodeToSchemaMap ??= _isTableLevel ? new XmlToDatasetMap(_dataReader.NameTable, _dataTable!) :
                                                 new XmlToDatasetMap(_dataReader.NameTable, _dataSet!);
 
            if (_isTableLevel)
            {
                _dataTable!.EnforceConstraints = false;           // Disable constraints
            }
            else
            {
                _dataSet!.EnforceConstraints = false;             // Disable constraints
                _dataSet._fInReadXml = true;                      // We're in ReadXml now
            }
 
            if (_topMostNode != null)
            {                          // Do we have top node?
                if (!_isDiffgram && !_isTableLevel)
                {             // Not a diffgram  and not DataSet?
                    DataTable? table = _nodeToSchemaMap.GetSchemaForNode(_topMostNode, FIgnoreNamespace(_topMostNode)) as DataTable;
                    // Try to match table in the dataset to this node
                    if (table != null)
                    {                        // Got the table ?
                        LoadTopMostTable(table);                // Load top most node
                    }
                }
 
                _topMostNode = null;                             // topMostNode is no more. Good riddance.
            }
 
            while (!_dataReader.EOF)
            {                          // Main XML parsing loop. Check for EOF just in case.
                if (_dataReader.Depth < entryDepth)              // Stop if we have consumed all elements allowed
                    break;
 
                if (reader.NodeType != XmlNodeType.Element)
                { // Read till Element is found
                    _dataReader.Read();
                    continue;
                }
                DataTable? table = _nodeToSchemaMap.GetTableForNode(_dataReader, FIgnoreNamespace(_dataReader));
                // Try to get table for node
                if (table == null)
                {                            // Read till table is found
                    if (!ProcessXsdSchema())                    // Check for schemas...
                        _dataReader.Read();                      // Not found? Read next element.
 
                    continue;
                }
 
                LoadTable(table, false /* isNested */);        // Here goes -- load data for this table
                                                               // This is a root table, so it's not nested
            }
 
            if (_isTableLevel)
            {
                _dataTable!.EnforceConstraints = fEnforce;        // Restore constraints and return
            }
            else
            {
                _dataSet!._fInReadXml = false;                     // We're done.
                _dataSet.EnforceConstraints = fEnforce;          // Restore constraints and return
            }
        }
 
        // Loads a top most table.
        // This is neded because .NET Framework is capable of loading almost anything into the dataset.
        // The top node could be a DataSet element or a Table element. To make things worse,
        // you could have a table with the same name as dataset.
        // Here's how we're going to dig into this mess:
        //
        //                                 TopNode is null ?
        //                                / No           \ Yes
        //                  Table matches TopNode ?       Current node is the table start
        //                 / No                  \ Yes (LoadTopMostTable called in this case only)
        //   Current node is the table start    DataSet name matches one of the tables ?
        //      TopNode is dataset node        / Yes                                  \ No
        //                                    /                                        TopNode is the table
        //      Current node matches column or nested table in the table ?             and current node
        //     / No                                                 \ Yes              is a column or a
        //     TopNode is DataSet                            TopNode is table          nested table
        //
        // Yes, it is terrible and I don't like it also..
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void LoadTopMostTable(DataTable table)
        {
            //        /------------------------------- This one is in topMostNode (backed up to XML DOM)
            //  <Table> /----------------------------- We are here on entrance
            //      <Column>Value</Column>
            //      <AnotherColumn>Value</AnotherColumn>
            //  </Table> ...
            //            \------------------------------ We are here on exit
 
 
            Debug.Assert(table != null, "Table to be loaded is null on LoadTopMostTable() entry");
            Debug.Assert(_topMostNode != null, "topMostNode is null on LoadTopMostTable() entry");
            Debug.Assert(!_isDiffgram, "Diffgram mode is on while we have topMostNode table. This is bad.");
 
            bool topNodeIsTable = _isTableLevel || (_dataSet!.DataSetName != table.TableName);
            // If table name we have matches dataset
            // name top node could be a DataSet OR a table.
            // It's a table overwise.
            DataRow? row;                                 // Data row we're going to add to this table
 
            bool matchFound = false;                            // Assume we found no matching elements
 
            int entryDepth = _dataReader!.Depth - 1;              // Store current reader depth so we know when to stop reading
                                                                 // Adjust depth by one as we've read top most element
                                                                 // outside this method.
            string textNodeValue;                               // Value of a text node we might have
 
            Debug.Assert(entryDepth >= 0, "Wrong entry Depth for top most element.");
 
            int entryChild = _childRowsStack!.Count;              // Memorize child stack level on entry
 
            DataColumn? c;                                       // Hold column here
            DataColumnCollection collection = table.Columns;    // Hold column collectio here
 
            object[] foundColumns = new object[collection.Count];
            // This is the columns data we might find
            XmlNode? n;                                          // Need this to pass by reference
 
            foreach (XmlAttribute attr in _topMostNode.Attributes)
            {
                // Check all attributes in this node
 
                c = _nodeToSchemaMap!.GetColumnSchema(attr, FIgnoreNamespace(attr)) as DataColumn;
                // Try to match attribute to column
                if ((c != null) && (c.ColumnMapping == MappingType.Attribute))
                {
                    // If it's a column with attribute mapping
                    n = attr.FirstChild;
 
                    foundColumns[c.Ordinal] = c.ConvertXmlToObject(GetInitialTextFromNodes(ref n));
                    // Get value
                    matchFound = true;                          // and note we found a matching element
                }
            }
 
            // Now handle elements. This could be columns or nested tables
            // We'll skip the rest as we have no idea what to do with it.
 
            // Note: we do not need to read first as we're already as it has been done by caller.
 
            while (entryDepth < _dataReader.Depth)
            {
                switch (_dataReader.NodeType)
                {                  // Process nodes based on type
                    case XmlNodeType.Element:                       // It's an element
                        object? o = _nodeToSchemaMap!.GetColumnSchema(table, _dataReader, FIgnoreNamespace(_dataReader));
                        // Get dataset element for this XML element
                        c = o as DataColumn;                        // Perhaps, it's a column?
 
                        if (c != null)
                        {                          // Do we have matched column in this table?
                                                   // Let's load column data
 
                            if (foundColumns[c.Ordinal] == null)
                            {
                                // If this column was not found before
                                LoadColumn(c, foundColumns);       // Get column value.
                                matchFound = true;                  // Got matched row.
                            }
                            else
                            {
                                _dataReader.Read();                  // Advance to next element.
                            }
                        }
                        else
                        {
                            DataTable? nestedTable = o as DataTable;
                            // Perhaps, it's a nested table ?
                            if (nestedTable != null)
                            {            // Do we have matched table in DataSet ?
                                LoadTable(nestedTable, true /* isNested */);
                                // Yes. Load nested table (recursive)
                                matchFound = true;                  // Got matched nested table
                            }
                            else if (ProcessXsdSchema())
                            {          // Check for schema. Skip or load if found.
                                continue;                           // Schema has been found. Process the next element
                                                                    // we're already at (done by schema processing).
                            }
                            else
                            {                                  // Not a table or column in this table ?
                                if (!(matchFound || topNodeIsTable))
                                {
                                    // Could top node be a DataSet?
 
 
                                    return;                         // Assume top node is DataSet
                                                                    // and stop top node processing
                                }
                                _dataReader.Read();                  // Continue to the next element.
                            }
                        }
                        break;
                    // Oops. Not supported
                    case XmlNodeType.EntityReference:               // Oops. No support for Entity Reference
                        throw ExceptionBuilder.FoundEntity();
                    case XmlNodeType.Text:                          // It looks like a text.
                    case XmlNodeType.Whitespace:                    // This actually could be
                    case XmlNodeType.CDATA:                         // if we have XmlText in our table
                    case XmlNodeType.SignificantWhitespace:
                        textNodeValue = _dataReader.ReadString();
                        // Get text node value.
                        c = table._xmlText;                          // Get XML Text column from our table
 
                        if (c != null && foundColumns[c.Ordinal] == null)
                        {
                            // If XmlText Column is set
                            // and we do not have data already
                            foundColumns[c.Ordinal] = c.ConvertXmlToObject(textNodeValue);
                            // Read and store the data
                        }
 
                        break;
                    default:
                        _dataReader.Read();                  // We don't process that, skip to the next element.
                        break;
                }
            }
 
            _dataReader.Read();                          // Proceed to the next element.
 
            // It's the time to populate row with loaded data and add it to the table we'we just read to the table
 
            for (int i = foundColumns.Length - 1; i >= 0; --i)
            {
                // Check all columns
                if (null == foundColumns[i])
                {              // Got data for this column ?
                    c = collection[i];                      // No. Get column for this index
 
                    if (c.AllowDBNull && c.ColumnMapping != MappingType.Hidden && !c.AutoIncrement)
                    {
                        foundColumns[i] = DBNull.Value;     // Assign DBNull if possible
                                                            // table.Rows.Add() below will deal
                                                            // with default values and autoincrement
                    }
                }
            }
 
            row = table.Rows.AddWithColumnEvents(foundColumns);             // Create, populate and add row
 
            while (entryChild < _childRowsStack.Count)
            {     // Process child rows we might have
                DataRow childRow = _childRowsStack.Pop();
                // Get row from the stack
                bool unchanged = (childRow.RowState == DataRowState.Unchanged);
                // Is data the same as before?
                childRow.SetNestedParentRow(row, /*setNonNested*/ false);
                // Set parent row
                if (unchanged)                              // Restore record if child row's unchanged
                    childRow._oldRecord = childRow._newRecord;
            }
        }
 
        // Loads a table.
        // Yes, I know it's a big method. This is done to avoid performance penalty of calling methods
        // with many arguments and to keep recursion within one method only. To make code readable,
        // this method divided into 3 parts: attribute processing (including diffgram),
        // nested elements processing and loading data. Please keep it this way.
 
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void LoadTable(DataTable table, bool isNested)
        {
            //  <DataSet> /--------------------------- We are here on entrance
            //      <Table>
            //          <Column>Value</Column>
            //          <AnotherColumn>Value</AnotherColumn>
            //      </Table>    /-------------------------- We are here on exit
            //      <AnotherTable>
            //      ...
            //      </AnotherTable>
            //      ...
            //  </DataSet>
 
            Debug.Assert(table != null, "Table to be loaded is null on LoadTable() entry");
 
            DataRow? row;                                 // Data row we're going to add to this table
 
            int entryDepth = _dataReader!.Depth;                  // Store current reader depth so we know when to stop reading
            int entryChild = _childRowsStack!.Count;              // Memorize child stack level on entry
 
            DataColumn? c;                                       // Hold column here
            DataColumnCollection collection = table.Columns;    // Hold column collectio here
 
            object[] foundColumns = new object[collection.Count];
            // This is the columns data we found
            // This is used to process diffgramms
 
            int rowOrder = -1;                                  // Row to insert data to
            string diffId = string.Empty;                       // Diffgram ID string
            string? hasChanges = null;                           // Changes string
            bool hasErrors = false;                             // Set this in case of problem
 
            string textNodeValue;                               // Value of a text node we might have
 
            // Process attributes first
 
            for (int i = _dataReader.AttributeCount - 1; i >= 0; --i)
            {
                // Check all attributes one by one
                _dataReader.MoveToAttribute(i);                  // Get this attribute
 
                c = _nodeToSchemaMap!.GetColumnSchema(table, _dataReader, FIgnoreNamespace(_dataReader)) as DataColumn;
                // Try to get column for this attribute
 
                if ((c != null) && (c.ColumnMapping == MappingType.Attribute))
                {
                    // Yep, it is a column mapped as attribute
                    // Get value from XML and store it in the object array
                    foundColumns[c.Ordinal] = c.ConvertXmlToObject(_dataReader.Value);
                }                                               // Oops. No column for this element
 
                if (_isDiffgram)
                {                             // Now handle some diffgram attributes
                    if (_dataReader.NamespaceURI == Keywords.DFFNS)
                    {
                        switch (_dataReader.LocalName)
                        {
                            case Keywords.DIFFID:                   // Is it a diffgeam ID ?
                                diffId = _dataReader.Value;          // Store ID
                                break;
                            case Keywords.HASCHANGES:               // Has changes attribute ?
                                hasChanges = _dataReader.Value;      // Store value
                                break;
                            case Keywords.HASERRORS:                // Has errors attribute ?
                                hasErrors = (bool)Convert.ChangeType(_dataReader.Value, typeof(bool), CultureInfo.InvariantCulture);
                                // Store value
                                break;
                        }
                    }
                    else if (_dataReader.NamespaceURI == Keywords.MSDNS)
                    {
                        if (_dataReader.LocalName == Keywords.ROWORDER)
                        {
                            // Is it a row order attribute ?
                            rowOrder = (int)Convert.ChangeType(_dataReader.Value, typeof(int), CultureInfo.InvariantCulture);
                            // Store it
                        }
                        else if (_dataReader.LocalName.StartsWith("hidden", StringComparison.Ordinal))
                        {
                            // Hidden column ?
                            c = collection[XmlConvert.DecodeName(_dataReader.LocalName.Substring(6))];
                            // Let's see if we have one.
                            // We have to decode name before we look it up
                            // We could not use XmlToDataSet map as it contains
                            // no hidden columns
                            if ((c != null) && (c.ColumnMapping == MappingType.Hidden))
                            {
                                // Got column and it is hidden ?
                                foundColumns[c.Ordinal] = c.ConvertXmlToObject(_dataReader.Value);
                            }
                        }
                    }
                }
            }                                                   // Done with attributes
 
            // Now handle elements. This could be columns or nested tables.
 
            //  <DataSet> /------------------- We are here after dealing with attributes
            //      <Table foo="FooValue" bar="BarValue">
            //          <Column>Value</Column>
            //          <AnotherColumn>Value</AnotherColumn>
            //      </Table>
            //  </DataSet>
 
            if (_dataReader.Read() && entryDepth < _dataReader.Depth)
            {
                // Read to the next element and see if we're inside
                while (entryDepth < _dataReader.Depth)
                {       // Get out as soon as we've processed all nested nodes.
                    switch (_dataReader.NodeType)
                    {              // Process nodes based on type
                        case XmlNodeType.Element:                   // It's an element
                            object? o = _nodeToSchemaMap!.GetColumnSchema(table, _dataReader, FIgnoreNamespace(_dataReader));
                            // Get dataset element for this XML element
                            c = o as DataColumn;                    // Perhaps, it's a column?
 
                            if (c != null)
                            {                      // Do we have matched column in this table?
                                                   // Let's load column data
                                if (foundColumns[c.Ordinal] == null)
                                {
                                    // If this column was not found before
                                    LoadColumn(c, foundColumns);
                                    // Get column value
                                }
                                else
                                {
                                    _dataReader.Read();              // Advance to next element.
                                }
                            }
                            else
                            {
                                DataTable? nestedTable = o as DataTable;
                                // Perhaps, it's a nested table ?
                                if (nestedTable != null)
                                {        // Do we have matched nested table in DataSet ?
                                    LoadTable(nestedTable, true /* isNested */);
                                    // Yes. Load nested table (recursive)
                                }                                   // Not a table nor column? Check if it's schema.
                                else if (ProcessXsdSchema())
                                {      // Check for schema. Skip or load if found.
                                    continue;                       // Schema has been found. Process the next element
                                                                    // we're already at (done by schema processing).
                                }
                                else
                                {
                                    // We've got element which is not supposed to he here according to the schema.
                                    // That might be a table which was misplaced. We should've thrown on that,
                                    // but we'll try to load it so we could keep compatibility.
                                    // We won't try to match to columns as we have no idea
                                    // which table this potential column might belong to.
                                    DataTable? misplacedTable = _nodeToSchemaMap.GetTableForNode(_dataReader, FIgnoreNamespace(_dataReader));
                                    // Try to get table for node
 
                                    if (misplacedTable != null)
                                    {   // Got some matching table?
                                        LoadTable(misplacedTable, false /* isNested */);
                                        // While table's XML element is nested,
                                        // the table itself is not. Load it this way.
                                    }
                                    else
                                    {
                                        _dataReader.Read();          // Not a table? Try next element.
                                    }
                                }
                            }
                            break;
                        case XmlNodeType.EntityReference:           // Oops. No support for Entity Reference
                            throw ExceptionBuilder.FoundEntity();
                        case XmlNodeType.Text:                      // It looks like a text.
                        case XmlNodeType.Whitespace:                // This actually could be
                        case XmlNodeType.CDATA:                     // if we have XmlText in our table
                        case XmlNodeType.SignificantWhitespace:
                            textNodeValue = _dataReader.ReadString();
                            // Get text node value.
                            c = table._xmlText;                      // Get XML Text column from our table
 
                            if (c != null && foundColumns[c.Ordinal] == null)
                            {
                                // If XmlText Column is set
                                // and we do not have data already
                                foundColumns[c.Ordinal] = c.ConvertXmlToObject(textNodeValue);
                                // Read and store the data
                            }
                            break;
                        default:
                            _dataReader.Read();                  // We don't process that, skip to the next element.
                            break;
                    }
                }
 
                _dataReader.Read();                              // We're done here, proceed to the next element.
            }
 
            // It's the time to populate row with loaded data and add it to the table we'we just read to the table
 
            if (_isDiffgram)
            {                               // In case of diffgram
                row = table.NewRow(table.NewUninitializedRecord());
                // just create an empty row
                row.BeginEdit();                            // and allow it's population with data
 
                for (int i = foundColumns.Length - 1; i >= 0; --i)
                {
                    // Check all columns
                    c = collection[i];                      // Get column for this index
 
                    c[row._tempRecord] = foundColumns[i] ?? DBNull.Value;
                    // Set column to loaded value of to
                    // DBNull if value is missing.
                }
 
                row.EndEdit();                              // Done with this row
 
                table.Rows.DiffInsertAt(row, rowOrder);     // insert data to specific location
 
                // And do some diff processing
                if (hasChanges == null)
                {                   // No changes ?
                    row._oldRecord = row._newRecord;          // Restore old record
                }
 
                if ((hasChanges == Keywords.MODIFIED) || hasErrors)
                {
                    table.RowDiffId[diffId] = row;
                }
            }
            else
            {
                for (int i = foundColumns.Length - 1; i >= 0; --i)
                {
                    // Check all columns
                    if (null == foundColumns[i])
                    {          // Got data for this column ?
                        c = collection[i];                  // No. Get column for this index
 
                        if (c.AllowDBNull && c.ColumnMapping != MappingType.Hidden && !c.AutoIncrement)
                        {
                            foundColumns[i] = DBNull.Value; // Assign DBNull if possible
                                                            // table.Rows.Add() below will deal
                                                            // with default values and autoincrement
                        }
                    }
                }
 
                row = table.Rows.AddWithColumnEvents(foundColumns);         // Create, populate and add row
            }
 
            // Data is loaded into the row and row is added to the table at this point
 
            while (entryChild < _childRowsStack.Count)
            {     // Process child rows we might have
                DataRow childRow = _childRowsStack.Pop();
                // Get row from the stack
                bool unchanged = (childRow.RowState == DataRowState.Unchanged);
                // Is data the same as before?
                childRow.SetNestedParentRow(row, /*setNonNested*/ false);
                // Set parent row
 
                if (unchanged)                              // Restore record if child row's unchanged
                    childRow._oldRecord = childRow._newRecord;
            }
 
            if (isNested)                                   // Got parent ?
                _childRowsStack.Push(row);                   // Push row to the stack
        }
 
        // Returns column value
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private void LoadColumn(DataColumn column, object[] foundColumns)
        {
            //  <DataSet>    /--------------------------------- We are here on entrance
            //      <Table> /
            //          <Column>Value</Column>
            //          <AnotherColumn>Value</AnotherColumn>
            //      </Table>    \------------------------------ We are here on exit
            //  </DataSet>
 
            //  <Column>                                        If we have something like this
            //      <Foo>FooVal</Foo>                           We would grab first text-like node
            //      Value                                       In this case it would be "FooVal"
            //      <Bar>BarVal</Bar>                           And not "Value" as you might think
            //  </Column>                                       This is how .NET Framework works
 
            string text = string.Empty;                         // Column text. Assume empty string
            string? xsiNilString = null;                         // Possible NIL attribute string
 
            int entryDepth = _dataReader!.Depth;                  // Store depth so we won't read too much
 
            if (_dataReader.AttributeCount > 0)                  // If have attributes
                xsiNilString = _dataReader.GetAttribute(Keywords.XSI_NIL, Keywords.XSINS);
            // Try to get NIL attribute
            // We have to do it before we move to the next element
            if (column.IsCustomType)
            {                          // Custom type column
                object? columnValue = null;                    // Column value we're after. Assume no value.
 
                string? xsiTypeString = null;                    // XSI type name from TYPE attribute
                string? typeName = null;                    // Type name from MSD_INSTANCETYPE attribute
 
                XmlRootAttribute? xmlAttrib = null;              // Might need this attribute for XmlSerializer
 
                if (_dataReader.AttributeCount > 0)
                {            // If have attributes, get attributes we'll need
                    xsiTypeString = _dataReader.GetAttribute(Keywords.TYPE, Keywords.XSINS);
                    typeName = _dataReader.GetAttribute(Keywords.MSD_INSTANCETYPE, Keywords.MSDNS);
                }
 
                // Check if need to use XmlSerializer. We need to do that if type does not implement IXmlSerializable.
                // We also need to do that if no polymorphism for this type allowed.
 
                bool useXmlSerializer = !column.ImplementsIXMLSerializable &&
                    !((column.DataType == typeof(object)) || (typeName != null) || (xsiTypeString != null));
 
                // Check if we have an attribute telling us value is null.
 
                if ((xsiNilString != null) && XmlConvert.ToBoolean(xsiNilString))
                {
                    if (!useXmlSerializer)
                    {                    // See if need to set typed null.
                        if (typeName != null && typeName.Length > 0)
                        {
                            // Got type name
                            columnValue = SqlUdtStorage.GetStaticNullForUdtType(DataStorage.GetType(typeName));
                        }
                    }
 
                    if (null == columnValue)
                    {                  // If no value,
                        columnValue = DBNull.Value;             // change to DBNull;
                    }
 
                    if (!_dataReader.IsEmptyElement)           // In case element is not empty
                        while (_dataReader.Read() && (entryDepth < _dataReader.Depth)) ;
                    // Read current elements
                    _dataReader.Read();                          // And start reading next element.
                }
                else
                {                                          // No NIL attribute. Get value
                    bool skipped = false;
 
                    if (column.Table!.DataSet != null && column.Table.DataSet._udtIsWrapped)
                    {
                        _dataReader.Read(); // if UDT is wrapped, skip the wrapper
                        skipped = true;
                    }
 
                    if (useXmlSerializer)
                    {                     // Create an attribute for XmlSerializer
                        if (skipped)
                        {
                            xmlAttrib = new XmlRootAttribute(_dataReader.LocalName);
                            xmlAttrib.Namespace = _dataReader.NamespaceURI;
                        }
                        else
                        {
                            xmlAttrib = new XmlRootAttribute(column.EncodedColumnName);
                            xmlAttrib.Namespace = column.Namespace;
                        }
                    }
 
                    columnValue = column.ConvertXmlToObject(_dataReader, xmlAttrib);
                    // Go get the value
                    if (skipped)
                    {
                        _dataReader.Read(); // if Wrapper is skipped, skip its end tag
                    }
                }
 
                foundColumns[column.Ordinal] = columnValue;     // Store value
            }
            else
            {                                                  // Not a custom type.
                if (_dataReader.Read() && entryDepth < _dataReader.Depth)
                {
                    // Read to the next element and see if we're inside.
                    while (entryDepth < _dataReader.Depth)
                    {
                        switch (_dataReader.NodeType)
                        {              // Process nodes based on type
                            case XmlNodeType.Text:                      // It looks like a text. And we need it.
                            case XmlNodeType.Whitespace:
                            case XmlNodeType.CDATA:
                            case XmlNodeType.SignificantWhitespace:
                                if (0 == text.Length)
                                {                 // In case we do not have value already
                                    text = _dataReader.Value;            // Get value.
 
                                    // See if we have other text nodes near. In most cases this loop will not be executed.
                                    StringBuilder? builder = null;
                                    while (_dataReader.Read() && entryDepth < _dataReader.Depth && IsTextLikeNode(_dataReader.NodeType))
                                    {
                                        builder ??= new StringBuilder(text);
                                        builder.Append(_dataReader.Value);  // Concatenate other sequential text like
                                                                            // nodes we might have. This is rare.
                                                                            // We're using this instead of dataReader.ReadString()
                                                                            // which would do the same thing but slower.
                                    }
 
                                    if (builder != null)
                                    {
                                        text = builder.ToString();
                                    }
                                }
                                else
                                {
                                    _dataReader.ReadString();            // We've got column value already. Read this one and ignore it.
                                }
                                break;
                            case XmlNodeType.Element:
                                if (ProcessXsdSchema())
                                {               // Check for schema. Skip or load if found.
                                    continue;                           // Schema has been found. Process the next element
                                                                        // we're already at (done by schema processing).
                                }
                                else
                                {
                                    // We've got element which is not supposed to he here.
                                    // That might be table which was misplaced.
                                    // Or it might be a column inside column (also misplaced).
                                    object? o = _nodeToSchemaMap!.GetColumnSchema(column.Table!, _dataReader, FIgnoreNamespace(_dataReader));
                                    // Get dataset element for this XML element
                                    DataColumn? c = o as DataColumn;     // Perhaps, it's a column?
 
                                    if (c != null)
                                    {                  // Do we have matched column in this table?
                                                       // Let's load column data
 
                                        if (foundColumns[c.Ordinal] == null)
                                        {
                                            // If this column was not found before
                                            LoadColumn(c, foundColumns);
                                            // Get column value
                                        }
                                        else
                                        {
                                            _dataReader.Read();          // Already loaded, proceed to the next element
                                        }
                                    }
                                    else
                                    {
                                        DataTable? nestedTable = o as DataTable;
                                        // Perhaps, it's a nested table ?
                                        if (nestedTable != null)
                                        {
                                            // Do we have matched table in DataSet ?
                                            LoadTable(nestedTable, true /* isNested */);
                                            // Yes. Load nested table (recursive)
                                        }
                                        else
                                        {                          // Not a nested column nor nested table.
                                                                   // Let's try other tables in the DataSet
 
                                            DataTable? misplacedTable = _nodeToSchemaMap.GetTableForNode(_dataReader, FIgnoreNamespace(_dataReader));
                                            // Try to get table for node
                                            if (misplacedTable != null)
                                            {
                                                // Got some table to match?
                                                LoadTable(misplacedTable, false /* isNested */);
                                                // While table's XML element is nested,
                                                // the table itself is not. Load it this way.
                                            }
                                            else
                                            {
                                                _dataReader.Read();      // No match? Try next element
                                            }
                                        }
                                    }
                                }
                                break;
                            case XmlNodeType.EntityReference:           // Oops. No support for Entity Reference
                                throw ExceptionBuilder.FoundEntity();
                            default:
                                _dataReader.Read();                      // We don't process that, skip to the next element.
                                break;
                        }
                    }
 
                    _dataReader.Read();                              // We're done here. To the next element.
                }
 
                if (0 == text.Length && xsiNilString != null && XmlConvert.ToBoolean(xsiNilString))
                {
                    foundColumns[column.Ordinal] = DBNull.Value;
                    // If no data and NIL attribute is true set value to null
                }
                else
                {
                    foundColumns[column.Ordinal] = column.ConvertXmlToObject(text);
                }
            }
        }
 
        // Check for schema and skips or loads XSD schema if found. Returns true if schema found.
        // DataReader would be set on the first XML element after the schema of schema was found.
        // If no schema detected, reader's position will not change.
        [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
        private bool ProcessXsdSchema()
        {
            if (((object)_dataReader!.LocalName == _XSD_SCHEMA && (object)_dataReader.NamespaceURI == _XSDNS))
            {
                // Found XSD schema
                if (_ignoreSchema)
                {                               // Should ignore it?
                    _dataReader.Skip();                              // Yes, skip it
                }
                else
                {                                              // Have to load schema.
                    if (_isTableLevel)
                    {                           // Loading into the DataTable ?
                        _dataTable!.ReadXSDSchema(_dataReader); // Invoke ReadXSDSchema on a table
                        _nodeToSchemaMap = new XmlToDatasetMap(_dataReader.NameTable, _dataTable);
                    }                                               // Rebuild XML to DataSet map with new schema.
                    else
                    {                                          // Loading into the DataSet ?
                        _dataSet!.ReadXSDSchema(_dataReader);   // Invoke ReadXSDSchema on a DataSet
                        _nodeToSchemaMap = new XmlToDatasetMap(_dataReader.NameTable, _dataSet);
                    }                                               // Rebuild XML to DataSet map with new schema.
                }
            }
            else if (((object)_dataReader.LocalName == _XDR_SCHEMA && (object)_dataReader.NamespaceURI == _XDRNS) ||
                    ((object)_dataReader.LocalName == _SQL_SYNC && (object)_dataReader.NamespaceURI == _UPDGNS))
            {
                _dataReader.Skip();                                  // Skip XDR or SQL sync
            }
            else
            {
                return false;                                       // No schema found. That means reader's position
                                                                    // is unchganged. Report that to the caller.
            }
 
            return true;                                            // Schema found, reader's position changed.
        }
    }
}