File: System\Data\XDRSchema.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="XDRSchema.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.Globalization;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Data.Common;
    
    internal sealed class XDRSchema : XMLSchema {
        internal String             _schemaName;
        internal String             _schemaUri;
        internal XmlElement         _schemaRoot;
        internal DataSet            _ds;
        private  static char[] colonArray = new char[] {':'};
 
 
        internal XDRSchema(DataSet ds, bool fInline) {
            _schemaUri = String.Empty;
            _schemaName = String.Empty;
            _schemaRoot = null;
            _ds = ds;
        }
 
        internal void LoadSchema(XmlElement schemaRoot, DataSet ds) {
            if (schemaRoot == null)
                return;
 
            _schemaRoot = schemaRoot;
            _ds = ds;
            _schemaName = schemaRoot.GetAttribute(Keywords.NAME);
                
 
            _schemaUri = "";
            Debug.Assert(FEqualIdentity(schemaRoot, Keywords.XDR_SCHEMA, Keywords.XDRNS), "Illegal node");
 
            // Get Locale and CaseSensitive properties
            
            if (_schemaName == null || _schemaName.Length == 0)
                _schemaName = "NewDataSet";
 
            ds.Namespace = _schemaUri;
            
            // Walk all the top level Element tags.  
            for (XmlNode n = schemaRoot.FirstChild; n != null; n = n.NextSibling) {
                if (!(n is XmlElement))
                    continue;
 
                XmlElement child = (XmlElement) n;
 
                if (FEqualIdentity(child, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS)) {
                    HandleTable(child);
                }
            }
            
            _schemaName = XmlConvert.DecodeName(_schemaName);
            if (ds.Tables[_schemaName] == null)
                ds.DataSetName = _schemaName;
                         
        }
 
        internal XmlElement FindTypeNode(XmlElement node) {
            string      strType;
            XmlNode     vn;
            XmlNode     vnRoof;
 
            Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS) ||
                         FEqualIdentity(node, Keywords.XDR_SCHEMA, Keywords.XDRNS) ||
                         FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS) ||
                         FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS),
                         "Invalid node type " + node.LocalName);
 
            if (FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS))
                return node;
 
            strType = node.GetAttribute(Keywords.TYPE);
 
            if (FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS) || 
                FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS)) {
                if (strType == null || strType.Length == 0)
                    return null;
 
                // Find an ELEMENTTYPE or ATTRIBUTETYPE with name=strType
                vn = node.OwnerDocument.FirstChild;
                vnRoof = node.OwnerDocument;
 
                while (vn != vnRoof) {
                    if ((FEqualIdentity(vn, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS) && 
                         FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS)) ||
                        (FEqualIdentity(vn, Keywords.XDR_ATTRIBUTETYPE, Keywords.XDRNS) && 
                         FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS))) {
                        if (vn is XmlElement && ((XmlElement)vn).GetAttribute(Keywords.NAME) == strType)
                            return(XmlElement)vn;
                    }
 
                    // Move vn node
                    if (vn.FirstChild != null)
                        vn = vn.FirstChild;
                    else if (vn.NextSibling != null)
                        vn = vn.NextSibling;
                    else {
                        while (vn != vnRoof) {
                            vn = vn.ParentNode;
                            if (vn.NextSibling != null) {
                                vn = vn.NextSibling;
                                break;
                            }
                        }                            
                    }                        
                } 
 
                return null;
            }
 
            return null;
        }
 
        internal bool IsTextOnlyContent(XmlElement node) {
            Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS), 
                         "Invalid node type " + node.LocalName);
 
            string value = node.GetAttribute(Keywords.CONTENT);
            if (value == null || value.Length == 0) {
                string type = node.GetAttribute(Keywords.DT_TYPE, Keywords.DTNS);
                if (type != null && type.Length > 0)
                    return true;
 
                return false;
            }
 
            if (value == Keywords.EMPTY || value == Keywords.ELTONLY || value == Keywords.ELEMENTONLY || value == Keywords.MIXED)
                return false;
            if (value == Keywords.TEXTONLY)
                return true;
 
            throw ExceptionBuilder.InvalidAttributeValue("content", value);
        }
 
        internal bool IsXDRField(XmlElement node, XmlElement typeNode) {
            int         min = 1;
            int         max = 1;
 
            if (!IsTextOnlyContent(typeNode))
                return false;
 
            for (XmlNode n = typeNode.FirstChild; n != null; n = n.NextSibling) {
                if (FEqualIdentity(n, Keywords.XDR_ELEMENT, Keywords.XDRNS) || 
                    FEqualIdentity(n, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS))
                    return false;
            }
 
            if (FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS)) {
                GetMinMax(node, ref min, ref max);
                if (max == -1 || max > 1)
                    return false;
            }
 
            return true;
        }
 
        internal DataTable HandleTable(XmlElement node) {
            XmlElement     typeNode;
 
            Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS) ||
                         FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS), "Invalid node type");
 
            
            // Figure out if this really is a table.  If not, bail out.
            typeNode = FindTypeNode(node);
 
            string occurs = node.GetAttribute(Keywords.MINOCCURS);
 
            if (occurs != null && occurs.Length > 0) 
                if ((Convert.ToInt32(occurs, CultureInfo.InvariantCulture)>1) && (typeNode==null)){
                    return InstantiateSimpleTable(_ds, node);
                }
            
            occurs = node.GetAttribute(Keywords.MAXOCCURS);
 
            if (occurs != null && occurs.Length > 0) 
                if ((string.Compare(occurs, "1" , StringComparison.Ordinal) != 0) && (typeNode==null)){
                    return InstantiateSimpleTable(_ds, node);
                }
            
 
            if (typeNode == null)
                return null;
 
            if (IsXDRField(node, typeNode))
                    return null;
 
            return InstantiateTable(_ds, node, typeNode);
        }
 
        private sealed class NameType : IComparable {
            public String   name;
            public Type     type;
            public NameType(String n, Type t) { 
                name = n;
                type = t;
            }
            public int CompareTo(object obj) { return String.Compare(name, (string)obj, StringComparison.Ordinal); }
        };
        // XDR spec: http://www.ltg.ed.ac.uk/~ht/XMLData-Reduced.htm
        //           http://webdata/newspecs/schema/xdr_dt_schema.xml
        private static NameType[] mapNameTypeXdr = {
            new NameType("bin.base64"          , typeof(Byte[])  ), /* XDR */ 
            new NameType("bin.hex"             , typeof(Byte[])  ), /* XDR */ 
            new NameType("boolean"             , typeof(bool)    ), /* XDR */ 
            new NameType("byte"                , typeof(SByte)   ), /* XDR */
            new NameType("char"                , typeof(Char)    ), /* XDR */ 
            new NameType("date"                , typeof(DateTime)), /* XDR */ 
            new NameType("dateTime"            , typeof(DateTime)), /* XDR */ 
            new NameType("dateTime.tz"         , typeof(DateTime)), /* XDR */ 
            new NameType("entities"            , typeof(string)  ), /* XDR */ 
            new NameType("entity"              , typeof(string)  ), /* XDR */ 
            new NameType("enumeration"         , typeof(string)  ), /* XDR */ 
            new NameType("fixed.14.4"          , typeof(Decimal) ), /* XDR */ 
            new NameType("float"               , typeof(Double)  ), /* XDR */
            new NameType("i1"                  , typeof(SByte)   ), /* XDR */ 
            new NameType("i2"                  , typeof(Int16)   ), /* XDR */ 
            new NameType("i4"                  , typeof(Int32)   ), /* XDR */ 
            new NameType("i8"                  , typeof(Int64)   ), /* XDR */         
            new NameType("id"                  , typeof(string)  ), /* XDR */ 
            new NameType("idref"               , typeof(string)  ), /* XDR */ 
            new NameType("idrefs"              , typeof(string)  ), /* XDR */ 
            new NameType("int"                 , typeof(Int32)   ), /* XDR */ 
            new NameType("nmtoken"             , typeof(string)  ), /* XDR */ 
            new NameType("nmtokens"            , typeof(string)  ), /* XDR */ 
            new NameType("notation"            , typeof(string)  ), /* XDR */ 
            new NameType("number"              , typeof(Decimal) ), /* XDR */ 
            new NameType("r4"                  , typeof(Single)  ), /* XDR */ 
            new NameType("r8"                  , typeof(Double)  ), /* XDR */ 
            new NameType("string"              , typeof(string)  ), /* XDR */ 
            new NameType("time"                , typeof(DateTime)), /* XDR */ 
            new NameType("time.tz"             , typeof(DateTime)), /* XDR */ 
            new NameType("ui1"                 , typeof(Byte)    ), /* XDR */ 
            new NameType("ui2"                 , typeof(UInt16)  ), /* XDR */ 
            new NameType("ui4"                 , typeof(UInt32)  ), /* XDR */ 
            new NameType("ui8"                 , typeof(UInt64)  ), /* XDR */ 
            new NameType("uri"                 , typeof(string)  ), /* XDR */ 
            new NameType("uuid"                , typeof(Guid)    ), /* XDR */
        };
 
        private static NameType FindNameType(string name) {
#if DEBUG
            for(int i = 1; i < mapNameTypeXdr.Length; ++i) {
                Debug.Assert((mapNameTypeXdr[i-1].CompareTo(mapNameTypeXdr[i].name)) < 0, "incorrect sorting");
            }
#endif
            int index = Array.BinarySearch(mapNameTypeXdr, name);
            if (index < 0) {
#if DEBUG
                // Let's check that we realy don't have this name:
                foreach (NameType nt in mapNameTypeXdr) {
                    Debug.Assert(nt.name != name, "FindNameType('" + name + "') -- failed. Existed name not found");
                }
#endif
                throw ExceptionBuilder.UndefinedDatatype(name);
            }
            Debug.Assert(mapNameTypeXdr[index].name == name, "FindNameType('" + name + "') -- failed. Wrong name found");
            return mapNameTypeXdr[index];
        }
 
        private static NameType enumerationNameType = FindNameType("enumeration");
 
        private Type ParseDataType(string dt, string dtValues) {
            string strType = dt;
            string[] parts = dt.Split(colonArray);  // ":"
 
            if (parts.Length > 2) {
                throw ExceptionBuilder.InvalidAttributeValue("type", dt);
            }
            else if (parts.Length == 2) {
                // 
                strType = parts[1];
            }
                
            NameType nt = FindNameType(strType);
            if (nt == enumerationNameType && (dtValues == null || dtValues.Length == 0))
                throw ExceptionBuilder.MissingAttribute("type", Keywords.DT_VALUES);
            return nt.type;
        }
 
        internal string GetInstanceName(XmlElement node) {
            string  instanceName;
 
            if (FEqualIdentity(node, Keywords.XDR_ELEMENTTYPE, Keywords.XDRNS) ||
                FEqualIdentity(node, Keywords.XDR_ATTRIBUTETYPE, Keywords.XDRNS)) {
                instanceName = node.GetAttribute(Keywords.NAME);
                if (instanceName == null || instanceName.Length == 0) {
                    throw ExceptionBuilder.MissingAttribute("Element", Keywords.NAME);
                }
            }
            else {
                instanceName = node.GetAttribute(Keywords.TYPE);
                if (instanceName == null || instanceName.Length == 0)
                    throw ExceptionBuilder.MissingAttribute("Element", Keywords.TYPE);
            }
 
            return instanceName;
        }
 
        internal void HandleColumn(XmlElement node, DataTable table) {
            Debug.Assert(FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS) ||
                         FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS), "Illegal node type");
 
            string          instanceName;
            string          strName;
            Type            type;
            string          strType;
            string          strValues;
            int             minOccurs = 0;
            int             maxOccurs = 1;
            string          strDefault;
            DataColumn      column;
 
            string strUse = node.GetAttribute(Keywords.USE);
 
 
 
            // Get the name
            if (node.Attributes.Count > 0) {
                string strRef = node.GetAttribute(Keywords.REF);
                
                if (strRef != null && strRef.Length>0)
                    return; //skip ref nodes. B2 item
 
                strName = instanceName = GetInstanceName(node);
                column = table.Columns[instanceName, _schemaUri];
                if (column != null) {
                    if (column.ColumnMapping == MappingType.Attribute) {
                        if (FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS))
                            throw ExceptionBuilder.DuplicateDeclaration(strName);
                    }
                    else {
                        if (FEqualIdentity(node, Keywords.XDR_ELEMENT, Keywords.XDRNS)) {
                            throw ExceptionBuilder.DuplicateDeclaration(strName);
                        }
                    }
                    instanceName = GenUniqueColumnName(strName, table);
                }
            }
            else
            {
                strName = instanceName = "";
            }
 
            // Now get the type
            XmlElement typeNode = FindTypeNode(node);
 
            SimpleType xsdType = null;
 
            if (typeNode == null) {
                strType = node.GetAttribute(Keywords.TYPE);
                throw ExceptionBuilder.UndefinedDatatype(strType);
            }
 
            strType = typeNode.GetAttribute(Keywords.DT_TYPE, Keywords.DTNS);
            strValues = typeNode.GetAttribute(Keywords.DT_VALUES, Keywords.DTNS);
            if (strType == null || strType.Length == 0) {
                strType = "";
                type = typeof(string);
            }
            else {
                type = ParseDataType(strType, strValues);
                // HACK: temp work around special types
                if (strType == "float") {
                    strType = "";
                }
 
                if (strType == "char") {
                    strType = "";
                    xsdType = SimpleType.CreateSimpleType(StorageType.Char, type);
                }
 
 
                if (strType == "enumeration") {
                    strType = "";
                    xsdType = SimpleType.CreateEnumeratedType(strValues);
                }
                
                if (strType == "bin.base64") {
                    strType = "";
                    xsdType = SimpleType.CreateByteArrayType("base64");
                }
 
                if (strType == "bin.hex") {
                    strType = "";
                    xsdType = SimpleType.CreateByteArrayType("hex");
                }
 
            }
 
            bool isAttribute = FEqualIdentity(node, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS);
 
            GetMinMax(node, isAttribute,  ref minOccurs, ref maxOccurs);
 
            strDefault = null;
 
            // Does XDR has default?
            strDefault = node.GetAttribute(Keywords.DEFAULT);
 
 
            bool bNullable = false;
 
            column = new DataColumn(XmlConvert.DecodeName(instanceName), type, null, 
                isAttribute ? MappingType.Attribute : MappingType.Element);
                 
            SetProperties(column, node.Attributes); // xmlschema.SetProperties will skipp setting expressions
            column.XmlDataType = strType;
            column.SimpleType = xsdType;
            column.AllowDBNull = (minOccurs == 0) || bNullable;
            column.Namespace = (isAttribute) ? String.Empty : _schemaUri;
