/// <summary> /// Context: name or "=\'\"" /// </summary> void ReadAttribute() { AssertHasMoreData(); InternalAttribute attr = new InternalAttribute(); var frame = BeginInternalObject(attr); // Read name string name; if (TryReadName(out name)) { if (!IsValidName(name)) { OnSyntaxError(this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name); } } else { OnSyntaxError("Attribute name expected"); } attr.Name = name; // Read equals sign and surrounding whitespace int checkpoint = this.CurrentLocation; TryMoveToNonWhiteSpace(); if (TryRead('=')) { int chk2 = this.CurrentLocation; TryMoveToNonWhiteSpace(); if (!TryPeek('"') && !TryPeek('\'')) { // Do not read whitespace if quote does not follow GoBack(chk2); } attr.EqualsSignLength = this.CurrentLocation - checkpoint; } else { GoBack(checkpoint); OnSyntaxError("'=' expected"); attr.EqualsSignLength = 0; } // Read attribute value int start = this.CurrentLocation; char quoteChar = TryPeek('"') ? '"' : '\''; bool startsWithQuote; if (TryRead(quoteChar)) { startsWithQuote = true; int valueStart = this.CurrentLocation; TryMoveToAnyOf(quoteChar, '<'); if (TryRead(quoteChar)) { if (!TryPeekAnyOf(' ', '\t', '\n', '\r', '/', '>', '?')) { if (TryPeekPrevious('=', 2) || (TryPeekPrevious('=', 3) && TryPeekPrevious(' ', 2))) { // This actually most likely means that we are in the next attribute value GoBack(valueStart); ReadAttributeValue(quoteChar); if (TryRead(quoteChar)) { OnSyntaxError("White space or end of tag expected"); } else { OnSyntaxError("Quote {0} expected (or add whitespace after the following one)", quoteChar); } } else { OnSyntaxError("White space or end of tag expected"); } } } else { // '<' or end of file GoBack(valueStart); ReadAttributeValue(quoteChar); OnSyntaxError("Quote {0} expected", quoteChar); } } else { startsWithQuote = false; int valueStart = this.CurrentLocation; ReadAttributeValue(null); TryRead('\"'); TryRead('\''); if (valueStart == this.CurrentLocation) { OnSyntaxError("Attribute value expected"); } else { OnSyntaxError(valueStart, this.CurrentLocation, "Attribute value must be quoted"); } } string val = GetText(start, this.CurrentLocation); val = Unquote(val); attr.Value = Dereference(val, startsWithQuote ? start + 1 : start); EndInternalObject(frame); }
/// <summary> /// Context: "<" /// </summary> void ReadTag() { AssertHasMoreData(); int tagStart = this.CurrentLocation; InternalTag tag = new InternalTag(); var frame = BeginInternalObject(tag); // Read the opening bracket // It identifies the type of tag and parsing behavior for the rest of it tag.OpeningBracket = ReadOpeningBracket(); if (tag.IsUnknownBang && !TryPeekWhiteSpace()) { OnSyntaxError(tagStart, this.CurrentLocation, "Unknown tag"); } if (tag.IsStartOrEmptyTag || tag.IsEndTag || tag.IsProcessingInstruction) { // Read the name TryMoveToNonWhiteSpace(); tag.RelativeNameStart = this.CurrentRelativeLocation; string name; if (TryReadName(out name)) { if (!IsValidName(name)) { OnSyntaxError(this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name); } } else { OnSyntaxError("Element name expected"); } tag.Name = name; } else { tag.Name = string.Empty; } bool isXmlDeclr = tag.Name == "xml" && tag.IsProcessingInstruction; int oldObjectCount = objects.Count; if (tag.IsStartOrEmptyTag || tag.IsEndTag || isXmlDeclr) { // Read attributes for the tag while (HasMoreData()) { // Chech for all forbiden 'name' characters first - see ReadName TryMoveToNonWhiteSpace(); if (TryPeek('<')) { break; } string endBr; int endBrStart = this.CurrentLocation; // Just peek if (TryReadClosingBracket(out endBr)) // End tag { GoBack(endBrStart); break; } // We have "=\'\"" or name - read attribute int attrStartOffset = this.CurrentLocation; ReadAttribute(); if (tag.IsEndTag) { OnSyntaxError(attrStartOffset, this.CurrentLocation, "Attribute not allowed in end tag."); } } } else if (tag.IsDocumentType) { ReadContentOfDTD(); } else { int start = this.CurrentLocation; if (tag.IsComment) { ReadText(TextType.Comment); } else if (tag.IsCData) { ReadText(TextType.CData); } else if (tag.IsProcessingInstruction) { ReadText(TextType.ProcessingInstruction); } else if (tag.IsUnknownBang) { ReadText(TextType.UnknownBang); } else { throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket)); } // Backtrack at complete start if (IsEndOfFile() || (tag.IsUnknownBang && TryPeek('<'))) { GoBack(start); objects.RemoveRange(oldObjectCount, objects.Count - oldObjectCount); } } // Read closing bracket string bracket; TryReadClosingBracket(out bracket); tag.ClosingBracket = bracket; // Error check int brStart = this.CurrentLocation - (tag.ClosingBracket ?? string.Empty).Length; int brEnd = this.CurrentLocation; if (tag.Name == null) { // One error was reported already } else if (tag.IsStartOrEmptyTag) { if (tag.ClosingBracket != ">" && tag.ClosingBracket != "/>") { OnSyntaxError(brStart, brEnd, "'>' or '/>' expected"); } } else if (tag.IsEndTag) { if (tag.ClosingBracket != ">") { OnSyntaxError(brStart, brEnd, "'>' expected"); } } else if (tag.IsComment) { if (tag.ClosingBracket != "-->") { OnSyntaxError(brStart, brEnd, "'-->' expected"); } } else if (tag.IsCData) { if (tag.ClosingBracket != "]]>") { OnSyntaxError(brStart, brEnd, "']]>' expected"); } } else if (tag.IsProcessingInstruction) { if (tag.ClosingBracket != "?>") { OnSyntaxError(brStart, brEnd, "'?>' expected"); } } else if (tag.IsUnknownBang) { if (tag.ClosingBracket != ">") { OnSyntaxError(brStart, brEnd, "'>' expected"); } } else if (tag.IsDocumentType) { if (tag.ClosingBracket != ">") { OnSyntaxError(brStart, brEnd, "'>' expected"); } } else { throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket)); } // Attribute name may not apper multiple times if (objects.Count > oldObjectCount) { // Move nested objects into tag.NestedObjects: tag.NestedObjects = new InternalObject[objects.Count - oldObjectCount]; objects.CopyTo(oldObjectCount, tag.NestedObjects, 0, tag.NestedObjects.Length); objects.RemoveRange(oldObjectCount, objects.Count - oldObjectCount); // Look for duplicate attributes: HashSet <string> attributeNames = new HashSet <string>(); foreach (var obj in tag.NestedObjects) { InternalAttribute attr = obj as InternalAttribute; if (attr != null && !attributeNames.Add(attr.Name)) { int attrStart = tagStart + attr.StartRelativeToParent; OnSyntaxError(attrStart, attrStart + attr.Name.Length, "Attribute with name '{0}' already exists", attr.Name); } } } EndInternalObject(frame); }
internal AXmlAttribute(AXmlObject parent, int startOffset, InternalAttribute internalObject) : base(parent, startOffset, internalObject) { }