// 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;
}
}
}