// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Data { using System; using System.IO; using System.Text; using System.Xml; using System.Xml.Linq; using SimpleJson; /// /// Represents file name and line number for source file /// public sealed class SourceLineNumber { /// /// Constructor for a source with no line information. /// /// File name of the source. public SourceLineNumber(string fileName) { this.FileName = fileName; } /// /// Constructor for a source with line information. /// /// File name of the source. /// Line number of the source. public SourceLineNumber(string fileName, int lineNumber) { this.FileName = fileName; this.LineNumber = lineNumber; } /// /// Gets the file name of the source. /// /// File name for the source. public string FileName { get; private set; } /// /// Gets or sets the line number of the source. /// /// Line number of the source. public int? LineNumber { get; set; } /// /// Gets or sets the parent source line number that included this source line number. /// public SourceLineNumber Parent { get; set; } /// /// Gets the file name and line information. /// /// File name and line information. public string QualifiedFileName { get { return this.LineNumber.HasValue ? String.Concat(this.FileName, "*", this.LineNumber) : this.FileName; } } internal static SourceLineNumber Deserialize(JsonObject jsonObject) { var fileName = jsonObject.GetValueOrDefault("file"); var lineNumber = jsonObject.GetValueOrDefault("line", null); var parentJson = jsonObject.GetValueOrDefault("parent"); var parent = (parentJson == null) ? null : SourceLineNumber.Deserialize(parentJson); return new SourceLineNumber(fileName) { LineNumber = lineNumber, Parent = parent }; } internal JsonObject Serialize() { var jsonObject = new JsonObject { { "file", this.FileName }, { "line", this.LineNumber } }; if (this.Parent != null) { var parentJson = this.Parent.Serialize(); jsonObject.Add("parent", parentJson); } return jsonObject; } /// /// Creates a source line number from an encoded string. /// /// Encoded string to parse. public static SourceLineNumber CreateFromEncoded(string encodedSourceLineNumbers) { string[] linesSplit = encodedSourceLineNumbers.Split('|'); SourceLineNumber first = null; SourceLineNumber parent = null; for (int i = 0; i < linesSplit.Length; ++i) { string[] filenameSplit = linesSplit[i].Split('*'); SourceLineNumber source; if (2 == filenameSplit.Length) { source = new SourceLineNumber(filenameSplit[0], Convert.ToInt32(filenameSplit[1])); } else { source = new SourceLineNumber(filenameSplit[0]); } if (null != parent) { parent.Parent = source; } parent = source; if (null == first) { first = parent; } } return first; } /// /// Creates a source line number from a URI. /// /// Uri to convert into source line number public static SourceLineNumber CreateFromUri(string uri) { if (String.IsNullOrEmpty(uri)) { return null; } // make the local path look like a normal local path var localPath = new Uri(uri).LocalPath; localPath = localPath.TrimStart(Path.AltDirectorySeparatorChar).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); return new SourceLineNumber(localPath); } /// /// Creates a source line number from an XObject. /// /// XML node to create source line number from. /// Optional line number offset into XML file not already included in the line information. public static SourceLineNumber CreateFromXObject(XObject node, int offset = 0) { string uri = node.BaseUri; IXmlLineInfo lineInfo = node as IXmlLineInfo; SourceLineNumber result = CreateFromUri(uri); if (null != result && null != lineInfo) { result.LineNumber = lineInfo.LineNumber + offset; } return result; } /// /// Get the source line information for the current element. Typically this information /// is set by the precompiler for each element that it encounters. /// /// Element to get source line information for. /// /// The source line number used to author the element being processed or /// null if the preprocessor did not process the element or the node is /// not an element. /// public static SourceLineNumber GetFromXAnnotation(XObject node) { return node.Annotation(); } /// /// Returns the SourceLineNumber and parents encoded as a string. /// public string GetEncoded() { StringBuilder sb = new StringBuilder(this.QualifiedFileName); for (SourceLineNumber source = this.Parent; null != source; source = source.Parent) { sb.Append("|"); sb.Append(source.QualifiedFileName); } return sb.ToString(); } /// /// Determines if two SourceLineNumbers are equivalent. /// /// Object to compare. /// True if SourceLineNumbers are equivalent. public override bool Equals(object obj) { SourceLineNumber other = obj as SourceLineNumber; return null != other && this.LineNumber.HasValue == other.LineNumber.HasValue && (!this.LineNumber.HasValue || this.LineNumber == other.LineNumber) && this.FileName.Equals(other.FileName, StringComparison.OrdinalIgnoreCase) && (null == this.Parent && null == other.Parent || this.Parent.Equals(other.Parent)); } /// /// Serves as a hash code for a particular type. /// /// The hash code. public override int GetHashCode() { return this.GetEncoded().GetHashCode(); } /// /// Shows a string representation of a source line number. /// /// String representation of a source line number. public override string ToString() { return this.LineNumber.HasValue && !String.IsNullOrEmpty(this.FileName) ? String.Concat(this.FileName, "(", this.LineNumber, ")") : this.FileName ?? String.Empty; } } }