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);
|
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>
|
/// <summary>
|
||||||
/// Detects encoding from BOM first, then the XML declaration, then defaults to UTF-8.
|
/// Detects encoding from BOM first, then the XML declaration, then defaults to UTF-8.
|
||||||
/// </summary>
|
/// </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)
|
if (raw.Length >= 3 && raw[0] == 0xEF && raw[1] == 0xBB && raw[2] == 0xBF)
|
||||||
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace XNeedle.Parser.Elements
|
|||||||
{
|
{
|
||||||
public string? XPathSelector { get; set; }
|
public string? XPathSelector { get; set; }
|
||||||
public XElement? Node { get; set; }
|
public XElement? Node { get; set; }
|
||||||
|
public SourceLocation SLoc { get; set; }
|
||||||
|
|
||||||
public Context Clone()
|
public Context Clone()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -13,15 +13,13 @@ namespace XNeedle.Parser
|
|||||||
from rbraket in Parse.Char('>')
|
from rbraket in Parse.Char('>')
|
||||||
select new string([.. any]);
|
select new string([.. any]);
|
||||||
}
|
}
|
||||||
public static Parser<string> ChecksumData(string opening, string closing)
|
public static Parser<string> ChecksumData(string tagName)
|
||||||
{
|
{
|
||||||
|
var closing = "</" + tagName + ">";
|
||||||
return
|
return
|
||||||
from pregarbage in Parse.AnyChar.Many()
|
from preamble in OpeningTagAnchor(tagName)
|
||||||
from preamble in Parse.String(opening).Token()
|
from content in Parse.AnyChar.Until(Parse.String(closing))
|
||||||
from content in Parse.AnyChar.Many()
|
select new string([.. content]) + closing;
|
||||||
from epilouge in Parse.String("</" + closing + ">")
|
|
||||||
from postgarbage in Parse.AnyChar.Many()
|
|
||||||
select new string([.. content]) + epilouge;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +37,9 @@ namespace XNeedle
|
|||||||
{
|
{
|
||||||
var masterRule = (Rule)XnParser.RuleObject.Parse(File.ReadAllText(opts.XnDefinition));
|
var masterRule = (Rule)XnParser.RuleObject.Parse(File.ReadAllText(opts.XnDefinition));
|
||||||
var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace);
|
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);
|
using var writer = XmlRoundtripWriter.Create(opts.Input, opts.Output);
|
||||||
doc.WriteTo(writer);
|
doc.WriteTo(writer);
|
||||||
Console.WriteLine($"Applied \"{opts.XnDefinition}\" to \"{opts.Input}\" and saved result to \"{opts.Output}\"");
|
Console.WriteLine($"Applied \"{opts.XnDefinition}\" to \"{opts.Input}\" and saved result to \"{opts.Output}\"");
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Sprache;
|
using Sprache;
|
||||||
|
using XNeedle.Formats;
|
||||||
using XNeedle.Parser;
|
using XNeedle.Parser;
|
||||||
using XNeedle.Parser.Elements;
|
using XNeedle.Parser.Elements;
|
||||||
|
|
||||||
@ -25,12 +27,24 @@ namespace XNeedle.Transformation
|
|||||||
public static double crc(Context ctx)
|
public static double crc(Context ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Node is null)
|
if (ctx.Node is null)
|
||||||
{
|
|
||||||
return 0;
|
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)
|
public static string text(Context ctx)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Xml.XPath;
|
using System.Xml.XPath;
|
||||||
|
using XNeedle;
|
||||||
using XNeedle.Parser.Elements;
|
using XNeedle.Parser.Elements;
|
||||||
|
|
||||||
namespace XNeedle.Transformation
|
namespace XNeedle.Transformation
|
||||||
@ -11,10 +12,11 @@ namespace XNeedle.Transformation
|
|||||||
public required string XPathSelector { get; set; }
|
public required string XPathSelector { get; set; }
|
||||||
public required IEnumerable<Part> Body;
|
public required IEnumerable<Part> Body;
|
||||||
|
|
||||||
public void Transform(XDocument xml)
|
public void Transform(XDocument xml, SourceLocation sloc)
|
||||||
{
|
{
|
||||||
var context = new Context();
|
var context = new Context();
|
||||||
context.Node = xml.Root;
|
context.Node = xml.Root;
|
||||||
|
context.SLoc = sloc;
|
||||||
Operate(context);
|
Operate(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +35,8 @@ namespace XNeedle.Transformation
|
|||||||
{
|
{
|
||||||
var loopCtx = ctx.Clone();
|
var loopCtx = ctx.Clone();
|
||||||
loopCtx.Node = n;
|
loopCtx.Node = n;
|
||||||
|
var lineInfo = (IXmlLineInfo)n;
|
||||||
|
loopCtx.SLoc = ctx.SLoc.WithLineNumber(lineInfo.HasLineInfo() ? lineInfo.LineNumber : -1);
|
||||||
foreach (var b in Body)
|
foreach (var b in Body)
|
||||||
{
|
{
|
||||||
b.Operate(loopCtx);
|
b.Operate(loopCtx);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user