// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.Activities.Debugger { using System.Collections.Generic; using System.IO; using System.Xml; internal class XmlReaderWithSourceLocation : XmlWrappingReader { private Dictionary attributeValueRanges; private Dictionary emptyElementRanges; private Dictionary contentValueRanges; private Dictionary startElementLocations; private Dictionary endElementLocations; private CharacterSpottingTextReader characterSpottingTextReader; private Stack contentStartLocationStack; public XmlReaderWithSourceLocation(TextReader underlyingTextReader) { UnitTestUtility.Assert(underlyingTextReader != null, "CharacterSpottingTextReader cannot be null and should be ensured by caller."); CharacterSpottingTextReader characterSpottingTextReader = new CharacterSpottingTextReader(underlyingTextReader); this.BaseReader = XmlReader.Create(characterSpottingTextReader); UnitTestUtility.Assert(this.BaseReaderAsLineInfo != null, "The XmlReader created by XmlReader.Create should ensure this."); UnitTestUtility.Assert(this.BaseReaderAsLineInfo.HasLineInfo(), "The XmlReader created by XmlReader.Create should ensure this."); this.characterSpottingTextReader = characterSpottingTextReader; this.contentStartLocationStack = new Stack(); } public Dictionary AttributeValueRanges { get { if (this.attributeValueRanges == null) { this.attributeValueRanges = new Dictionary(); } return this.attributeValueRanges; } } public Dictionary ContentValueRanges { get { if (this.contentValueRanges == null) { this.contentValueRanges = new Dictionary(); } return this.contentValueRanges; } } public Dictionary EmptyElementRanges { get { if (this.emptyElementRanges == null) { this.emptyElementRanges = new Dictionary(); } return this.emptyElementRanges; } } public Dictionary StartElementLocations { get { if (this.startElementLocations == null) { this.startElementLocations = new Dictionary(); } return this.startElementLocations; } } public Dictionary EndElementLocations { get { if (this.endElementLocations == null) { this.endElementLocations = new Dictionary(); } return this.endElementLocations; } } private DocumentLocation CurrentLocation { get { return new DocumentLocation(this.BaseReaderAsLineInfo.LineNumber, this.BaseReaderAsLineInfo.LinePosition); } } public override bool Read() { bool result = base.Read(); if (this.NodeType == Xml.XmlNodeType.Element) { DocumentLocation elementLocation = this.CurrentLocation; if (this.IsEmptyElement) { DocumentRange emptyElementRange = this.FindEmptyElementRange(elementLocation); this.EmptyElementRanges.Add(elementLocation, emptyElementRange); } else { DocumentLocation startElementBracket = this.FindStartElementBracket(elementLocation); this.StartElementLocations.Add(elementLocation, startElementBracket); // Push a null as a place holder. In XmlNodeType.Text part, we replace this // null with real data. Why not pushing real data only without this place holder? // Because in XmlNodeType.EndElement, we need to know whether there is Text. Think // about situation like Text1Text2Text3 // So, each time an Element starts, we push a place holder in the stack so that Start // and End don't mis-match. this.contentStartLocationStack.Push(null); } int attributeCount = this.AttributeCount; if (attributeCount > 0) { for (int i = 0; i < attributeCount; i++) { this.MoveToAttribute(i); DocumentLocation memberLocation = this.CurrentLocation; DocumentRange attributeValueRange = this.FindAttributeValueLocation(memberLocation); this.AttributeValueRanges.Add(memberLocation, attributeValueRange); } this.MoveToElement(); } } else if (this.NodeType == Xml.XmlNodeType.EndElement) { DocumentLocation endElementLocation = this.CurrentLocation; DocumentLocation endElementBracket = this.FindEndElementBracket(endElementLocation); this.EndElementLocations.Add(endElementLocation, endElementBracket); UnitTestUtility.Assert( this.contentStartLocationStack.Count > 0, "The stack should contain at least a null we pushed in StartElement."); DocumentLocation contentStartLocation = this.contentStartLocationStack.Pop(); if (contentStartLocation != null) { DocumentLocation contentEnd = this.FindContentEndBefore(endElementLocation); this.ContentValueRanges.Add(endElementLocation, new DocumentRange(contentStartLocation, contentEnd)); } } else if (this.NodeType == Xml.XmlNodeType.Text) { UnitTestUtility.Assert(this.contentStartLocationStack.Count > 0, "Adding Text with out StartElement?"); if (this.contentStartLocationStack.Peek() == null) { // no text was added since the last StartElement. // This is the start of the content of this Element. // ABCDE // Sometimes, xml reader gives the text by ABC and DE in // two times. this.contentStartLocationStack.Pop(); this.contentStartLocationStack.Push(this.CurrentLocation); } } return result; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { if (this.characterSpottingTextReader != null) { ((IDisposable)this.characterSpottingTextReader).Dispose(); } this.characterSpottingTextReader = null; } } private DocumentLocation FindStartElementBracket(DocumentLocation elementLocation) { return this.characterSpottingTextReader.FindCharacterStrictlyBefore('<', elementLocation); } private DocumentLocation FindEndElementBracket(DocumentLocation elementLocation) { return this.characterSpottingTextReader.FindCharacterStrictlyAfter('>', elementLocation); } private DocumentRange FindEmptyElementRange(DocumentLocation elementLocation) { DocumentLocation startBracket = this.FindStartElementBracket(elementLocation); DocumentLocation endBracket = this.FindEndElementBracket(elementLocation); UnitTestUtility.Assert(startBracket != null, "XmlReader should guarantee there must be a start angle bracket."); UnitTestUtility.Assert(endBracket != null, "XmlReader should guarantee there must be an end angle bracket."); DocumentRange emptyElementRange = new DocumentRange(startBracket, endBracket); return emptyElementRange; } private DocumentRange FindAttributeValueLocation(DocumentLocation memberLocation) { UnitTestUtility.Assert(this.characterSpottingTextReader != null, "Ensured by constructor."); DocumentLocation attributeStart = this.characterSpottingTextReader.FindCharacterStrictlyAfter(this.QuoteChar, memberLocation); UnitTestUtility.Assert(attributeStart != null, "Read should ensure the two quote characters exists"); DocumentLocation attributeEnd = this.characterSpottingTextReader.FindCharacterStrictlyAfter(this.QuoteChar, attributeStart); UnitTestUtility.Assert(attributeEnd != null, "Read should ensure the two quote characters exists"); return new DocumentRange(attributeStart, attributeEnd); } private DocumentLocation FindContentEndBefore(DocumentLocation location) { DocumentLocation contentEnd = this.FindStartElementBracket(location); int linePosition = contentEnd.LinePosition.Value - 1; // Line position is 1-based if (linePosition < 1) { return this.characterSpottingTextReader.FindCharacterStrictlyBefore('\n', contentEnd); } else { return new DocumentLocation(contentEnd.LineNumber.Value, linePosition); } } } }