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, double 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 double 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 (double)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().FirstOrDefault(); return cdataNode?.Value ?? ""; } public static string cdata(Context ctx, string txt) { if (ctx.Node is null) return ""; var cdataNode = ctx.Node.Nodes().OfType().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 ""; } } }