Don't add IDREFs to set of missing IDs multiple times
[mono.git] / mcs / class / System.XML / Mono.Xml.Schema / XsdValidatingReader.cs
index 8ca43b5524a7e4a249ce5231f6297965b6827c5f..8eea3d30a2aa07cfed6f729da274dd2dc59f5487 100644 (file)
@@ -29,6 +29,9 @@
 //
 using System;
 using System.Collections;
+#if NET_2_0
+using System.Collections.Generic;
+#endif
 using System.Collections.Specialized;
 using System.IO;
 using System.Text;
@@ -55,7 +58,7 @@ using XsDatatype = System.Xml.Schema.XmlSchemaDatatype;
 
 namespace Mono.Xml.Schema
 {
-       internal class XsdValidatingReader : XmlReader, IXmlLineInfo, IHasXmlSchemaInfo, IHasXmlParserContext, IXmlNamespaceResolver
+       internal class XsdValidatingReader : XmlReader, IXmlLineInfo, IHasXmlSchemaInfo, IHasXmlParserContext
        {
                static readonly XsAttribute [] emptyAttributeArray =
                        new XsAttribute [0];
@@ -67,6 +70,7 @@ namespace Mono.Xml.Schema
                ValidationType validationType;
                XmlSchemaSet schemas = new XmlSchemaSet ();
                bool namespaces = true;
+               bool validationStarted;
 
 #region ID Constraints
                bool checkIdentity = true;
@@ -144,7 +148,7 @@ namespace Mono.Xml.Schema
                public XmlSchemaSet Schemas {
                        get { return schemas; }
                        set {
-                               if (ReadState != ReadState.Initial)
+                               if (validationStarted)
                                        throw new InvalidOperationException ("Schemas must be set before the first call to Read().");
                                schemas = value;
                        }
@@ -186,43 +190,17 @@ namespace Mono.Xml.Schema
                public ValidationType ValidationType {
                        get { return validationType; }
                        set {
-                               if (ReadState != ReadState.Initial)
+                               if (validationStarted)
                                        throw new InvalidOperationException ("ValidationType must be set before reading.");
                                validationType = value;
                        }
                }
 
-               IDictionary IXmlNamespaceResolver.GetNamespacesInScope (XmlNamespaceScope scope)
-               {
-                       IXmlNamespaceResolver resolver = reader as IXmlNamespaceResolver;
-                       if (resolver == null)
-                               throw new NotSupportedException ("The input XmlReader does not implement IXmlNamespaceResolver and thus this validating reader cannot collect in-scope namespaces.");
-                       return resolver.GetNamespacesInScope (scope);
-               }
-
-               string IXmlNamespaceResolver.LookupPrefix (string ns)
-               {
-                       return ((IXmlNamespaceResolver) this).LookupPrefix (ns, false);
-               }
-
-               string IXmlNamespaceResolver.LookupPrefix (string ns, bool atomizedNames)
-               {
-                       IXmlNamespaceResolver resolver = reader as IXmlNamespaceResolver;
-                       if (resolver == null)
-                               throw new NotSupportedException ("The input XmlReader does not implement IXmlNamespaceResolver and thus this validating reader cannot execute namespace prefix lookup.");
-                       return resolver.LookupPrefix (ns, atomizedNames);
-               }
-
                // It is used only for independent XmlReader use, not for XmlValidatingReader.
-#if NET_2_0
-               [Obsolete]
-               public override object ReadTypedValue ()
-#else
                public object ReadTypedValue ()
-#endif
                {
                        object o = XmlSchemaUtil.ReadTypedValue (this,
-                               SchemaType, ParserContext.NamespaceManager,
+                               SchemaType, NamespaceManager,
                                storedCharacters);
                        storedCharacters.Length = 0;
                        return o;
@@ -359,6 +337,10 @@ namespace Mono.Xml.Schema
                        get { return XmlSchemaUtil.GetParserContext (reader); }
                }
 
+               internal XmlNamespaceManager NamespaceManager {
+                       get { return ParserContext != null ? ParserContext.NamespaceManager : null; }
+               }
+
                public override string Prefix {
                        get {
                                if (currentDefaultAttribute < 0)
@@ -366,7 +348,7 @@ namespace Mono.Xml.Schema
                                if (defaultAttributeConsumed)
                                        return String.Empty;
                                QName qname = defaultAttributes [currentDefaultAttribute].QualifiedName;
-                               string prefix = this.ParserContext.NamespaceManager.LookupPrefix (qname.Namespace, false);
+                               string prefix = NamespaceManager != null ? NamespaceManager.LookupPrefix (qname.Namespace, false) : null;
                                if (prefix == null)
                                        return String.Empty;
                                else
@@ -486,7 +468,7 @@ namespace Mono.Xml.Schema
                        if (Context.IsInvalid)
                                HandleError ("Invalid start element: " + reader.NamespaceURI + ":" + reader.LocalName);
 
-                       Context.SetElement (state.CurrentElement);
+                       Context.PushCurrentElement (state.CurrentElement);
                }
 
                private void ValidateEndElementParticle ()
@@ -496,6 +478,7 @@ namespace Mono.Xml.Schema
                                        HandleError ("Invalid end element: " + reader.Name);
                                }
                        }
+                       Context.PopCurrentElement ();
                        state.PopContext ();
                }
 
@@ -543,6 +526,9 @@ namespace Mono.Xml.Schema
                                        dt = ct.Datatype;
                                        switch (ct.ContentType) {
                                        case XmlSchemaContentType.ElementOnly:
+                                               if (value.Length > 0 && !XmlChar.IsWhitespace (value))
+                                                       HandleError ("Character content not allowed.");
+                                               break;
                                        case XmlSchemaContentType.Empty:
                                                if (value.Length > 0)
                                                        HandleError ("Character content not allowed.");
@@ -573,6 +559,20 @@ namespace Mono.Xml.Schema
                        XsDatatype validatedDatatype = dt;
                        if (st != null) {
                                string normalized = validatedDatatype.Normalize (value);
+                               ValidateRestrictedSimpleTypeValue (st, ref validatedDatatype, normalized);
+                       }
+                       if (validatedDatatype != null) {
+                               try {
+                                       validatedDatatype.ParseValue (value, NameTable, NamespaceManager);
+                               } catch (Exception ex) {        // FIXME: (wishlist) It is bad manner ;-(
+                                       HandleError ("Invalidly typed data was specified.", ex);
+                               }
+                       }
+               }
+
+               void ValidateRestrictedSimpleTypeValue (SimpleType st, ref XsDatatype dt, string normalized)
+               {
+                       {
                                string [] values;
                                XsDatatype itemDatatype;
                                SimpleType itemSimpleType;
@@ -589,7 +589,7 @@ namespace Mono.Xml.Schema
                                                // validate against ValidatedItemType
                                                if (itemDatatype != null) {
                                                        try {
-                                                               itemDatatype.ParseValue (each, NameTable, ParserContext.NamespaceManager);
+                                                               itemDatatype.ParseValue (each, NameTable, NamespaceManager);
                                                        } catch (Exception ex) { // FIXME: (wishlist) better exception handling ;-(
                                                                HandleError ("List type value contains one or more invalid values.", ex);
                                                                break;
@@ -610,7 +610,7 @@ namespace Mono.Xml.Schema
                                                        itemSimpleType = eachType as SimpleType;
                                                        if (itemDatatype != null) {
                                                                try {
-                                                                       itemDatatype.ParseValue (each, NameTable, ParserContext.NamespaceManager);
+                                                                       itemDatatype.ParseValue (each, NameTable, NamespaceManager);
                                                                } catch (Exception) { // FIXME: (wishlist) better exception handling ;-(
                                                                        continue;
                                                                }
@@ -644,22 +644,15 @@ namespace Mono.Xml.Schema
                                                if (baseType != null) {
                                                         AssessStringValid(baseType, dt, normalized);
                                                }
-                                               if (!str.ValidateValueWithFacets (normalized, NameTable)) {
+                                               if (!str.ValidateValueWithFacets (normalized, NameTable, NamespaceManager)) {
                                                        HandleError ("Specified value was invalid against the facets.");
                                                        break;
                                                }
                                        }
-                                       validatedDatatype = st.Datatype;
+                                       dt = st.Datatype;
                                        break;
                                }
                        }
-                       if (validatedDatatype != null) {
-                               try {
-                                       validatedDatatype.ParseValue (value, NameTable, ParserContext.NamespaceManager);
-                               } catch (Exception ex) {        // FIXME: (wishlist) It is bad manner ;-(
-                                       HandleError ("Invalidly typed data was specified.", ex);
-                               }
-                       }
                }
 
                private object GetXsiType (string name)
@@ -776,7 +769,7 @@ namespace Mono.Xml.Schema
                        // [Schema Validity Assessment (Element) 1.1]
                        if (Context.Element == null) {
                                state.CurrentElement = FindElement (reader.LocalName, reader.NamespaceURI);
-                               Context.SetElement (state.CurrentElement);
+                               Context.PushCurrentElement (state.CurrentElement);
                        }
                        if (Context.Element != null) {
                                if (Context.XsiType == null) {
@@ -972,14 +965,22 @@ namespace Mono.Xml.Schema
                        if (dt != SimpleType.AnySimpleType || attr.ValidatedFixedValue != null) {
                                string normalized = dt.Normalize (reader.Value);
                                object parsedValue = null;
+
+                               // check part of 3.14.4 StringValid
+                               SimpleType st = attr.AttributeType as SimpleType;
+                               if (st != null)
+                                       ValidateRestrictedSimpleTypeValue (st, ref dt, normalized);
+
                                try {
-                                       parsedValue = dt.ParseValue (normalized, reader.NameTable, this.ParserContext.NamespaceManager);
+                                       parsedValue = dt.ParseValue (normalized, reader.NameTable, NamespaceManager);
                                } catch (Exception ex) { // FIXME: (wishlist) It is bad manner ;-(
                                        HandleError ("Attribute value is invalid against its data type " + dt.TokenizedType, ex);
                                }
-                               if (attr.ValidatedFixedValue != null && attr.ValidatedFixedValue != normalized) {
+
+                               if (attr.ValidatedFixedValue != null &&
+                                   attr.ValidatedFixedValue != normalized) {
                                        HandleError ("The value of the attribute " + attr.QualifiedName + " does not match with its fixed value.");
-                                       parsedValue = dt.ParseValue (attr.ValidatedFixedValue, reader.NameTable, this.ParserContext.NamespaceManager);
+                                       parsedValue = dt.ParseValue (attr.ValidatedFixedValue, reader.NameTable, NamespaceManager);
                                }
 #region ID Constraints
                                if (this.checkIdentity) {
@@ -1000,10 +1001,10 @@ namespace Mono.Xml.Schema
 
                private void AssessEndElementSchemaValidity ()
                {
-                       ValidateEndElementParticle ();  // validate against childrens' state.
-
                        ValidateEndSimpleContent ();
 
+                       ValidateEndElementParticle ();  // validate against childrens' state.
+
                        // 3.3.4 Assess ElementLocallyValidElement 5: value constraints.
                        // 3.3.4 Assess ElementLocallyValidType 3.1.3. = StringValid(3.14.4)
                        // => ValidateEndSimpleContent().
@@ -1108,7 +1109,7 @@ namespace Mono.Xml.Schema
                private void ProcessKeyEntry (XsdKeyEntry entry)
                {
                        bool isNil = XsiNilDepth == Depth;
-                       entry.ProcessMatch (false, elementQNameStack, this, NameTable, BaseURI, SchemaType, ParserContext.NamespaceManager, readerLineInfo, Depth, null, null, null, isNil, CurrentKeyFieldConsumers);
+                       entry.ProcessMatch (false, elementQNameStack, this, NameTable, BaseURI, SchemaType, NamespaceManager, readerLineInfo, Depth, null, null, null, isNil, CurrentKeyFieldConsumers);
                        if (MoveToFirstAttribute ()) {
                                try {
                                        do {
@@ -1117,7 +1118,16 @@ namespace Mono.Xml.Schema
                                                case XmlSchema.InstanceNamespace:
                                                        continue;
                                                }
-                                               entry.ProcessMatch (true, elementQNameStack, this, NameTable, BaseURI, SchemaType, ParserContext.NamespaceManager, readerLineInfo, Depth, LocalName, NamespaceURI, Value, false, CurrentKeyFieldConsumers);
+                                               XmlSchemaDatatype dt = SchemaType as XmlSchemaDatatype;
+                                               XmlSchemaSimpleType st = SchemaType as XmlSchemaSimpleType;
+                                               if (dt == null && st != null)
+                                                       dt = st.Datatype;
+                                               object identity = null;
+                                               if (dt != null)
+                                                       identity = dt.ParseValue (Value, NameTable, NamespaceManager);
+                                               if (identity == null)
+                                                       identity = Value;
+                                               entry.ProcessMatch (true, elementQNameStack, this, NameTable, BaseURI, SchemaType, NamespaceManager, readerLineInfo, Depth, LocalName, NamespaceURI, identity, false, CurrentKeyFieldConsumers);
                                        } while (MoveToNextAttribute ());
                                } finally {
                                        MoveToElement ();
@@ -1145,7 +1155,7 @@ namespace Mono.Xml.Schema
                                        object identity = null; // This means empty value
                                        if (dt != null) {
                                                try {
-                                                       identity = dt.ParseValue (value, NameTable, ParserContext.NamespaceManager);
+                                                       identity = dt.ParseValue (value, NameTable, NamespaceManager);
                                                } catch (Exception ex) { // FIXME: (wishlist) This is bad manner ;-(
                                                        HandleError ("Identity value is invalid against its data type " + dt.TokenizedType, ex);
                                                }
@@ -1318,15 +1328,6 @@ namespace Mono.Xml.Schema
                        return reader.LookupNamespace (prefix);
                }
 
-               string IXmlNamespaceResolver.LookupNamespace (string prefix, bool atomizedNames)
-               {
-                       IXmlNamespaceResolver res = reader as IXmlNamespaceResolver;
-                       if (res != null)
-                               return res.LookupNamespace (prefix, atomizedNames);
-                       else
-                               return reader.LookupNamespace (prefix);
-               }
-
                public override void MoveToAttribute (int i)
                {
                        switch (reader.NodeType) {
@@ -1487,7 +1488,7 @@ namespace Mono.Xml.Schema
 
                private void ExamineAdditionalSchema ()
                {
-                       if (resolver == null)
+                       if (resolver == null || ValidationType == ValidationType.None)
                                return;
                        XmlSchema schema = null;
                        string schemaLocation = reader.GetAttribute ("schemaLocation", XmlSchema.InstanceNamespace);
@@ -1498,37 +1499,46 @@ namespace Mono.Xml.Schema
                                        schemaLocation = XsDatatype.FromName ("token", XmlSchema.Namespace).Normalize (schemaLocation);
                                        tmp = schemaLocation.Split (XmlChar.WhitespaceChars);
                                } catch (Exception ex) {
-                                       HandleError ("Invalid schemaLocation attribute format.", ex, true);
+                                       if (schemas.Count == 0)
+                                               HandleError ("Invalid schemaLocation attribute format.", ex, true);
                                        tmp = new string [0];
                                }
                                if (tmp.Length % 2 != 0)
-                                       HandleError ("Invalid schemaLocation attribute format.");
-                               for (int i = 0; i < tmp.Length; i += 2) {
+                                       if (schemas.Count == 0)
+                                               HandleError ("Invalid schemaLocation attribute format.");
+                               int i=0;
+                               do {
                                        try {
-                                               schema = ReadExternalSchema (tmp [i + 1]);
-                                       } catch (Exception) { // FIXME: (wishlist) It is bad manner ;-(
-                                               HandleError ("Could not resolve schema location URI: " + tmp [i + 1], null, true);
+                                               for (; i < tmp.Length; i += 2) {
+                                                       schema = ReadExternalSchema (tmp [i + 1]);
+                                                       if (schema.TargetNamespace == null)
+                                                               schema.TargetNamespace = tmp [i];
+                                                       else if (schema.TargetNamespace != tmp [i])
+                                                               HandleError ("Specified schema has different target namespace.");
+                                                       if (schema != null) {
+                                                               if (!schemas.Contains (schema.TargetNamespace)) {
+                                                                       schemaAdded = true;
+                                                                       schemas.Add (schema);
+                                                               }
+                                                               schema = null;
+                                                       }
+                                               }
+                                       } catch (Exception) {
+                                               if (!schemas.Contains (tmp [i]))
+                                                       HandleError (String.Format ("Could not resolve schema location URI: {0}",
+                                                               i + 1 < tmp.Length ? tmp [i + 1] : String.Empty), null, true);
+                                               i += 2;
                                                continue;
                                        }
-                                       if (schema.TargetNamespace == null)
-                                               schema.TargetNamespace = tmp [i];
-                                       else if (schema.TargetNamespace != tmp [i])
-                                               HandleError ("Specified schema has different target namespace.");
-                               }
-                       }
-                       if (schema != null) {
-                               if (!schemas.Contains (schema.TargetNamespace)) {
-                                       schemaAdded = true;
-                                       schemas.Add (schema);
-                               }
+                               } while (i < tmp.Length);
                        }
-                       schema = null;
                        string noNsSchemaLocation = reader.GetAttribute ("noNamespaceSchemaLocation", XmlSchema.InstanceNamespace);
                        if (noNsSchemaLocation != null) {
                                try {
                                        schema = ReadExternalSchema (noNsSchemaLocation);
                                } catch (Exception) { // FIXME: (wishlist) It is bad manner ;-(
-                                       HandleError ("Could not resolve schema location URI: " + noNsSchemaLocation, null, true);
+                                       if (schemas.Count != 0)
+                                               HandleError ("Could not resolve schema location URI: " + noNsSchemaLocation, null, true);
                                }
                                if (schema != null && schema.TargetNamespace != null)
                                        HandleError ("Specified schema has different target namespace.");
@@ -1546,33 +1556,43 @@ namespace Mono.Xml.Schema
 
                public override bool Read ()
                {
+                       validationStarted = true;
                        currentDefaultAttribute = -1;
                        defaultAttributeConsumed = false;
                        currentAttrType = null;
-#region ID Constraints
-                       if (this.checkIdentity)
-                               idManager.OnStartElement ();
-#endregion
                        defaultAttributes = emptyAttributeArray;
 
                        bool result = reader.Read ();
-#region ID Constraints
-                       // 3.3.4 ElementLocallyValidElement 7 = Root Valid.
-                       if (!result && this.checkIdentity &&
-                               idManager.HasMissingIDReferences ())
-                               HandleError ("There are missing ID references: " + idManager.GetMissingIDString ());
-#endregion
 
                        // FIXME: schemaLocation could be specified 
                        // at any Depth.
                        if (reader.Depth == 0 &&
-                               reader.NodeType == XmlNodeType.Element)
+                               reader.NodeType == XmlNodeType.Element) {
+                               // If the reader is DTDValidatingReader (it
+                               // is the default behavior of 
+                               // XmlValidatingReader) and DTD didn't appear,
+                               // we could just use its source XmlReader.
+                               DTDValidatingReader dtdr = reader as DTDValidatingReader;
+                               if (dtdr != null && dtdr.DTD == null)
+                                       reader = dtdr.Source;
+
                                ExamineAdditionalSchema ();
+                       }
                        if (schemas.Count == 0)
                                return result;
                        if (!schemas.IsCompiled)
                                schemas.Compile ();
 
+#region ID Constraints
+                       if (this.checkIdentity)
+                               idManager.OnStartElement ();
+
+                       // 3.3.4 ElementLocallyValidElement 7 = Root Valid.
+                       if (!result && this.checkIdentity &&
+                               idManager.HasMissingIDReferences ())
+                               HandleError ("There are missing ID references: " + idManager.GetMissingIDString ());
+#endregion
+
                        switch (reader.NodeType) {
                        case XmlNodeType.Element:
 #region Key Constraints
@@ -1588,7 +1608,7 @@ namespace Mono.Xml.Schema
 
                                if (reader.IsEmptyElement)
                                        goto case XmlNodeType.EndElement;
-                               else
+                               else if (xsiNilDepth < reader.Depth)
                                        shouldValidateCharacters = true;
                                break;
                        case XmlNodeType.EndElement:
@@ -1603,14 +1623,20 @@ namespace Mono.Xml.Schema
 
                        case XmlNodeType.CDATA:
                        case XmlNodeType.SignificantWhitespace:
+                       case XmlNodeType.Whitespace:
                        case XmlNodeType.Text:
-                               // FIXME: does this check make sense?
+                               if (skipValidationDepth >= 0 && reader.Depth > skipValidationDepth)
+                                       break;
+
                                ComplexType ct = Context.ActualType as ComplexType;
-                               if (ct != null && storedCharacters.Length > 0) {
+                               if (ct != null) {
                                        switch (ct.ContentType) {
                                        case XmlSchemaContentType.ElementOnly:
+                                               if (reader.NodeType != XmlNodeType.Whitespace)
+                                                       HandleError (String.Format ("Not allowed character content is found (current content model '{0}' is element-only).", ct.QualifiedName));
+                                               break;
                                        case XmlSchemaContentType.Empty:
-                                               HandleError ("Not allowed character content was found.");
+                                               HandleError (String.Format ("Not allowed character content is found (current element content model '{0}' is empty).", ct.QualifiedName));
                                                break;
                                        }
                                }
@@ -1671,14 +1697,32 @@ namespace Mono.Xml.Schema
                {
                }
 
-               // Some of them might be missing (See the spec section 5.3, and also 3.3.4).
-               public XsElement Element;
-               public object XsiType; // xsi:type
+               object xsi_type;
+               public object XsiType { get { return xsi_type; } set { xsi_type = value; } } // xsi:type
                internal XsdValidationState State;
+               Stack element_stack = new Stack ();
+
+               // Some of them might be missing (See the spec section 5.3, and also 3.3.4).
+               public XsElement Element {
+                       get { return element_stack.Count > 0 ? element_stack.Peek () as XsElement : null; }
+               }
+
+               public void PushCurrentElement (XsElement element)
+               {
+                       element_stack.Push (element);
+               }
+
+               public void PopCurrentElement ()
+               {
+                       element_stack.Pop ();
+               }
 
                // Note that it represents current element's type.
                public object ActualType {
                        get {
+                               // FIXME: actually this should also be stacked
+                               if (element_stack.Count == 0)
+                                       return null;
                                if (XsiType != null)
                                        return XsiType;
                                else
@@ -1720,11 +1764,6 @@ namespace Mono.Xml.Schema
                {
                        return State.EvaluateEndElement ();
                }
-
-               public void SetElement (XsElement element)
-               {
-                       Element = element;
-               }
        }
 
        internal class XsdIDManager
@@ -1770,14 +1809,14 @@ namespace Mono.Xml.Schema
                                        MissingIDReferences.Remove (str);
                                break;
                        case XmlTokenizedType.IDREF:
-                               if (!idList.Contains (str))
+                               if (!idList.Contains (str) && !MissingIDReferences.Contains (str))
                                        MissingIDReferences.Add (str);
                                break;
                        case XmlTokenizedType.IDREFS:
                                string [] idrefs = (string []) parsedValue;
                                for (int i = 0; i < idrefs.Length; i++) {
                                        string id = idrefs [i];
-                                       if (!idList.Contains (id))
+                                       if (!idList.Contains (id) && !MissingIDReferences.Contains (str))
                                                MissingIDReferences.Add (id);
                                }
                                break;
@@ -1785,11 +1824,6 @@ namespace Mono.Xml.Schema
                        return null;
                }
 
-               public object FindID (string name)
-               {
-                       return idList [name];
-               }
-
                public bool HasMissingIDReferences ()
                {
                        return missingIDReferences != null