[Xml] Fix GetAttribute to handle null namespaces properly, add unit test.
[mono.git] / mcs / class / System.XML / System.Xml / DTDObjectModel.cs
index dba05d65c1959dc7c38395aa4f11f28acc8f68a3..bbe073f6f13c6461611e3d586245fff56805aa98 100644 (file)
@@ -34,7 +34,14 @@ using System.IO;
 using System.Text;
 using System.Xml;
 using System.Xml.Schema;
+#if NET_2_0
+using System.Collections.Generic;
+#endif
+#if NET_2_1
+using XmlSchemaException = System.Xml.XmlException;
+#else
 using Mono.Xml.Schema;
+#endif
 
 #if NET_2_0
 using XmlTextReaderImpl = Mono.Xml2.XmlTextReader;
@@ -140,14 +147,31 @@ namespace Mono.Xml
                        get { return linePosition; }
                        set { linePosition = value; }
                }
-               
+
+#if !NET_2_1
+               internal XmlSchema CreateXsdSchema ()
+               {
+                       XmlSchema s = new XmlSchema ();
+                       s.SourceUri = BaseURI;
+                       s.LineNumber = LineNumber;
+                       s.LinePosition = LinePosition;
+                       foreach (DTDElementDeclaration el in ElementDecls.Values)
+                               s.Items.Add (el.CreateXsdElement ());
+                       return s;
+               }
+#endif
+
                public string ResolveEntity (string name)
                {
                        DTDEntityDeclaration decl = EntityDecls [name] 
                                as DTDEntityDeclaration;
                        if (decl == null) {
+#if NET_2_1
+                               AddError (new XmlSchemaException (String.Format ("Required entity was not found: {0}", name), null, this.LineNumber, this.LinePosition));
+#else
                                AddError (new XmlSchemaException ("Required entity was not found.",
                                        this.LineNumber, this.LinePosition, null, this.BaseURI, null));
+#endif
                                return " ";
                        }
                        else
@@ -235,7 +259,6 @@ namespace Mono.Xml
                        validationErrors.Add (ex);
                }
 
-#if NET_2_0
                internal string GenerateEntityAttributeText (string entityName)
                {
                        DTDEntityDeclaration entity = EntityDecls [entityName] as DTDEntityDeclaration;
@@ -251,16 +274,26 @@ namespace Mono.Xml
                                return null;
 
                        if (entity.SystemId != null) {
-                               Uri baseUri = entity.BaseURI == null ? null : new Uri (entity.BaseURI);
+                               Uri baseUri = entity.BaseURI == String.Empty ? null : new Uri (entity.BaseURI);
                                Stream stream = resolver.GetEntity (resolver.ResolveUri (baseUri, entity.SystemId), null, typeof (Stream)) as Stream;
                                return new XmlTextReaderImpl (stream, XmlNodeType.Element, context);
                        }
                        else
                                return new XmlTextReaderImpl (entity.EntityValue, XmlNodeType.Element, context);
                }
-#endif
        }
 
+#if NET_2_0
+       class DictionaryBase : List<KeyValuePair<string,DTDNode>>
+       {
+               public IEnumerable<DTDNode> Values {
+                       get {
+                               foreach (KeyValuePair<string,DTDNode> p in this)
+                                       yield return p.Value;
+                       }
+               }
+       }
+#endif
        internal class DTDCollectionBase : DictionaryBase
        {
                DTDObjectModel root;
@@ -274,6 +307,32 @@ namespace Mono.Xml
                        get { return root; }
                }
 
+#if NET_2_0
+               public DictionaryBase InnerHashtable {
+                       get { return this; }
+               }
+
+               protected void BaseAdd (string name, DTDNode value)
+               {
+                       base.Add (new KeyValuePair<string,DTDNode> (name, value));
+               }
+
+               public bool Contains (string key)
+               {
+                       foreach (KeyValuePair<string,DTDNode> p in this)
+                               if (p.Key == key)
+                                       return true;
+                       return false;
+               }
+
+               protected object BaseGet (string name)
+               {
+                       foreach (KeyValuePair<string,DTDNode> p in this)
+                               if (p.Key == name)
+                                       return p.Value;
+                       return null;
+               }
+#else
                public ICollection Keys {
                        get { return InnerHashtable.Keys; }
                }
