// // System.Xml.DTDReader // // Author: // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp) // // (C)2003 Atsushi Enomoto // (C)2004 Novell Inc. // // FIXME: // When a parameter entity contains cp section, it should be closed // within that declaration. // // Resolution to external entities from different BaseURI fails (it is // the same as MS.NET 1.1, but should be fixed in the future). // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Globalization; using System.IO; using System.Text; using Mono.Xml; #if NET_2_1 using XmlSchemaException = System.Xml.XmlException; #else using System.Xml.Schema; #endif namespace System.Xml { internal class DTDReader : IXmlLineInfo { private XmlParserInput currentInput; private Stack parserInputStack; private char [] nameBuffer; private int nameLength; private int nameCapacity; private const int initialNameCapacity = 256; private StringBuilder valueBuffer; private int currentLinkedNodeLineNumber; private int currentLinkedNodeLinePosition; // Parameter entity placeholder private int dtdIncludeSect; private bool normalization; private bool processingInternalSubset; string cachedPublicId; string cachedSystemId; DTDObjectModel DTD; #if DTD_HANDLE_EVENTS public event ValidationEventHandler ValidationEventHandler; #endif // .ctor() public DTDReader (DTDObjectModel dtd, int startLineNumber, int startLinePosition) { this.DTD = dtd; currentLinkedNodeLineNumber = startLineNumber; currentLinkedNodeLinePosition = startLinePosition; Init (); } // Properties public string BaseURI { get { return currentInput.BaseURI; } } public bool Normalization { get { return normalization; } set { normalization = value; } } public int LineNumber { get { return currentInput.LineNumber; } } public int LinePosition { get { return currentInput.LinePosition; } } public bool HasLineInfo () { return true; } // Methods private XmlException NotWFError (string message) { return new XmlException (this as IXmlLineInfo, BaseURI, message); } private void Init () { parserInputStack = new Stack (); nameBuffer = new char [initialNameCapacity]; nameLength = 0; nameCapacity = initialNameCapacity; valueBuffer = new StringBuilder (512); } internal DTDObjectModel GenerateDTDObjectModel () { // now compile DTD int originalParserDepth = parserInputStack.Count; bool more; if (DTD.InternalSubset != null && DTD.InternalSubset.Length > 0) { this.processingInternalSubset = true; XmlParserInput original = currentInput; currentInput = new XmlParserInput ( new StringReader (DTD.InternalSubset), DTD.BaseURI, currentLinkedNodeLineNumber, currentLinkedNodeLinePosition); currentInput.AllowTextDecl = false; do { more = ProcessDTDSubset (); if (PeekChar () == -1 && parserInputStack.Count > 0) PopParserInput (); } while (more || parserInputStack.Count > originalParserDepth); if (dtdIncludeSect != 0) throw NotWFError ("INCLUDE section is not ended correctly."); currentInput = original; this.processingInternalSubset = false; } if (DTD.SystemId != null && DTD.SystemId != String.Empty && DTD.Resolver != null) { PushParserInput (DTD.SystemId); do { more = ProcessDTDSubset (); if (PeekChar () == -1 && parserInputStack.Count > 1) PopParserInput (); } while (more || parserInputStack.Count > originalParserDepth + 1); if (dtdIncludeSect != 0) throw NotWFError ("INCLUDE section is not ended correctly."); PopParserInput (); } ArrayList sc = new ArrayList (); // Entity recursion check. foreach (DTDEntityDeclaration ent in DTD.EntityDecls.Values) { if (ent.NotationName != null) { ent.ScanEntityValue (sc); sc.Clear (); } } // release unnecessary memory usage DTD.ExternalResources.Clear (); return DTD; } // Read any one of following: // elementdecl, AttlistDecl, EntityDecl, NotationDecl, // PI, Comment, Parameter Entity, or doctype termination char(']') // // Returns true if it may have any more contents, or false if not. private bool ProcessDTDSubset () { SkipWhitespace (); int c2 = ReadChar (); switch(c2) { case -1: return false; case '%': // It affects on entity references' well-formedness if (this.processingInternalSubset) DTD.InternalSubsetHasPEReference = true; string peName = ReadName (); Expect (';'); DTDParameterEntityDeclaration peDecl = GetPEDecl (peName); if (peDecl == null) break; currentInput.PushPEBuffer (peDecl); // int currentLine = currentInput.LineNumber; // int currentColumn = currentInput.LinePosition; while (currentInput.HasPEBuffer) ProcessDTDSubset (); SkipWhitespace (); // FIXME: Implement correct nest-level check. // Don't depend on lineinfo (might not be supplied) // if (currentInput.LineNumber != currentLine || // currentInput.LinePosition != currentColumn) // throw NotWFError ("Incorrectly nested parameter entity."); break; case '<': int c = ReadChar (); switch(c) { case '?': // Only read, no store. ReadProcessingInstruction (); break; case '!': CompileDeclaration (); break; case -1: throw NotWFError ("Unexpected end of stream."); default: throw NotWFError ("Syntax Error after '<' character: " + (char) c); } break; case ']': if (dtdIncludeSect == 0) throw NotWFError ("Unbalanced end of INCLUDE/IGNORE section."); // End of inclusion Expect ("]>"); dtdIncludeSect--; SkipWhitespace (); break; default: throw NotWFError (String.Format ("Syntax Error inside doctypedecl markup : {0}({1})", c2, (char) c2)); } currentInput.AllowTextDecl = false; return true; } private void CompileDeclaration () { switch(ReadChar ()) { case '-': Expect ('-'); // Only read, no store. ReadComment (); break; case 'E': switch(ReadChar ()) { case 'N': Expect ("TITY"); if (!SkipWhitespace ()) throw NotWFError ( "Whitespace is required after ' // (i.e. Can PE name be replaced by another PE?) TryExpandPERef (); if (XmlChar.IsNameChar (PeekChar ())) ReadParameterEntityDecl (); else throw NotWFError ("expected name character"); } break; } DTDEntityDeclaration ent = ReadEntityDecl (); if (DTD.EntityDecls [ent.Name] == null) DTD.EntityDecls.Add (ent.Name, ent); break; case 'L': Expect ("EMENT"); DTDElementDeclaration el = ReadElementDecl (); DTD.ElementDecls.Add (el.Name, el); break; default: throw NotWFError ("Syntax Error after ' 0) { switch (ReadChar ()) { case -1: throw NotWFError ("Unexpected IGNORE section end."); case '<': if (PeekChar () != '!') break; ReadChar (); if (PeekChar () != '[') break; ReadChar (); dtdIgnoreSect++; break; case ']': if (PeekChar () != ']') break; ReadChar (); if (PeekChar () != '>') break; ReadChar (); dtdIgnoreSect--; break; } } if (dtdIgnoreSect != 0) throw NotWFError ("IGNORE section is not ended correctly."); } // The reader is positioned on the head of the name. private DTDElementDeclaration ReadElementDecl () { DTDElementDeclaration decl = new DTDElementDeclaration (DTD); decl.IsInternalSubset = this.processingInternalSubset; if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between ''); return decl; } // read 'children'(BNF) of contentspec private void ReadContentSpec (DTDElementDeclaration decl) { TryExpandPERef (); switch(ReadChar ()) { case 'E': decl.IsEmpty = true; Expect ("MPTY"); break; case 'A': decl.IsAny = true; Expect ("NY"); break; case '(': DTDContentModel model = decl.ContentModel; SkipWhitespace (); TryExpandPERef (); if(PeekChar () == '#') { // Mixed Contents. "#PCDATA" must appear first. decl.IsMixedContent = true; model.Occurence = DTDOccurence.ZeroOrMore; model.OrderType = DTDContentOrderType.Or; Expect ("#PCDATA"); SkipWhitespace (); TryExpandPERef (); while(PeekChar () != ')') { SkipWhitespace (); if (PeekChar () == '%') { TryExpandPERef (); continue; } Expect('|'); SkipWhitespace (); TryExpandPERef (); DTDContentModel elem = new DTDContentModel (DTD, decl.Name); // elem.LineNumber = currentInput.LineNumber; // elem.LinePosition = currentInput.LinePosition; elem.ElementName = ReadName (); this.AddContentModel (model.ChildModels, elem); SkipWhitespace (); TryExpandPERef (); } Expect (')'); if (model.ChildModels.Count > 0) Expect ('*'); else if (PeekChar () == '*') Expect ('*'); } else { // Non-Mixed Contents model.ChildModels.Add (ReadCP (decl)); SkipWhitespace (); do { // copied from ReadCP() ...;-) if (PeekChar () == '%') { TryExpandPERef (); continue; } if(PeekChar ()=='|') { // CPType=Or if (model.OrderType == DTDContentOrderType.Seq) throw NotWFError ("Inconsistent choice markup in sequence cp."); model.OrderType = DTDContentOrderType.Or; ReadChar (); SkipWhitespace (); AddContentModel (model.ChildModels, ReadCP (decl)); SkipWhitespace (); } else if(PeekChar () == ',') { // CPType=Seq if (model.OrderType == DTDContentOrderType.Or) throw NotWFError ("Inconsistent sequence markup in choice cp."); model.OrderType = DTDContentOrderType.Seq; ReadChar (); SkipWhitespace (); model.ChildModels.Add (ReadCP (decl)); SkipWhitespace (); } else break; } while(true); Expect (')'); switch(PeekChar ()) { case '?': model.Occurence = DTDOccurence.Optional; ReadChar (); break; case '*': model.Occurence = DTDOccurence.ZeroOrMore; ReadChar (); break; case '+': model.Occurence = DTDOccurence.OneOrMore; ReadChar (); break; } SkipWhitespace (); } SkipWhitespace (); break; default: throw NotWFError ("ContentSpec is missing."); } } // Read 'cp' (BNF) of contentdecl (BNF) private DTDContentModel ReadCP (DTDElementDeclaration elem) { DTDContentModel model = null; TryExpandPERef (); if(PeekChar () == '(') { model = new DTDContentModel (DTD, elem.Name); ReadChar (); SkipWhitespace (); model.ChildModels.Add (ReadCP (elem)); SkipWhitespace (); do { if (PeekChar () == '%') { TryExpandPERef (); continue; } if(PeekChar ()=='|') { // CPType=Or if (model.OrderType == DTDContentOrderType.Seq) throw NotWFError ("Inconsistent choice markup in sequence cp."); model.OrderType = DTDContentOrderType.Or; ReadChar (); SkipWhitespace (); AddContentModel (model.ChildModels, ReadCP (elem)); SkipWhitespace (); } else if(PeekChar () == ',') { // CPType=Seq if (model.OrderType == DTDContentOrderType.Or) throw NotWFError ("Inconsistent sequence markup in choice cp."); model.OrderType = DTDContentOrderType.Seq; ReadChar (); SkipWhitespace (); model.ChildModels.Add (ReadCP (elem)); SkipWhitespace (); } else break; } while(true); ExpectAfterWhitespace (')'); } else { TryExpandPERef (); model = new DTDContentModel (DTD, elem.Name); model.ElementName = ReadName (); } switch(PeekChar ()) { case '?': model.Occurence = DTDOccurence.Optional; ReadChar (); break; case '*': model.Occurence = DTDOccurence.ZeroOrMore; ReadChar (); break; case '+': model.Occurence = DTDOccurence.OneOrMore; ReadChar (); break; } return model; } private void AddContentModel (DTDContentModelCollection cmc, DTDContentModel cm) { if (cm.ElementName != null) { for (int i = 0; i < cmc.Count; i++) { if (cmc [i].ElementName == cm.ElementName) { HandleError (new XmlSchemaException ("Element content must be unique inside mixed content model.", this.LineNumber, this.LinePosition, null, this.BaseURI, null)); return; } } } cmc.Add (cm); } // The reader is positioned on the first name char. private void ReadParameterEntityDecl () { DTDParameterEntityDeclaration decl = new DTDParameterEntityDeclaration (DTD); decl.BaseURI = BaseURI; decl.XmlResolver = DTD.Resolver; decl.Name = ReadName (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required after name in DTD parameter entity declaration."); if (PeekChar () == 'S' || PeekChar () == 'P') { // read publicId/systemId ReadExternalID (); decl.PublicId = cachedPublicId; decl.SystemId = cachedSystemId; SkipWhitespace (); decl.Resolve (); ResolveExternalEntityReplacementText (decl); } else { TryExpandPERef (); int quoteChar = ReadChar (); if (quoteChar != '\'' && quoteChar != '"') throw NotWFError ("quotation char was expected."); ClearValueBuffer (); bool loop = true; while (loop) { int c = ReadChar (); switch (c) { case -1: throw NotWFError ("unexpected end of stream in entity value definition."); case '"': if (quoteChar == '"') loop = false; else AppendValueChar ('"'); break; case '\'': if (quoteChar == '\'') loop = false; else AppendValueChar ('\''); break; default: if (XmlChar.IsInvalid (c)) throw NotWFError ("Invalid character was used to define parameter entity."); AppendValueChar (c); break; } } decl.LiteralEntityValue = CreateValueString (); ClearValueBuffer (); ResolveInternalEntityReplacementText (decl); } ExpectAfterWhitespace ('>'); if (DTD.PEDecls [decl.Name] == null) { DTD.PEDecls.Add (decl.Name, decl); } } private void ResolveExternalEntityReplacementText (DTDEntityBase decl) { if (decl.SystemId != null && decl.SystemId.Length > 0) { // FIXME: not always it should be read in Element context XmlTextReader xtr = new XmlTextReader (decl.LiteralEntityValue, XmlNodeType.Element, null); xtr.SkipTextDeclaration (); if (decl is DTDEntityDeclaration && DTD.EntityDecls [decl.Name] == null) { // GE - also checked as valid contents StringBuilder sb = new StringBuilder (); xtr.Normalization = this.Normalization; xtr.Read (); while (!xtr.EOF) sb.Append (xtr.ReadOuterXml ()); decl.ReplacementText = sb.ToString (); } else // PE decl.ReplacementText = xtr.GetRemainder ().ReadToEnd (); } else decl.ReplacementText = decl.LiteralEntityValue; } private void ResolveInternalEntityReplacementText (DTDEntityBase decl) { string value = decl.LiteralEntityValue; int len = value.Length; ClearValueBuffer (); for (int i = 0; i < len; i++) { int ch = value [i]; int end = 0; string name; switch (ch) { case '&': i++; end = value.IndexOf (';', i); if (end < i + 1) throw new XmlException (decl, decl.BaseURI, "Invalid reference markup."); // expand charref if (value [i] == '#') { i++; ch = GetCharacterReference (decl, value, ref i, end); if (XmlChar.IsInvalid (ch)) throw NotWFError ("Invalid character was used to define parameter entity."); } else { name = value.Substring (i, end - i); if (!XmlChar.IsName (name)) throw NotWFError (String.Format ("'{0}' is not a valid entity reference name.", name)); // don't expand "general" entity. AppendValueChar ('&'); valueBuffer.Append (name); AppendValueChar (';'); i = end; break; } if (XmlChar.IsInvalid (ch)) throw new XmlException (decl, decl.BaseURI, "Invalid character was found in the entity declaration."); AppendValueChar (ch); break; case '%': i++; end = value.IndexOf (';', i); if (end < i + 1) throw new XmlException (decl, decl.BaseURI, "Invalid reference markup."); name = value.Substring (i, end - i); valueBuffer.Append (GetPEValue (name)); i = end; break; default: AppendValueChar (ch); break; } } decl.ReplacementText = CreateValueString (); ClearValueBuffer (); } private int GetCharacterReference (DTDEntityBase li, string value, ref int index, int end) { int ret = 0; if (value [index] == 'x') { try { ret = int.Parse (value.Substring (index + 1, end - index - 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture); } catch (FormatException) { throw new XmlException (li, li.BaseURI, "Invalid number for a character reference."); } } else { try { ret = int.Parse (value.Substring (index, end - index), CultureInfo.InvariantCulture); } catch (FormatException) { throw new XmlException (li, li.BaseURI, "Invalid number for a character reference."); } } index = end; return ret; } private string GetPEValue (string peName) { DTDParameterEntityDeclaration peDecl = GetPEDecl (peName); return peDecl != null ? peDecl.ReplacementText : String.Empty; } private DTDParameterEntityDeclaration GetPEDecl (string peName) { DTDParameterEntityDeclaration peDecl = DTD.PEDecls [peName] as DTDParameterEntityDeclaration; if (peDecl != null) { if (peDecl.IsInternalSubset) throw NotWFError ("Parameter entity is not allowed in internal subset entity '" + peName + "'"); return peDecl; } // See XML 1.0 section 4.1 for both WFC and VC. if ((DTD.SystemId == null && !DTD.InternalSubsetHasPEReference) || DTD.IsStandalone) throw NotWFError (String.Format ("Parameter entity '{0}' not found.",peName)); HandleError (new XmlSchemaException ( "Parameter entity " + peName + " not found.", null)); return null; } private bool TryExpandPERef () { if (PeekChar () != '%') return false; while (PeekChar () == '%') { TryExpandPERefSpaceKeep (); SkipWhitespace (); } return true; } // Tries to expand parameter entities, but it should not skip spaces private bool TryExpandPERefSpaceKeep () { if (PeekChar () == '%') { if (this.processingInternalSubset) throw NotWFError ("Parameter entity reference is not allowed inside internal subset."); ReadChar (); ExpandPERef (); return true; } else return false; } // reader is positioned after '%' private void ExpandPERef () { string peName = ReadName (); Expect (';'); DTDParameterEntityDeclaration peDecl = DTD.PEDecls [peName] as DTDParameterEntityDeclaration; if (peDecl == null) { HandleError (new XmlSchemaException ("Parameter entity " + peName + " not found.", null)); return; // do nothing } currentInput.PushPEBuffer (peDecl); } // The reader is positioned on the head of the name. private DTDEntityDeclaration ReadEntityDecl () { DTDEntityDeclaration decl = new DTDEntityDeclaration (DTD); decl.BaseURI = BaseURI; decl.XmlResolver = DTD.Resolver; decl.IsInternalSubset = this.processingInternalSubset; TryExpandPERef (); decl.Name = ReadName (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between name and content in DTD entity declaration."); TryExpandPERef (); if (PeekChar () == 'S' || PeekChar () == 'P') { // external entity ReadExternalID (); decl.PublicId = cachedPublicId; decl.SystemId = cachedSystemId; if (SkipWhitespace ()) { if (PeekChar () == 'N') { // NDataDecl Expect ("NDATA"); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required after NDATA."); decl.NotationName = ReadName (); // ndata_name } } if (decl.NotationName == null) { decl.Resolve (); ResolveExternalEntityReplacementText (decl); } else { // Unparsed entity. decl.LiteralEntityValue = String.Empty; decl.ReplacementText = String.Empty; } } else { // literal entity ReadEntityValueDecl (decl); ResolveInternalEntityReplacementText (decl); } SkipWhitespace (); // This expanding is only allowed as a non-validating parser. TryExpandPERef (); Expect ('>'); return decl; } private void ReadEntityValueDecl (DTDEntityDeclaration decl) { SkipWhitespace (); // quotation char will be finally removed on unescaping int quoteChar = ReadChar (); if (quoteChar != '\'' && quoteChar != '"') throw NotWFError ("quotation char was expected."); ClearValueBuffer (); while (PeekChar () != quoteChar) { int ch = ReadChar (); switch (ch) { case '%': string name = ReadName (); Expect (';'); if (decl.IsInternalSubset) throw NotWFError (String.Format ("Parameter entity is not allowed in internal subset entity '{0}'", name)); valueBuffer.Append (GetPEValue (name)); break; case -1: throw NotWFError ("unexpected end of stream."); default: if (this.normalization && XmlChar.IsInvalid (ch)) throw NotWFError ("Invalid character was found in the entity declaration."); AppendValueChar (ch); break; } } // string value = Dereference (CreateValueString (), false); string value = CreateValueString (); ClearValueBuffer (); Expect (quoteChar); decl.LiteralEntityValue = value; } private DTDAttListDeclaration ReadAttListDecl () { TryExpandPERefSpaceKeep (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between ATTLIST and name in DTD attlist declaration."); TryExpandPERef (); string name = ReadName (); // target element name DTDAttListDeclaration decl = DTD.AttListDecls [name] as DTDAttListDeclaration; if (decl == null) decl = new DTDAttListDeclaration (DTD); decl.IsInternalSubset = this.processingInternalSubset; decl.Name = name; if (!SkipWhitespace ()) if (PeekChar () != '>') throw NotWFError ("Whitespace is required between name and content in non-empty DTD attlist declaration."); TryExpandPERef (); while (XmlChar.IsNameChar (PeekChar ())) { DTDAttributeDefinition def = ReadAttributeDefinition (); // There must not be two or more ID attributes. if (def.Datatype.TokenizedType == XmlTokenizedType.ID) { for (int i = 0; i < decl.Definitions.Count; i++) { DTDAttributeDefinition d = decl [i]; if (d.Datatype.TokenizedType == XmlTokenizedType.ID) { HandleError (new XmlSchemaException ("AttList declaration must not contain two or more ID attributes.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); break; } } } if (decl [def.Name] == null) decl.Add (def); SkipWhitespace (); TryExpandPERef (); } SkipWhitespace (); // This expanding is only allowed as a non-validating parser. TryExpandPERef (); Expect ('>'); return decl; } private DTDAttributeDefinition ReadAttributeDefinition () { #if NET_2_1_HACK throw new NotImplementedException (); #else DTDAttributeDefinition def = new DTDAttributeDefinition (DTD); def.IsInternalSubset = this.processingInternalSubset; // attr_name TryExpandPERef (); def.Name = ReadName (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between name and content in DTD attribute definition."); // attr_value TryExpandPERef (); switch(PeekChar ()) { case 'C': // CDATA Expect ("CDATA"); def.Datatype = XmlSchemaDatatype.FromName ("normalizedString", XmlSchema.Namespace); break; case 'I': // ID, IDREF, IDREFS Expect ("ID"); if(PeekChar () == 'R') { Expect ("REF"); if(PeekChar () == 'S') { // IDREFS ReadChar (); def.Datatype = XmlSchemaDatatype.FromName ("IDREFS", XmlSchema.Namespace); } else // IDREF def.Datatype = XmlSchemaDatatype.FromName ("IDREF", XmlSchema.Namespace); } else // ID def.Datatype = XmlSchemaDatatype.FromName ("ID", XmlSchema.Namespace); break; case 'E': // ENTITY, ENTITIES Expect ("ENTIT"); switch(ReadChar ()) { case 'Y': // ENTITY def.Datatype = XmlSchemaDatatype.FromName ("ENTITY", XmlSchema.Namespace); break; case 'I': // ENTITIES Expect ("ES"); def.Datatype = XmlSchemaDatatype.FromName ("ENTITIES", XmlSchema.Namespace); break; } break; case 'N': // NMTOKEN, NMTOKENS, NOTATION ReadChar (); switch(PeekChar ()) { case 'M': Expect ("MTOKEN"); if(PeekChar ()=='S') { // NMTOKENS ReadChar (); def.Datatype = XmlSchemaDatatype.FromName ("NMTOKENS", XmlSchema.Namespace); } else // NMTOKEN def.Datatype = XmlSchemaDatatype.FromName ("NMTOKEN", XmlSchema.Namespace); break; case 'O': Expect ("OTATION"); def.Datatype = XmlSchemaDatatype.FromName ("NOTATION", XmlSchema.Namespace); TryExpandPERefSpaceKeep (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required after notation name in DTD attribute definition."); Expect ('('); SkipWhitespace (); TryExpandPERef (); def.EnumeratedNotations.Add (ReadName ()); // notation name SkipWhitespace (); TryExpandPERef (); while(PeekChar () == '|') { ReadChar (); SkipWhitespace (); TryExpandPERef (); def.EnumeratedNotations.Add (ReadName ()); // notation name SkipWhitespace (); TryExpandPERef (); } Expect (')'); break; default: throw NotWFError ("attribute declaration syntax error."); } break; default: // Enumerated Values def.Datatype = XmlSchemaDatatype.FromName ("NMTOKEN", XmlSchema.Namespace); TryExpandPERef (); Expect ('('); SkipWhitespace (); TryExpandPERef (); def.EnumeratedAttributeDeclaration.Add ( def.Datatype.Normalize (ReadNmToken ())); // enum value SkipWhitespace (); while(PeekChar () == '|') { ReadChar (); SkipWhitespace (); TryExpandPERef (); def.EnumeratedAttributeDeclaration.Add ( def.Datatype.Normalize (ReadNmToken ())); // enum value SkipWhitespace (); TryExpandPERef (); } Expect (')'); break; } TryExpandPERefSpaceKeep (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between type and occurence in DTD attribute definition."); // def_value ReadAttributeDefaultValue (def); return def; #endif } private void ReadAttributeDefaultValue (DTDAttributeDefinition def) { if(PeekChar () == '#') { ReadChar (); switch(PeekChar ()) { case 'R': Expect ("REQUIRED"); def.OccurenceType = DTDAttributeOccurenceType.Required; break; case 'I': Expect ("IMPLIED"); def.OccurenceType = DTDAttributeOccurenceType.Optional; break; case 'F': Expect ("FIXED"); def.OccurenceType = DTDAttributeOccurenceType.Fixed; if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between FIXED and actual value in DTD attribute definition."); def.UnresolvedDefaultValue = ReadDefaultAttribute (); break; } } else { // one of the enumerated value SkipWhitespace (); TryExpandPERef (); def.UnresolvedDefaultValue = ReadDefaultAttribute (); } // VC: If default value exists, it should be valid. if (def.DefaultValue != null) { string normalized = def.Datatype.Normalize (def.DefaultValue); bool breakup = false; object parsed = null; // enumeration validity if (def.EnumeratedAttributeDeclaration.Count > 0) { if (!def.EnumeratedAttributeDeclaration.Contains (normalized)) { HandleError (new XmlSchemaException ("Default value is not one of the enumerated values.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); breakup = true; } } if (def.EnumeratedNotations.Count > 0) { if (!def.EnumeratedNotations.Contains (normalized)) { HandleError (new XmlSchemaException ("Default value is not one of the enumerated notation values.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); breakup = true; } } // type based validity if (!breakup) { try { parsed = def.Datatype.ParseValue (normalized, DTD.NameTable, null); } catch (Exception ex) { // FIXME: (wishlist) bad catch ;-( HandleError (new XmlSchemaException ("Invalid default value for ENTITY type.", def.LineNumber, def.LinePosition, null, def.BaseURI, ex)); breakup = true; } } if (!breakup) { switch (def.Datatype.TokenizedType) { case XmlTokenizedType.ENTITY: if (DTD.EntityDecls [normalized] == null) HandleError (new XmlSchemaException ("Specified entity declaration used by default attribute value was not found.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); break; case XmlTokenizedType.ENTITIES: string [] entities = parsed as string []; for (int i = 0; i < entities.Length; i++) { string entity = entities [i]; if (DTD.EntityDecls [entity] == null) HandleError (new XmlSchemaException ("Specified entity declaration used by default attribute value was not found.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); } break; } } } // Extra ID attribute validity check. if (def.Datatype != null && def.Datatype.TokenizedType == XmlTokenizedType.ID) if (def.UnresolvedDefaultValue != null) HandleError (new XmlSchemaException ("ID attribute must not have fixed value constraint.", def.LineNumber, def.LinePosition, null, def.BaseURI, null)); } private DTDNotationDeclaration ReadNotationDecl() { DTDNotationDeclaration decl = new DTDNotationDeclaration (DTD); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between NOTATION and name in DTD notation declaration."); TryExpandPERef (); decl.Name = ReadName (); // notation name /* if (namespaces) { // copy from SetProperties ;-) int indexOfColon = decl.Name.IndexOf (':'); if (indexOfColon == -1) { decl.Prefix = String.Empty; decl.LocalName = decl.Name; } else { decl.Prefix = decl.Name.Substring (0, indexOfColon); decl.LocalName = decl.Name.Substring (indexOfColon + 1); } } else { */ decl.Prefix = String.Empty; decl.LocalName = decl.Name; // } SkipWhitespace (); if(PeekChar () == 'P') { decl.PublicId = ReadPubidLiteral (); bool wsSkipped = SkipWhitespace (); if (PeekChar () == '\'' || PeekChar () == '"') { if (!wsSkipped) throw NotWFError ("Whitespace is required between public id and system id."); decl.SystemId = ReadSystemLiteral (false); SkipWhitespace (); } } else if(PeekChar () == 'S') { decl.SystemId = ReadSystemLiteral (true); SkipWhitespace (); } if(decl.PublicId == null && decl.SystemId == null) throw NotWFError ("public or system declaration required for \"NOTATION\" declaration."); // This expanding is only allowed as a non-validating parser. TryExpandPERef (); Expect ('>'); return decl; } private void ReadExternalID () { switch (PeekChar ()) { case 'S': cachedSystemId = ReadSystemLiteral (true); break; case 'P': cachedPublicId = ReadPubidLiteral (); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required between PUBLIC id and SYSTEM id."); cachedSystemId = ReadSystemLiteral (false); break; } } // The reader is positioned on the first 'S' of "SYSTEM". private string ReadSystemLiteral (bool expectSYSTEM) { if(expectSYSTEM) { Expect ("SYSTEM"); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required after 'SYSTEM'."); } else SkipWhitespace (); int quoteChar = ReadChar (); // apos or quot int c = 0; ClearValueBuffer (); while (c != quoteChar) { c = ReadChar (); if (c < 0) throw NotWFError ("Unexpected end of stream in ExternalID."); if (c != quoteChar) AppendValueChar (c); } return CreateValueString (); //currentTag.ToString (startPos, currentTag.Length - 1 - startPos); } private string ReadPubidLiteral() { Expect ("PUBLIC"); if (!SkipWhitespace ()) throw NotWFError ("Whitespace is required after 'PUBLIC'."); int quoteChar = ReadChar (); int c = 0; ClearValueBuffer (); while(c != quoteChar) { c = ReadChar (); if(c < 0) throw NotWFError ("Unexpected end of stream in ExternalID."); if(c != quoteChar && !XmlChar.IsPubidChar (c)) throw NotWFError (String.Format ("character '{0}' not allowed for PUBLIC ID", (char) c)); if (c != quoteChar) AppendValueChar (c); } return CreateValueString (); //currentTag.ToString (startPos, currentTag.Length - 1 - startPos); } // The reader is positioned on the first character // of the name. internal string ReadName () { return ReadNameOrNmToken(false); } // The reader is positioned on the first character // of the name. private string ReadNmToken () { return ReadNameOrNmToken(true); } private string ReadNameOrNmToken(bool isNameToken) { int ch = PeekChar (); if(isNameToken) { if (!XmlChar.IsNameChar (ch)) throw NotWFError (String.Format ("a nmtoken did not start with a legal character {0} ({1})", ch, (char) ch)); } else { if (!XmlChar.IsFirstNameChar (ch)) throw NotWFError (String.Format ("a name did not start with a legal character {0} ({1})", ch, (char) ch)); } nameLength = 0; AppendNameChar (ReadChar ()); while (XmlChar.IsNameChar (PeekChar ())) { AppendNameChar (ReadChar ()); } return CreateNameString (); } // Read the next character and compare it against the // specified character. private void Expect (int expected) { int ch = ReadChar (); if (ch != expected) { throw NotWFError (String.Format (CultureInfo.InvariantCulture, "expected '{0}' ({1:X}) but found '{2}' ({3:X})", (char) expected, expected, (char) ch, ch)); } } private void Expect (string expected) { int len = expected.Length; for (int i=0; i< len; i++) Expect (expected [i]); } private void ExpectAfterWhitespace (char c) { while (true) { int i = ReadChar (); if (XmlChar.IsWhitespace (i)) continue; if (c != i) throw NotWFError (String.Format (CultureInfo.InvariantCulture, "Expected {0} but found {1} [{2}].", c, (char) i, i)); break; } } // Does not consume the first non-whitespace character. private bool SkipWhitespace () { bool skipped = XmlChar.IsWhitespace (PeekChar ()); while (XmlChar.IsWhitespace (PeekChar ())) ReadChar (); return skipped; } private int PeekChar () { return currentInput.PeekChar (); } private int ReadChar () { return currentInput.ReadChar (); } // The reader is positioned on the first character after // the leading '