246 lines
7.8 KiB
C#
246 lines
7.8 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Sprache;
|
|
using XNeedle.Formats;
|
|
using XNeedle.Parser;
|
|
using XNeedle.Parser.Elements;
|
|
using System.Xml.Linq;
|
|
using System.Xml.XPath;
|
|
|
|
namespace XNeedle.Transformation
|
|
{
|
|
public class ContextFunctions
|
|
{
|
|
public static string hexlit(Context ctx, int value)
|
|
{
|
|
uint v = (uint)value;
|
|
return $"#x{v:x8}";
|
|
}
|
|
|
|
public static string lower(Context ctx, string text)
|
|
{
|
|
return text.ToLower();
|
|
}
|
|
|
|
public static string upper(Context ctx, string text)
|
|
{
|
|
return text.ToUpper();
|
|
}
|
|
|
|
public static double GetLevel(Context ctx)
|
|
{
|
|
int level = 0;
|
|
XElement? n = ctx.Node;
|
|
while (null != (n = n?.Parent))
|
|
level++;
|
|
return level;
|
|
}
|
|
|
|
private static uint ComputeCrc32(byte[] data, uint poly, uint init, uint xorOut, bool reflected)
|
|
{
|
|
uint crc = init;
|
|
uint polyReflected = ReflectBits(poly, 32);
|
|
foreach (byte b in data)
|
|
{
|
|
if (reflected)
|
|
{
|
|
crc ^= b;
|
|
for (int i = 0; i < 8; i++)
|
|
crc = (crc & 1) != 0 ? (crc >> 1) ^ polyReflected : crc >> 1;
|
|
}
|
|
else
|
|
{
|
|
crc ^= (uint)b << 24;
|
|
for (int i = 0; i < 8; i++)
|
|
crc = (crc & 0x80000000u) != 0 ? (crc << 1) ^ poly : crc << 1;
|
|
}
|
|
}
|
|
return crc ^ xorOut;
|
|
}
|
|
|
|
private static uint ReflectBits(uint value, int bits)
|
|
{
|
|
uint reflected = 0;
|
|
for (int i = 0; i < bits; i++)
|
|
{
|
|
if ((value & (1u << i)) != 0)
|
|
reflected |= 1u << (bits - 1 - i);
|
|
}
|
|
return reflected;
|
|
}
|
|
|
|
public static int crc32(Context ctx, int poly = 0x04C11DB7, int init = 0, int xorOut = 0, bool reflected = false)
|
|
{
|
|
if (ctx.Node is null)
|
|
return 0;
|
|
|
|
// 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);
|
|
//byte[] fragBytes = ctx.SLoc.Encoding.GetBytes(data);
|
|
byte[] fragBytes = Encoding.UTF8.GetBytes(data);
|
|
|
|
// Step 3: Digest into CRC32 using parameterized CRC32 - by default MPEG-2
|
|
return (int)ComputeCrc32(fragBytes, (uint)poly, (uint)init, (uint)xorOut, reflected);
|
|
}
|
|
|
|
public static string text(Context ctx)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
return ctx.Node.Value;
|
|
}
|
|
|
|
public static string text(Context ctx, string txt)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
ctx.Node.SetValue(txt);
|
|
return txt;
|
|
}
|
|
|
|
public static string cdata(Context ctx)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
var cdataNode = ctx.Node.Nodes().OfType<System.Xml.Linq.XCData>().FirstOrDefault();
|
|
return cdataNode?.Value ?? "";
|
|
}
|
|
|
|
public static string cdata(Context ctx, string txt)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
var cdataNode = ctx.Node.Nodes().OfType<System.Xml.Linq.XCData>().FirstOrDefault();
|
|
if (cdataNode != null)
|
|
{
|
|
cdataNode.Value = txt;
|
|
}
|
|
else
|
|
{
|
|
ctx.Node.Add(new System.Xml.Linq.XCData(txt));
|
|
}
|
|
return txt;
|
|
}
|
|
|
|
public static string attr(Context ctx, string name, string value)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
var attr = ctx.Node.Attribute(name);
|
|
if (attr != null)
|
|
{
|
|
attr.Value = value;
|
|
}
|
|
else
|
|
{
|
|
ctx.Node.SetAttributeValue(name, value);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public static string attr(Context ctx, string name)
|
|
{
|
|
var attr = ctx.Node?.Attribute(name);
|
|
if (attr != null)
|
|
{
|
|
return attr.Value;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public static string swap(Context ctx, string a_select, string b_select)
|
|
{
|
|
if (ctx.Node == null)
|
|
{
|
|
throw new ArgumentNullException("!swap needs a reachable node");
|
|
}
|
|
|
|
var a_node = ctx.Node.XPathSelectElement(a_select);
|
|
var b_node = ctx.Node.XPathSelectElement(b_select);
|
|
|
|
if (a_node == null || b_node == null)
|
|
{
|
|
throw new InvalidOperationException($"!swap could not find nodes for \"{a_select}\" and/or \"{b_select}\".");
|
|
}
|
|
|
|
var placeholder = new XElement("__placeholder__");
|
|
a_node.ReplaceWith(placeholder);
|
|
b_node.ReplaceWith(a_node);
|
|
placeholder.ReplaceWith(b_node);
|
|
|
|
return "";
|
|
}
|
|
|
|
public static string remove(Context ctx, string select)
|
|
{
|
|
if (ctx.Node == null)
|
|
{
|
|
throw new ArgumentNullException("!remove needs a reachable node");
|
|
}
|
|
|
|
// Use ToList to make the lazy/generator enumeration concrete before applying operations
|
|
var nodes = ctx.Node.XPathSelectElements(select).ToList();
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
node.Remove();
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
public static string rename(Context ctx, string newName)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
var replacement = new XElement(newName, ctx.Node.Attributes(), ctx.Node.Nodes());
|
|
ctx.Node.ReplaceWith(replacement);
|
|
ctx.Node = replacement; // update context to point to new element
|
|
return "";
|
|
}
|
|
|
|
public static string rename(Context ctx, string select, string newName)
|
|
{
|
|
if (ctx.Node is null) return "";
|
|
|
|
int atIdx = select.IndexOf('@');
|
|
if (atIdx >= 0)
|
|
{
|
|
// "Node@Attr" — rename an attribute
|
|
var elementSelect = select[..atIdx].TrimEnd();
|
|
var attrName = select[(atIdx + 1)..].TrimStart();
|
|
var target = string.IsNullOrEmpty(elementSelect)
|
|
? ctx.Node
|
|
: ctx.Node.XPathSelectElement(elementSelect);
|
|
var attr = target?.Attribute(attrName);
|
|
if (attr is null) return "";
|
|
attr.Remove();
|
|
target!.SetAttributeValue(newName, attr.Value);
|
|
}
|
|
else
|
|
{
|
|
// plain XPath select — rename element
|
|
var target = ctx.Node.XPathSelectElement(select);
|
|
if (target is null) return "";
|
|
var replacement = new XElement(newName, target.Attributes(), target.Nodes());
|
|
target.ReplaceWith(replacement);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public static string require(Context ctx, string message = "Required node not present")
|
|
{
|
|
if (ctx.Node == null)
|
|
{
|
|
throw new RequireException{SLoc=ctx.SLoc, UserMessage=message};
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(ctx.Node.ToString());
|
|
}
|
|
return "";
|
|
}
|
|
}
|
|
} |