@@ -281,6 +340,22 @@ namespace Mono.Xml
                public ICollection Values {
                        get { return InnerHashtable.Values; }
                }
+
+               protected void BaseAdd (string name, object value)
+               {
+                       InnerHashtable.Add (name, value);
+               }
+
+               public bool Contains (string key)
+               {
+                       return InnerHashtable.Contains (key);
+               }
+
+               protected object BaseGet (string name)
+               {
+                       return InnerHashtable [name];
+               }
+#endif
        }
 
        internal class DTDElementDeclarationCollection : DTDCollectionBase
@@ -294,19 +369,19 @@ namespace Mono.Xml
 
                public DTDElementDeclaration Get (string name)
                {
-                       return InnerHashtable [name] as DTDElementDeclaration;
+                       return BaseGet (name) as DTDElementDeclaration;
                }
 
                public void Add (string name, DTDElementDeclaration decl)
                {
-                       if (InnerHashtable.Contains (name)) {
+                       if (Contains (name)) {
                                Root.AddError (new XmlSchemaException (String.Format (
                                        "Element declaration for {0} was already added.",
                                        name), null));
                                return;
                        }
                        decl.SetRoot (Root);
-                       InnerHashtable.Add (name, decl);
+                       BaseAdd (name, decl);
                }
        }
 
@@ -315,7 +390,7 @@ namespace Mono.Xml
                public DTDAttListDeclarationCollection (DTDObjectModel root) : base (root) {}
 
                public DTDAttListDeclaration this [string name] {
-                       get { return InnerHashtable [name] as DTDAttListDeclaration; }
+                       get { return BaseGet (name) as DTDAttListDeclaration; }
                }
 
                public void Add (string name, DTDAttListDeclaration decl)
@@ -328,7 +403,7 @@ namespace Mono.Xml
                                                existing.Add (def);
                        } else {
                                decl.SetRoot (Root);
-                               InnerHashtable.Add (name, decl);
+                               BaseAdd (name, decl);
                        }
                }
        }
@@ -338,17 +413,17 @@ namespace Mono.Xml
                public DTDEntityDeclarationCollection (DTDObjectModel root) : base (root) {}
 
                public DTDEntityDeclaration this [string name] {
-                       get { return InnerHashtable [name] as DTDEntityDeclaration; }
+                       get { return BaseGet (name) as DTDEntityDeclaration; }
                }
 
                public void Add (string name, DTDEntityDeclaration decl)
                {
-                       if (InnerHashtable [name] != null)
+                       if (Contains (name))
                                throw new InvalidOperationException (String.Format (
                                        "Entity declaration for {0} was already added.",
                                        name));
                        decl.SetRoot (Root);
-                       InnerHashtable.Add (name, decl);
+                       BaseAdd (name, decl);
                }
        }
 
@@ -357,17 +432,17 @@ namespace Mono.Xml
                public DTDNotationDeclarationCollection (DTDObjectModel root) : base (root) {}
 
                public DTDNotationDeclaration this [string name] {
-                       get { return InnerHashtable [name] as DTDNotationDeclaration; }
+                       get { return BaseGet (name) as DTDNotationDeclaration; }
                }
 
                public void Add (string name, DTDNotationDeclaration decl)
                {
-                       if (InnerHashtable [name] != null)
+                       if (Contains (name))
                                throw new InvalidOperationException (String.Format (
                                        "Notation declaration for {0} was already added.",
                                        name));
                        decl.SetRoot (Root);
-                       InnerHashtable.Add (name, decl);
+                       BaseAdd (name, decl);
                }
        }
 
@@ -426,6 +501,57 @@ namespace Mono.Xml
                        return compiledAutomata;
                }
 
