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 hex(Context ctx, double value) { uint v = (uint)value; return $"#x{v:X8}"; } public static double GetLevel(Context ctx) { int level = 0; XElement? n = ctx.Node; while (null != (n = n?.Parent)) level++; return level; } public static double crc(Context ctx) { 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); // 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) { 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 ""; } } }