Merge MVP features #1
29
Common/SourceLocation.cs
Normal file
29
Common/SourceLocation.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Text;
|
||||
|
||||
namespace XNeedle
|
||||
{
|
||||
/// <summary>
|
||||
/// Carries the origin information for a loaded XML document:
|
||||
/// the file path, the detected encoding, and the line number of the
|
||||
/// associated node (-1 when line information is unavailable).
|
||||
/// </summary>
|
||||
public struct SourceLocation
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public Encoding Encoding { get; set; }
|
||||
/// <summary>
|
||||
/// 1-based line number of the associated node, or -1 if unknown.
|
||||
/// </summary>
|
||||
public int LineNumber { get; set; }
|
||||
|
||||
public SourceLocation(string filePath, Encoding encoding, int lineNumber = -1)
|
||||
{
|
||||
FilePath = filePath;
|
||||
Encoding = encoding;
|
||||
LineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public SourceLocation WithLineNumber(int lineNumber) =>
|
||||
new SourceLocation(FilePath, Encoding, lineNumber);
|
||||
}
|
||||
}
|
||||
@ -57,10 +57,33 @@ namespace XNeedle.Formats
|
||||
return new XmlRoundtripWriter(xmlWriter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an XmlRoundtripWriter that writes to a StringBuilder for in-memory rendering.
|
||||
/// Style detection (compact self-closing tags, DOS line endings) is read from the source file.
|
||||
/// </summary>
|
||||
public static (XmlRoundtripWriter writer, StringBuilder buffer) CreateToString(string filePath, Encoding encoding)
|
||||
{
|
||||
var rawBytes = File.ReadAllBytes(filePath);
|
||||
var rawText = encoding.GetString(rawBytes);
|
||||
|
||||
var compact = Regex.IsMatch(rawText, @"\S/>");
|
||||
var dosLineEndings = rawText.Contains("\r\n");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var stringWriter = new StringWriter(sb);
|
||||
var filterWriter = new FilteringTextWriter(stringWriter, compact, dosLineEndings, verbatimEncodingName: null);
|
||||
var xmlWriter = new XmlTextWriter(filterWriter)
|
||||
{
|
||||
Formatting = Formatting.None
|
||||
};
|
||||
|
||||
return (new XmlRoundtripWriter(xmlWriter), sb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects encoding from BOM first, then the XML declaration, then defaults to UTF-8.
|
||||
/// </summary>
|
||||
private static Encoding DetectEncoding(byte[] raw)
|
||||
internal static Encoding DetectEncoding(byte[] raw)
|
||||
{
|
||||
if (raw.Length >= 3 && raw[0] == 0xEF && raw[1] == 0xBB && raw[2] == 0xBF)
|
||||
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
||||
|
||||
@ -6,6 +6,7 @@ namespace XNeedle.Parser.Elements
|
||||
{
|
||||
public string? XPathSelector { get; set; }
|
||||
public XElement? Node { get; set; }
|
||||
public SourceLocation SLoc { get; set; }
|
||||
|
||||
public Context Clone()
|
||||
{
|
||||
|
||||
@ -13,15 +13,13 @@ namespace XNeedle.Parser
|
||||
from rbraket in Parse.Char('>')
|
||||
select new string([.. any]);
|
||||
}
|
||||
public static Parser<string> ChecksumData(string opening, string closing)
|
||||
public static Parser<string> ChecksumData(string tagName)
|
||||
{
|
||||
var closing = "</" + tagName + ">";
|
||||
return
|
||||
from pregarbage in Parse.AnyChar.Many()
|
||||
from preamble in Parse.String(opening).Token()
|
||||
from content in Parse.AnyChar.Many()
|
||||
from epilouge in Parse.String("</" + closing + ">")
|
||||
from postgarbage in Parse.AnyChar.Many()
|
||||
select new string([.. content]) + epilouge;
|
||||
from preamble in OpeningTagAnchor(tagName)
|
||||
from content in Parse.AnyChar.Until(Parse.String(closing))
|
||||
select new string([.. content]) + closing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,9 @@ namespace XNeedle
|
||||
{
|
||||
var masterRule = (Rule)XnParser.RuleObject.Parse(File.ReadAllText(opts.XnDefinition));
|
||||
var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace);
|
||||
masterRule.Transform(doc);
|
||||
var encoding = XmlRoundtripWriter.DetectEncoding(File.ReadAllBytes(opts.Input));
|
||||
var sloc = new SourceLocation(opts.Input, encoding);
|
||||
masterRule.Transform(doc, sloc);
|
||||
using var writer = XmlRoundtripWriter.Create(opts.Input, opts.Output);
|
||||
doc.WriteTo(writer);
|
||||
Console.WriteLine($"Applied \"{opts.XnDefinition}\" to \"{opts.Input}\" and saved result to \"{opts.Output}\"");
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Sprache;
|
||||
using XNeedle.Formats;
|
||||
using XNeedle.Parser;
|
||||
using XNeedle.Parser.Elements;
|
||||
|
||||
@ -25,12 +27,24 @@ namespace XNeedle.Transformation
|
||||
public static double crc(Context ctx)
|
||||
{
|
||||
if (ctx.Node is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int crc = 0xAFFE;
|
||||
|
||||
return (double)crc;
|
||||
// Step 1: Render the element to a string, preserving source style
|
||||
var (writer, buffer) = XmlRoundtripWriter.CreateToString(ctx.SLoc.FilePath, ctx.SLoc.Encoding);
|
||||
ctx.Node.WriteTo(writer);
|
||||
writer.Flush();
|
||||
string xml = buffer.ToString();
|
||||
|
||||
// Step 2: Extract all text after the opening tag through and including the closing tag
|
||||
string tagName = ctx.Node.Name.LocalName;
|
||||
string data = XmlFragParser.ChecksumData(tagName).Parse(xml);
|
||||
|
||||
// Step 3: Digest into CRC32 using the source encoding
|
||||
var crc32 = new System.IO.Hashing.Crc32();
|
||||
crc32.Append(ctx.SLoc.Encoding.GetBytes(data));
|
||||
uint result = BitConverter.ToUInt32(crc32.GetCurrentHash());
|
||||
|
||||
return (double)result;
|
||||
}
|
||||
|
||||
public static string text(Context ctx)
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using XNeedle;
|
||||
using XNeedle.Parser.Elements;
|
||||
|
||||
namespace XNeedle.Transformation
|
||||
@ -11,10 +12,11 @@ namespace XNeedle.Transformation
|
||||
public required string XPathSelector { get; set; }
|
||||
public required IEnumerable<Part> Body;
|
||||
|
||||
public void Transform(XDocument xml)
|
||||
public void Transform(XDocument xml, SourceLocation sloc)
|
||||
{
|
||||
var context = new Context();
|
||||
context.Node = xml.Root;
|
||||
context.SLoc = sloc;
|
||||
Operate(context);
|
||||
}
|
||||
|
||||
@ -33,6 +35,8 @@ namespace XNeedle.Transformation
|
||||
{
|
||||
var loopCtx = ctx.Clone();
|
||||
loopCtx.Node = n;
|
||||
var lineInfo = (IXmlLineInfo)n;
|
||||
loopCtx.SLoc = ctx.SLoc.WithLineNumber(lineInfo.HasLineInfo() ? lineInfo.LineNumber : -1);
|
||||
foreach (var b in Body)
|
||||
{
|
||||
b.Operate(loopCtx);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user