+#if !NET_2_1
+               internal XmlSchemaParticle CreateXsdParticle ()
+               {
+                       XmlSchemaParticle p = CreateXsdParticleCore ();
+                       if (p == null)
+                               return null;
+
+                       switch (Occurence) {
+                       case DTDOccurence.Optional:
+                               p.MinOccurs = 0;
+                               break;
+                       case DTDOccurence.OneOrMore:
+                               p.MaxOccursString = "unbounded";
+                               break;
+                       case DTDOccurence.ZeroOrMore:
+                               p.MinOccurs = 0;
+                               p.MaxOccursString = "unbounded";
+                               break;
+                       }
+                       return p;
+               }
+
+               XmlSchemaParticle CreateXsdParticleCore ()
+               {
+                       XmlSchemaParticle p = null;
+                       if (ElementName != null) {
+                               XmlSchemaElement el = new XmlSchemaElement ();
+                               SetLineInfo (el);
+                               el.RefName = new XmlQualifiedName (ElementName);
+                               return el;
+                       }
+                       else if (ChildModels.Count == 0)
+                               return null;
+                       else {
+                               XmlSchemaGroupBase gb = 
+                                       (OrderType == DTDContentOrderType.Seq) ?
+                                               (XmlSchemaGroupBase)
+                                               new XmlSchemaSequence () :
+                                               new XmlSchemaChoice ();
+                               SetLineInfo (gb);
+                               foreach (DTDContentModel cm in ChildModels.Items) {
+                                       XmlSchemaParticle c = cm.CreateXsdParticle ();
+                                       if (c != null)
+                                               gb.Items.Add (c);
+                               }
+                               p = gb;
+                       }
+                       return p;
+               }
+#endif
+
                private DTDAutomata CompileInternal ()
                {
                        if (ElementDecl.IsAny)
@@ -491,7 +617,6 @@ namespace Mono.Xml
                {
                        return l.MakeChoice (r);
                }
-
        }
 
        internal class DTDContentModelCollection
@@ -502,6 +627,10 @@ namespace Mono.Xml
                {
                }
 
+               public IList Items {
+                       get { return contentModel; }
+               }
+
                public DTDContentModel this [int i] {
                        get { return contentModel [i] as DTDContentModel; }
                }
@@ -564,6 +693,15 @@ namespace Mono.Xml
                {
                        return new XmlException (this as IXmlLineInfo, BaseURI, message);
                }
+
+#if !NET_2_1
+               public void SetLineInfo (XmlSchemaObject obj)
+               {
+                       obj.SourceUri = BaseURI;
+                       obj.LineNumber = LineNumber;
+                       obj.LinePosition = LinePosition;
+               }
+#endif
        }
 
        internal class DTDElementDeclaration : DTDNode
@@ -612,6 +750,60 @@ namespace Mono.Xml
                                return Root.AttListDecls [Name];
                        }
                }
+
+#if !NET_2_1
+               internal XmlSchemaElement CreateXsdElement ()
+               {
+                       XmlSchemaElement el = new XmlSchemaElement ();
+                       SetLineInfo (el);
+                       el.Name = Name;
+
+                       XmlSchemaComplexType ct = new XmlSchemaComplexType ();
+                       el.SchemaType = ct;
+                       if (Attributes != null) {
+                               SetLineInfo (ct);
+                               foreach (DTDAttributeDefinition a in
+                                       Attributes.Definitions)
+                                       ct.Attributes.Add (a.CreateXsdAttribute ());
+                       }
+                       if (IsEmpty)
+                               ; // nothing to do
+                       else if (IsAny) {
+                               XmlSchemaAny any = new XmlSchemaAny ();
+                               any.MinOccurs = 0;
+                               any.MaxOccursString = "unbounded";
+                               ct.Particle = any;
+                       }
+                       else {
+                               if (IsMixedContent)
+                                       ct.IsMixed = true;
+                               ct.Particle = ContentModel.CreateXsdParticle ();
+                       }
+
+                       /*
+                       if (IsEmpty) {
+                               el.SchemaType = new XmlSchemaComplexType ();
+                               SetLineInfo (el.SchemaType);
+                       }
+                       else if (IsAny)
+                               el.SchemaTypeName = new XmlQualifiedName (
+                                       "anyType", XmlSchema.Namespace);
+                       else {
+                               XmlSchemaComplexType ct = new XmlSchemaComplexType ();
+                               SetLineInfo (ct);
+                               if (Attributes != null)
+                                       foreach (DTDAttributeDefinition a in
+                                               Attributes.Definitions)
+                                               ct.Attributes.Add (a.CreateXsdAttribute ());
+                               if (IsMixedContent)
+                                       ct.IsMixed = true;
+                               ct.Particle = ContentModel.CreateXsdParticle ();
+                               el.SchemaType = ct;
+                       }
+                       */
+                       return el;
+               }
+#endif
        }
 
        internal class DTDAttributeDefinition : DTDNode
