diff --git a/Transformation/Bangs/AfterBang.cs b/Transformation/Bangs/AfterBang.cs new file mode 100644 index 0000000..bd08428 --- /dev/null +++ b/Transformation/Bangs/AfterBang.cs @@ -0,0 +1,28 @@ +using System.Xml.Linq; +using XNeedle.Parser.Elements; + +namespace XNeedle.Transformation.Bangs +{ + public class AfterBang : BangOperation + { + public override string Name => "after"; + + public override Context Execute(Context ctx, IEnumerable arguments) + { + if (ctx.Node == null) + { + throw new ArgumentNullException("!after needs a reachable node"); + } + if (arguments.Count() < 1) + { + throw new ArgumentException("!after needs at least the tag name as argument."); + } + + var tagname = arguments.ElementAt(0); + var element = new XElement(tagname, ""); + ctx.Node.AddAfterSelf(element); + + return new Context{ Node = element, SLoc = ctx.SLoc.AsSynthetic(), XPathSelector = tagname}; + } + } +} \ No newline at end of file diff --git a/Transformation/Bangs/BeforeBang.cs b/Transformation/Bangs/BeforeBang.cs new file mode 100644 index 0000000..4fc98bc --- /dev/null +++ b/Transformation/Bangs/BeforeBang.cs @@ -0,0 +1,28 @@ +using System.Xml.Linq; +using XNeedle.Parser.Elements; + +namespace XNeedle.Transformation.Bangs +{ + public class BeforeBang : BangOperation + { + public override string Name => "before"; + + public override Context Execute(Context ctx, IEnumerable arguments) + { + if (ctx.Node == null) + { + throw new ArgumentNullException("!before needs a reachable node"); + } + if (arguments.Count() < 1) + { + throw new ArgumentException("!before needs at least the tag name as argument."); + } + + var tagname = arguments.ElementAt(0); + var element = new XElement(tagname, ""); + ctx.Node.AddBeforeSelf(element); + + return new Context{ Node = element, SLoc = ctx.SLoc.AsSynthetic(), XPathSelector = tagname}; + } + } +} \ No newline at end of file diff --git a/Transformation/ContextFunctions.cs b/Transformation/ContextFunctions.cs index acefd4a..008b16f 100644 --- a/Transformation/ContextFunctions.cs +++ b/Transformation/ContextFunctions.cs @@ -113,7 +113,7 @@ namespace XNeedle.Transformation { if (ctx.Node == null) { - throw new ArgumentNullException("!seap needs a reachable node"); + throw new ArgumentNullException("!swap needs a reachable node"); } var a_node = ctx.Node.XPathSelectElement(a_select); @@ -131,5 +131,61 @@ namespace XNeedle.Transformation 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 ""; + } } } \ No newline at end of file