// webdata 97925
// We will skip handling expression columns in SetProperties, so we need set the expressions here
            if (node.Attributes != null) {
                for (int i = 0; i < node.Attributes.Count; i++) {
                    if (node.Attributes[i].NamespaceURI == Keywords.MSDNS) {
                        if (node.Attributes[i].LocalName == "Expression"){
                            column.Expression =  node.Attributes[i].Value;
                            break;
                        }
                    }
                }
            }
 
            String targetNamespace = node.GetAttribute(Keywords.TARGETNAMESPACE);
            if (targetNamespace != null && targetNamespace.Length > 0)
                column.Namespace = targetNamespace;
            
            table.Columns.Add(column);
            if (strDefault != null && strDefault.Length != 0)
                try {
                    column.DefaultValue = SqlConvert.ChangeTypeForXML(strDefault, type);                    
                }
                catch (System.FormatException) {
 
                    throw ExceptionBuilder.CannotConvert(strDefault, type.FullName);
                }    
        }
        
        internal void GetMinMax(XmlElement elNode, ref int minOccurs, ref int maxOccurs) {
 
            GetMinMax(elNode, false,  ref minOccurs, ref maxOccurs);
 
        }
 
        internal void GetMinMax(XmlElement elNode, bool isAttribute, ref int minOccurs, ref int maxOccurs) {
            
            string occurs = elNode.GetAttribute(Keywords.MINOCCURS);
            if (occurs != null && occurs.Length > 0) {
                try {
                    minOccurs = Int32.Parse(occurs, CultureInfo.InvariantCulture);
                }
                catch (Exception e) {
                    // 
                    if (!ADP.IsCatchableExceptionType (e)) {
                        throw;
                    }
                    throw ExceptionBuilder.AttributeValues("minOccurs", "0", "1");
                }
            }
            occurs = elNode.GetAttribute(Keywords.MAXOCCURS);
 
            if (occurs != null && occurs.Length > 0) {
                int bZeroOrMore = string.Compare(occurs, Keywords.STAR , StringComparison.Ordinal);
                if (bZeroOrMore == 0) {
                    maxOccurs = -1;
                }
                else {
                    try {
                        maxOccurs = Int32.Parse(occurs, CultureInfo.InvariantCulture);
                    }
                    catch (Exception e) {
                        // 
                        if (!ADP.IsCatchableExceptionType (e)) {
                            throw;
                        }
                        throw ExceptionBuilder.AttributeValues("maxOccurs", "1", Keywords.STAR);
                    }
                    if (maxOccurs != 1) {
                        throw ExceptionBuilder.AttributeValues("maxOccurs", "1", Keywords.STAR);
                    }
                }
            }
        
        }
 
 
        internal void HandleTypeNode(XmlElement typeNode, DataTable table, ArrayList tableChildren)
        {
            DataTable       tableChild;
            
            for (XmlNode n = typeNode.FirstChild; n != null; n = n.NextSibling) {
 
                if (!(n is XmlElement))
                    continue;
 
                if (FEqualIdentity(n, Keywords.XDR_ELEMENT, Keywords.XDRNS)) {
                    
                    
                    tableChild = HandleTable((XmlElement) n);
                    if (tableChild != null) {
                            tableChildren.Add(tableChild);
                        continue;
                    }
                }
 
                if (FEqualIdentity(n, Keywords.XDR_ATTRIBUTE, Keywords.XDRNS) ||
                    FEqualIdentity(n, Keywords.XDR_ELEMENT, Keywords.XDRNS)) {
                    HandleColumn((XmlElement) n, table);
                    continue;
                }
 
 
                
            }
 
    
        }
 
        internal DataTable InstantiateTable(DataSet dataSet, XmlElement node, XmlElement typeNode) {
            string          typeName = "";
            XmlAttributeCollection      attrs = node.Attributes;
            DataTable       table;
            int             minOccurs = 1;
            int             maxOccurs = 1;
            string          keys = null;
            ArrayList       tableChildren = new ArrayList();
            
         
 
            if (attrs.Count > 0) {
                typeName = GetInstanceName(node);
                table = dataSet.Tables.GetTable(typeName, _schemaUri);
                if (table != null) {
                      return  table;
                }
            }
 
            table = new DataTable(XmlConvert.DecodeName(typeName));
            // fxcop: new DataTable should inherit the CaseSensitive, Locale from DataSet and possibly updating during SetProperties
                
            table.Namespace = _schemaUri;            
 
            GetMinMax(node, ref minOccurs, ref maxOccurs);
            table.MinOccurs = minOccurs;
            table.MaxOccurs = maxOccurs;
 
            _ds.Tables.Add(table);
 
            HandleTypeNode(typeNode, table, tableChildren);
 
            SetProperties(table, attrs);
 
            // check to see if we fave unique constraint
 
            if (keys != null) {
                string[] list = keys.TrimEnd(null).Split(null);
                int keyLength = list.Length;
 
                DataColumn[] cols = new DataColumn[keyLength];
 
                for (int i = 0; i < keyLength; i++) {
                    DataColumn col = table.Columns[list[i], _schemaUri];
                    if (col == null)
                        throw ExceptionBuilder.ElementTypeNotFound(list[i]);
                    cols[i] = col;
                }
                table.PrimaryKey = cols;
            }
 
 
            foreach(DataTable _tableChild in tableChildren) {
                DataRelation relation = null;
 
                DataRelationCollection childRelations = table.ChildRelations;
                
                for (int j = 0; j < childRelations.Count; j++) {
                    if (!childRelations[j].Nested)
                        continue;
 
                    if (_tableChild == childRelations[j].ChildTable)
                        relation = childRelations[j];
                }
 
                if (relation!=null)
                    continue;
 
                DataColumn parentKey = table.AddUniqueKey();
                // foreign key in the child table
                DataColumn childKey = _tableChild.AddForeignKey(parentKey);
                // create relationship
                // setup relationship between parent and this table
                relation = new DataRelation(table.TableName + "_" + _tableChild.TableName, parentKey, childKey, true);
 
                relation.CheckMultipleNested = false; // disable the check for multiple nested parent
                
                relation.Nested = true;
                _tableChild.DataSet.Relations.Add(relation);
                relation.CheckMultipleNested = true; // enable the check for multiple nested parent
            }
 
            return table;
        }
 
        internal DataTable InstantiateSimpleTable(DataSet dataSet, XmlElement node) {
            string          typeName;
            XmlAttributeCollection      attrs = node.Attributes;
            DataTable       table;
            int             minOccurs = 1;
            int             maxOccurs = 1;
 
            typeName = GetInstanceName(node);
            table = dataSet.Tables.GetTable(typeName, _schemaUri);
            if (table != null) {
                throw ExceptionBuilder.DuplicateDeclaration(typeName);
            }
            String tbName = XmlConvert.DecodeName(typeName);
            table = new DataTable(tbName);
            // fxcop: new DataTable will either inherit the CaseSensitive, Locale from DataSet or be set during SetProperties
            table.Namespace = _schemaUri;
            GetMinMax(node, ref minOccurs, ref maxOccurs);
            table.MinOccurs = minOccurs;
            table.MaxOccurs = maxOccurs;
            SetProperties(table, attrs);
            table.repeatableElement = true;
            
            HandleColumn((XmlElement) node, table);
            
            table.Columns[0].ColumnName = tbName + "_Column";
            _ds.Tables.Add(table);
 
 
            return table;
        }
 
 
 
    }
}