@@ -705,6 +897,69 @@ namespace Mono.Xml
                        }
                }
 
+#if !NET_2_1
+               internal XmlSchemaAttribute CreateXsdAttribute ()
+               {
+                       XmlSchemaAttribute a = new XmlSchemaAttribute ();
+                       SetLineInfo (a);
+                       a.Name = Name;
+                       a.DefaultValue = resolvedNormalizedDefaultValue;
+                       if (OccurenceType != DTDAttributeOccurenceType.Required)
+                               a.Use = XmlSchemaUse.Optional;
+
+                       XmlQualifiedName qname = XmlQualifiedName.Empty;
+                       ArrayList enumeration = null;
+                       if (enumeratedNotations != null && enumeratedNotations.Count > 0) {
+                               qname = new XmlQualifiedName ("NOTATION", XmlSchema.Namespace);
+                               enumeration = enumeratedNotations;
+                       }
+                       else if (enumeratedLiterals != null)
+                               enumeration = enumeratedLiterals;
+                       else {
+                               switch (Datatype.TokenizedType) {
+                               case XmlTokenizedType.ID:
+                                       qname = new XmlQualifiedName ("ID", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.IDREF:
+                                       qname = new XmlQualifiedName ("IDREF", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.IDREFS:
+                                       qname = new XmlQualifiedName ("IDREFS", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.ENTITY:
+                                       qname = new XmlQualifiedName ("ENTITY", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.ENTITIES:
+                                       qname = new XmlQualifiedName ("ENTITIES", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.NMTOKEN:
+                                       qname = new XmlQualifiedName ("NMTOKEN", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.NMTOKENS:
+                                       qname = new XmlQualifiedName ("NMTOKENS", XmlSchema.Namespace); break;
+                               case XmlTokenizedType.NOTATION:
+                                       qname = new XmlQualifiedName ("NOTATION", XmlSchema.Namespace); break;
+                               }
+                       }
+
+                       if (enumeration != null) {
+                               XmlSchemaSimpleType st = new XmlSchemaSimpleType ();
+                               SetLineInfo (st);
+                               XmlSchemaSimpleTypeRestriction r =
+                                       new XmlSchemaSimpleTypeRestriction ();
+                               SetLineInfo (r);
+                               r.BaseTypeName = qname;
+                               if (enumeratedNotations != null) {
+                                       foreach (string name in enumeratedNotations) {
+                                               XmlSchemaEnumerationFacet f =
+                                                       new XmlSchemaEnumerationFacet ();
+                                               SetLineInfo (f);
+                                               r.Facets.Add (f);
+                                               f.Value = name;
+                                       }
+                               }
+                               st.Content = r;
+                       }
+                       else if (qname != XmlQualifiedName.Empty)
+                               a.SchemaTypeName = qname;
+                       return a;
+               }
+#endif
+
                internal string ComputeDefaultValue ()
                {
                        if (UnresolvedDefaultValue == null)
@@ -813,9 +1068,12 @@ namespace Mono.Xml
                string systemId;
                string literalValue;
                string replacementText;
+               string uriString;
+               Uri absUri;
                bool isInvalid;
-               Exception loadException;
+//             Exception loadException;
                bool loadFailed;
+               XmlResolver resolver;
 
                protected DTDEntityBase (DTDObjectModel root)
                {
@@ -857,42 +1115,57 @@ namespace Mono.Xml
                        set { replacementText = value; }
                }
 
-               public void Resolve (XmlResolver resolver)
+               public XmlResolver XmlResolver {
+                       set { resolver = value; }
+               }
+
+               public string ActualUri {
+                       get {
+                               if (uriString == null) {
+                                       if (resolver == null || SystemId == null || SystemId.Length == 0)
+                                               uriString = BaseURI;
+                                       else {
+                                               Uri baseUri = null;
+                                               try {
+                                                       if (BaseURI != null && BaseURI.Length > 0)
+                                                               baseUri = new Uri (BaseURI);
+                                               } catch (UriFormatException) {
+                                               }
+
+                                               absUri = resolver.ResolveUri (baseUri, SystemId);
+                                               uriString = absUri != null ? absUri.ToString () : String.Empty;
+                                       }
+                               }
+                               return uriString;
+                       }
+               }
+
+               public void Resolve ()
                {
-                       if (resolver == null || SystemId == null || SystemId.Length == 0) {
+                       if (ActualUri == String.Empty) {
                                LoadFailed = true;
                                LiteralEntityValue = String.Empty;
                                return;
                        }
 
-                       Uri baseUri = null;
-                       try {
-                               if (BaseURI != null && BaseURI.Length > 0)
-                                       baseUri = new Uri (BaseURI);
-                       } catch (UriFormatException) {
-                       }
-
-                       Uri absUri = resolver.ResolveUri (baseUri, SystemId);
-                       string absPath = absUri.ToString ();
-                       if (Root.ExternalResources.ContainsKey (absPath))
-                               LiteralEntityValue = (string) Root.ExternalResources [absPath];
+                       if (Root.ExternalResources.ContainsKey (ActualUri))
+                               LiteralEntityValue = (string) Root.ExternalResources [ActualUri];
                        Stream s = null;
                        try {
                                s = resolver.GetEntity (absUri, null, typeof (Stream)) as Stream;
-                               XmlTextReaderImpl xtr = new XmlTextReaderImpl (absPath, s, Root.NameTable);
+                               XmlTextReaderImpl xtr = new XmlTextReaderImpl (ActualUri, s, Root.NameTable);
                                // Don't skip Text declaration here. LiteralEntityValue contains it. See spec 4.5
-                               this.BaseURI = absPath;
                                LiteralEntityValue = xtr.GetRemainder ().ReadToEnd ();
 
-                               Root.ExternalResources.Add (absPath, LiteralEntityValue);
+                               Root.ExternalResources.Add (ActualUri, LiteralEntityValue);
                                if (Root.ExternalResources.Count > DTDObjectModel.AllowedExternalEntitiesMax)
                                        throw new InvalidOperationException ("The total amount of external entities exceeded the allowed number.");
 
-                       } catch (Exception ex) {
-                               loadException = ex;
+                       } catch (Exception) {
+//                             loadException = ex;
                                LiteralEntityValue = String.Empty;
                                LoadFailed = true;
-//                             throw NotWFError ("Cannot resolve external entity. URI is " + absPath + " .");
+//                             throw NotWFError ("Cannot resolve external entity. URI is " + ActualUri + " .");
                        } finally {
                                if (s != null)
                                        s.Close ();
@@ -1013,8 +1286,12 @@ namespace Mono.Xml
                                }
                        }
                        if (start != 0)
+#if NET_2_1
+                               Root.AddError (new XmlSchemaException (this, this.BaseURI, "Invalid reference character '&' is specified."));
+#else
                                Root.AddError (new XmlSchemaException ("Invalid reference character '&' is specified.",
                                        this.LineNumber, this.LinePosition, null, this.BaseURI, null));
+#endif
                        scanned = true;
                        recursed = false;
                }