From 6ef074081a9edacf9be4c801355405ac04f6b775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20H=C3=B6glinger?= Date: Thu, 23 Apr 2026 18:37:38 +0200 Subject: [PATCH] Restructured project, removed comment-out code --- Parser/Elements/Arithmeric.cs | 18 ++ Parser/Elements/Call.cs | 10 + Parser/Elements/Context.cs | 16 + Parser/Elements/Evaluatable.cs | 10 + Parser/Elements/ExpressionPart.cs | 14 + Parser/Elements/Part.cs | 7 + Parser/XmlFragParser.cs | 27 ++ Parser/XnParser.cs | 195 ++++++++++++ Program.cs | 467 ++--------------------------- Transformation/ContextFunctions.cs | 122 ++++++++ Transformation/Rule.cs | 41 +++ XNeedle.csproj | 2 +- 12 files changed, 484 insertions(+), 445 deletions(-) create mode 100644 Parser/Elements/Arithmeric.cs create mode 100644 Parser/Elements/Call.cs create mode 100644 Parser/Elements/Context.cs create mode 100644 Parser/Elements/Evaluatable.cs create mode 100644 Parser/Elements/ExpressionPart.cs create mode 100644 Parser/Elements/Part.cs create mode 100644 Parser/XmlFragParser.cs create mode 100644 Parser/XnParser.cs create mode 100644 Transformation/ContextFunctions.cs create mode 100644 Transformation/Rule.cs diff --git a/Parser/Elements/Arithmeric.cs b/Parser/Elements/Arithmeric.cs new file mode 100644 index 0000000..8419678 --- /dev/null +++ b/Parser/Elements/Arithmeric.cs @@ -0,0 +1,18 @@ +using System.Linq.Expressions; + +namespace XNeedle.Parser.Elements +{ + public class Arithmeric : Evaluatable + { + public required Expression> Expr { get; set; } + public override string Evaluate(Context ctx) + { + return Expr.Compile()().ToString(); + } + + public override void Operate(Context xtx) + { + Expr.Compile()(); + } + } +} \ No newline at end of file diff --git a/Parser/Elements/Call.cs b/Parser/Elements/Call.cs new file mode 100644 index 0000000..5710453 --- /dev/null +++ b/Parser/Elements/Call.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace XNeedle.Parser.Elements +{ + public class Call : Evaluatable + { + public required string Identifier { get; set; } + public required IEnumerable Arguments; + } +} \ No newline at end of file diff --git a/Parser/Elements/Context.cs b/Parser/Elements/Context.cs new file mode 100644 index 0000000..71948ed --- /dev/null +++ b/Parser/Elements/Context.cs @@ -0,0 +1,16 @@ +using System.Xml; + +namespace XNeedle.Parser.Elements +{ + public class Context + { + public string? XPathSelector { get; set; } + public XmlNodeList? Nodes { get; set; } + public XmlNode? Node { get; set; } + + public Context Clone() + { + return (Context)this.MemberwiseClone(); + } + } +} \ No newline at end of file diff --git a/Parser/Elements/Evaluatable.cs b/Parser/Elements/Evaluatable.cs new file mode 100644 index 0000000..bd81d94 --- /dev/null +++ b/Parser/Elements/Evaluatable.cs @@ -0,0 +1,10 @@ +namespace XNeedle.Parser.Elements +{ + public class Evaluatable : Part + { + public virtual string Evaluate(Context ctx) + { + return ""; + } + } +} \ No newline at end of file diff --git a/Parser/Elements/ExpressionPart.cs b/Parser/Elements/ExpressionPart.cs new file mode 100644 index 0000000..d003bb3 --- /dev/null +++ b/Parser/Elements/ExpressionPart.cs @@ -0,0 +1,14 @@ +using System.Linq.Expressions; + +namespace XNeedle.Parser.Elements +{ + public class ExpressionPart : Part + { + public required Expression> Expr { get; set; } + + public override void Operate(Context xtx) + { + Expr.Compile()(); + } + } +} \ No newline at end of file diff --git a/Parser/Elements/Part.cs b/Parser/Elements/Part.cs new file mode 100644 index 0000000..1acb93a --- /dev/null +++ b/Parser/Elements/Part.cs @@ -0,0 +1,7 @@ +namespace XNeedle.Parser.Elements +{ + public class Part + { + public virtual void Operate(Context ctx) {} + } +} \ No newline at end of file diff --git a/Parser/XmlFragParser.cs b/Parser/XmlFragParser.cs new file mode 100644 index 0000000..88acf58 --- /dev/null +++ b/Parser/XmlFragParser.cs @@ -0,0 +1,27 @@ +using Sprache; + +namespace XNeedle.Parser +{ + public class XmlFragParser + { + public static Parser OpeningTagAnchor(string tag) + { + return + from lbraket in Parse.Char('<') + from tagname in Parse.String(tag) + from any in Parse.CharExcept('>').Many() + from rbraket in Parse.Char('>') + select new string([.. any]); + } + public static Parser ChecksumData(string opening, string closing) + { + return + from pregarbage in Parse.AnyChar.Many() + from preamble in Parse.String(opening).Token() + from content in Parse.AnyChar.Many() + from epilouge in Parse.String("") + from postgarbage in Parse.AnyChar.Many() + select new string([.. content]) + epilouge; + } + } +} \ No newline at end of file diff --git a/Parser/XnParser.cs b/Parser/XnParser.cs new file mode 100644 index 0000000..7c7c741 --- /dev/null +++ b/Parser/XnParser.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Sprache; +using System.Globalization; +using XNeedle.Parser.Elements; +using Part = XNeedle.Parser.Elements.Part; +using XNeedle.Transformation; +using Rule = XNeedle.Transformation.Rule; + +namespace XNeedle.Parser +{ + public class XnParser + { + static Parser Operator(string op, ExpressionType opType) + { + return Parse.String(op).Token().Return(opType); + } + + //static MethodInfo ConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); + static readonly Parser Add = Operator("+", ExpressionType.AddChecked); + static readonly Parser Subtract = Operator("-", ExpressionType.SubtractChecked); + static readonly Parser Multiply = Operator("*", ExpressionType.MultiplyChecked); + static readonly Parser Divide = Operator("/", ExpressionType.Divide); + static readonly Parser Modulo = Operator("%", ExpressionType.Modulo); + static readonly Parser Power = Operator("^", ExpressionType.Power); + + static Parser Function(Context ctx) + { + return + from name in Parse.Letter.AtLeastOnce().Text() + from lparen in Parse.Char('(') + from expr in Parse.Ref(() => Expr(ctx)).DelimitedBy(Parse.Char(',').Token()).Optional() + from rparen in Parse.Char(')') + select CallFunction(ctx, name, expr.GetOrElse(new Expression[0]).ToArray()); + } + + static Expression CallFunction(Context ctx, string name, Expression[] parameters) + { + var mathMethodInfo = typeof(Math).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).ToArray()); + if (mathMethodInfo != null) + { + return Expression.Call(mathMethodInfo, parameters); + } + var ctxMethodInfo = typeof(ContextFunctions).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).Prepend(typeof(Context)).ToArray()); + if (ctxMethodInfo != null) + { + return Expression.Call(ctxMethodInfo, parameters.Prepend(Expression.Constant(ctx)).ToArray()); + } + + throw new ParseException(string.Format("Function '{0}({1})' does not exist.", name, + string.Join(",", parameters.Select(e => e.Type.Name)))); + } + + static readonly Parser NumberConstant = + Parse.Decimal + .Select(x => Expression.Constant(double.Parse(x))) + .Named("number"); + + private static readonly Parser StringConstant = + from open in Parse.Char('"') + from value in Parse.CharExcept('"').Many().Text() + from close in Parse.Char('"') + select Expression.Constant(new string(value)); + + static Parser Factor(Context ctx) + { + return (from lparen in Parse.Char('(') + from expr in Parse.Ref(() => Expr(ctx)) + from rparen in Parse.Char(')') + select expr).Named("expression") + .XOr(NumberConstant) + .XOr(StringConstant) + .XOr(Function(ctx)); + } + + static Parser Operand(Context ctx) + { + return ((from sign in Parse.Char('-') + from factor in Factor(ctx) + select Expression.Negate(factor) + ).XOr(Factor(ctx))).Token(); + } + static Parser InnerTerm(Context ctx) + { + return Parse.ChainRightOperator(Power, Operand(ctx), Expression.MakeBinary); + } + + static Parser Term(Context ctx) + { + return Parse.ChainOperator(Multiply.Or(Divide).Or(Modulo), InnerTerm(ctx), Expression.MakeBinary); + } + + static Parser ConcatExpr(Context ctx) + { + var concatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); + return + from left in StringConstant.Or(Function(ctx)) + from op in Parse.String("..").Token() + from right in StringConstant.Or(Function(ctx)) + select Expression.Add(left, right, concatMethod); + } + + static Parser ArithExpr(Context ctx) + { + return Parse.ChainOperator(Add.Or(Subtract), Term(ctx), Expression.MakeBinary); + } + + static Parser Expr(Context ctx) + { + return ConcatExpr(ctx).Or(ArithExpr(ctx)); + } + + static Parser ExprPart(Context ctx) + { + return Expr(ctx).Select(body => new ExpressionPart{Expr = Expression.Lambda>(body)}); + } + + static readonly Parser XPath = + from path in Parse.LetterOrDigit + .XOr(Parse.Char('/')) + .XOr(Parse.Char(' ')) + .XOr(Parse.Char('[')) + .XOr(Parse.Char(']')) + .XOr(Parse.Char('=')) + .XOr(Parse.Char('.')) + .XOr(Parse.Char('_')) + .XOr(Parse.Char('*')) + .XOr(Parse.Char('@')) + .XOr(Parse.Char('#')) + .XOr(Parse.Char('(')) + .XOr(Parse.Char(')')) + .XOr(Parse.Char('\'')) + .Many() + select new string(path.ToArray()); + + static Parser Selector(Parser selection) + { + return from open in Parse.String("$(\"").Token() + from s in selection + from close in Parse.String("\")").Token() + select s; + } + + public static readonly Parser XPathSelector = Selector(XPath); + + public static readonly Parser Identifier = + from first in Parse.Letter.Once() + from rest in Parse.LetterOrDigit.XOr(Parse.Char('-')).XOr(Parse.Char('_')).Many() + select new string(first.Concat(rest).ToArray()); + + private static readonly Parser StringObject = + from open in Parse.Char('"') + from value in Parse.CharExcept('"').Many().Text() + from close in Parse.Char('"') + select new string(value); + + private static readonly Parser NumberObject = + Parse.DecimalInvariant + .Select(s => double.Parse(s, CultureInfo.InvariantCulture)) + .Select(v => v.ToString()); + + public static readonly Parser Argument = + from argument in StringObject.Or(NumberObject) + select argument; + + public static readonly Parser CallObject = + from identifier in Identifier + from open in Parse.Char('(') + from arguments in Argument.DelimitedBy(Parse.Char(',').Token()) + from close in Parse.Char(')') + select new Call{Identifier = new string(identifier.ToArray()), Arguments = arguments}; + + /*public static readonly Parser ExpressionObject = + from expression in CallObject + */ + + public static CommentParser Comment = new CommentParser { Single = "#", NewLine = Environment.NewLine }; + + public static readonly Parser RuleObject = + from c1 in Comment.SingleLineComment.Optional() + from selector in Selector(XPath).Token() + let ctx = new Context{XPathSelector = selector} + from ws1 in Parse.WhiteSpace.Many().Optional() + from open in Parse.Char('{') + from ws2 in Parse.WhiteSpace.Many().Optional() + from c2 in Comment.SingleLineComment.Optional() + from expressions in ExprPart(ctx).Or(Parse.Ref(() => RuleObject)).DelimitedBy(Parse.Char(';').Token()) + from tailingsemi in Parse.Char(';').Optional() + from ws3 in Parse.WhiteSpace.Many().Optional() + from close in Parse.Char('}') + select new Rule{XPathSelector = selector, Body = expressions}; + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index f88013b..9a2e2d2 100644 --- a/Program.cs +++ b/Program.cs @@ -1,453 +1,32 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Sprache; -using System.IO; -using System.Globalization; -using System.Reflection.Metadata; using System.Xml; -using System.Threading.Tasks.Dataflow; +using Sprache; +using XNeedle.Parser; +using XNeedle.Parser.Elements; +using XNeedle.Transformation; -public class Context +namespace XNeedle { - public required string XPathSelector { get; set; } - public XmlNodeList? Nodes { get; set; } - public XmlNode? Node { get; set; } -} - -public class ContextFunctions -{ - - public static string hex(Context ctx, double value) + class Program { - uint v = (uint)value; - return $"#x{v:X4}"; - } - - public static int GetLevel(XmlNode node) { - int level = 0; - while (null != (node = node.ParentNode)) - level++; - - return level; - } - public static double crc(Context ctx) - { - int crc = 0xAFFE; - - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - settings.ConformanceLevel = ConformanceLevel.Fragment; - - // This isn't working, need to render the _entire_ xml each time :( - using (var sw = new StringWriter()) { - using (var xw = XmlWriter.Create(sw, settings)) { - ctx.Node.WriteContentTo(xw); - } - var data = sw.ToString(); - - var indentLevel = GetLevel(ctx.Node); - var extraIndent = new string(' ', indentLevel); - - data = extraIndent + data.ReplaceLineEndings(Environment.NewLine + extraIndent); - - var crc32 = new System.IO.Hashing.Crc32(); - var bytes = Encoding.UTF8.GetBytes(data); - crc32.Append(bytes); - crc = BitConverter.ToInt32(crc32.GetCurrentHash()); - } - - return (double)crc; - } - - public static string text(Context ctx) - { - Console.WriteLine($"Get T: {ctx.Node.InnerText}"); - return ctx.Node.InnerText; - } - - public static string text(Context ctx, string txt) - { - Console.WriteLine($"Set T: {txt}"); - return ctx.Node.InnerText = txt; - } - - public static string attr(Context ctx, string name, string value) - { - if (ctx.Node.Attributes[name] != null) + static void Main() { - ctx.Node.Attributes[name].Value = value; + var input = "$(\"//Device/Type[@ProductCode='#x0B583052' and @RevisionNo='#x00110000']\")"; + + var rule = (Rule)XnParser.RuleObject.Parse(input); + + XmlDocument doc = new XmlDocument(); + doc.Load("ELx9xx.xml"); + + rule.Transform(doc); + + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + XmlWriter writer = XmlWriter.Create("El9xxFi.xml", settings); + + doc.WriteContentTo(writer); + + Console.WriteLine("Done."); } - else - { - var add = ctx.Node.OwnerDocument.CreateAttribute(name); - add.Value = value; - ctx.Node.Attributes.SetNamedItem(add); - } - return ""; - } - - public static string attr(Context ctx, string name) - { - if (ctx.Node.Attributes[name] != null) - { - return ctx.Node.Attributes[name].Value; - } - return ""; - } -} - -public class Part -{ - public virtual void Operate(Context ctx) {} -} - -public class ExpressionPart : Part -{ - public required Expression> Expr { get; set; } - - public override void Operate(Context xtx) - { - Expr.Compile()(); - } -} - -public class Evaluatable : Part -{ - public virtual string Evaluate(Context ctx) - { - return ""; - } -} - -public class Call : Evaluatable -{ - public required string Identifier { get; set; } - public required IEnumerable Arguments; -} - -public class Arithmeric : Evaluatable -{ - public required Expression> Expr { get; set; } - public override string Evaluate(Context ctx) - { - return Expr.Compile()().ToString(); - } - - public override void Operate(Context xtx) - { - Expr.Compile()(); - } -} - -public class Rule : Part -{ - public required string XPathSelector { get; set; } - public required IEnumerable Body; - public required Context Context; - - public void Transform(XmlDocument xml) - { - Context.Node = xml.DocumentElement; - Operate(Context); - } - - public override void Operate(Context ctx) - { - Context.Nodes = ctx.Node.SelectNodes(Context.XPathSelector); - foreach (XmlNode n in Context.Nodes) - { - Context.Node = n; - foreach (var b in Body) - { - b.Operate(Context); - } - } - } -} - -public static class TrafoParser -{ - static Parser Operator(string op, ExpressionType opType) - { - return Parse.String(op).Token().Return(opType); - } - - //static MethodInfo ConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); - static readonly Parser Add = Operator("+", ExpressionType.AddChecked); - static readonly Parser Subtract = Operator("-", ExpressionType.SubtractChecked); - static readonly Parser Multiply = Operator("*", ExpressionType.MultiplyChecked); - static readonly Parser Divide = Operator("/", ExpressionType.Divide); - static readonly Parser Modulo = Operator("%", ExpressionType.Modulo); - static readonly Parser Power = Operator("^", ExpressionType.Power); - /* - static readonly Parser Function = - from name in Parse.Letter.AtLeastOnce().Text() - from lparen in Parse.Char('(') - from expr in Parse.Ref(() => Expr).DelimitedBy(Parse.Char(',').Token()) - from rparen in Parse.Char(')') - select CallFunction(name, expr.ToArray()); - */ - static Parser Function(Context ctx) - { - return - from name in Parse.Letter.AtLeastOnce().Text() - from lparen in Parse.Char('(') - from expr in Parse.Ref(() => Expr(ctx)).DelimitedBy(Parse.Char(',').Token()).Optional() - from rparen in Parse.Char(')') - select CallFunction(ctx, name, expr.GetOrElse(new Expression[0]).ToArray()); - } - - static Expression CallFunction(Context ctx, string name, Expression[] parameters) - { - var mathMethodInfo = typeof(Math).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).ToArray()); - if (mathMethodInfo != null) - { - return Expression.Call(mathMethodInfo, parameters); - } - var ctxMethodInfo = typeof(ContextFunctions).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).Prepend(typeof(Context)).ToArray()); - if (ctxMethodInfo != null) - { - return Expression.Call(ctxMethodInfo, parameters.Prepend(Expression.Constant(ctx)).ToArray()); - } - - throw new ParseException(string.Format("Function '{0}({1})' does not exist.", name, - string.Join(",", parameters.Select(e => e.Type.Name)))); - } - - static readonly Parser NumberConstant = - Parse.Decimal - .Select(x => Expression.Constant(double.Parse(x))) - .Named("number"); - /* - static readonly Parser Factor = - (from lparen in Parse.Char('(') - from expr in Parse.Ref(() => Expr) - from rparen in Parse.Char(')') - select expr).Named("expression") - .XOr(Constant) - .XOr(Function); - */ - - private static readonly Parser StringConstant = - from open in Parse.Char('"') - from value in Parse.CharExcept('"').Many().Text() - from close in Parse.Char('"') - select Expression.Constant(new string(value)); - - static Parser Factor(Context ctx) - { - return (from lparen in Parse.Char('(') - from expr in Parse.Ref(() => Expr(ctx)) - from rparen in Parse.Char(')') - select expr).Named("expression") - .XOr(NumberConstant) - .XOr(StringConstant) - .XOr(Function(ctx)); - } - /* - static readonly Parser Operand = - ((from sign in Parse.Char('-') - from factor in Factor - select Expression.Negate(factor) - ).XOr(Factor)).Token(); - */ - static Parser Operand(Context ctx) - { - return ((from sign in Parse.Char('-') - from factor in Factor(ctx) - select Expression.Negate(factor) - ).XOr(Factor(ctx))).Token(); - } - - //static readonly Parser InnerTerm = Parse.ChainRightOperator(Power, Operand, Expression.MakeBinary); - static Parser InnerTerm(Context ctx) - { - return Parse.ChainRightOperator(Power, Operand(ctx), Expression.MakeBinary); - } - - //static readonly Parser Term = Parse.ChainOperator(Multiply.Or(Divide).Or(Modulo), InnerTerm, Expression.MakeBinary); - static Parser Term(Context ctx) - { - return Parse.ChainOperator(Multiply.Or(Divide).Or(Modulo), InnerTerm(ctx), Expression.MakeBinary); - } - - //static readonly Parser Expr = Parse.ChainOperator(Add.Or(Subtract), Term, Expression.MakeBinary); - - static Parser ConcatExpr(Context ctx) - { - var concatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); - return - from left in StringConstant.Or(Function(ctx)) - from op in Parse.String("..").Token() - from right in StringConstant.Or(Function(ctx)) - select Expression.Add(left, right, concatMethod); - } - - static Parser ArithExpr(Context ctx) - { - return Parse.ChainOperator(Add.Or(Subtract), Term(ctx), Expression.MakeBinary); - } - - static Parser Expr(Context ctx) - { - return ConcatExpr(ctx).Or(ArithExpr(ctx)); - } - - - /*static readonly Parser>> Lambda = - Expr.End().Select(body => Expression.Lambda>(body)); - */ - - static Parser ExprPart(Context ctx) - { - return Expr(ctx).Select(body => new ExpressionPart{Expr = Expression.Lambda>(body)}); - } - - static readonly Parser XPath = - from path in Parse.LetterOrDigit - .XOr(Parse.Char('/')) - .XOr(Parse.Char(' ')) - .XOr(Parse.Char('[')) - .XOr(Parse.Char(']')) - .XOr(Parse.Char('=')) - .XOr(Parse.Char('.')) - .XOr(Parse.Char('_')) - .XOr(Parse.Char('*')) - .XOr(Parse.Char('@')) - .XOr(Parse.Char('#')) - .XOr(Parse.Char('(')) - .XOr(Parse.Char(')')) - .XOr(Parse.Char('\'')) - .Many() - select new string(path.ToArray()); - - static Parser Selector(Parser selection) - { - return from open in Parse.String("$(\"").Token() - from s in selection - from close in Parse.String("\")").Token() - select s; - } - - public static readonly Parser XPathSelector = Selector(XPath); - - public static readonly Parser Identifier = - from first in Parse.Letter.Once() - from rest in Parse.LetterOrDigit.XOr(Parse.Char('-')).XOr(Parse.Char('_')).Many() - select new string(first.Concat(rest).ToArray()); - - private static readonly Parser StringObject = - from open in Parse.Char('"') - from value in Parse.CharExcept('"').Many().Text() - from close in Parse.Char('"') - select new string(value); - - private static readonly Parser NumberObject = - Parse.DecimalInvariant - .Select(s => double.Parse(s, CultureInfo.InvariantCulture)) - .Select(v => v.ToString()); - - public static readonly Parser Argument = - from argument in StringObject.Or(NumberObject) - select argument; - - public static readonly Parser CallObject = - from identifier in Identifier - from open in Parse.Char('(') - from arguments in Argument.DelimitedBy(Parse.Char(',').Token()) - from close in Parse.Char(')') - select new Call{Identifier = new string(identifier.ToArray()), Arguments = arguments}; - - /*public static readonly Parser ExpressionObject = - from expression in CallObject - */ - - public static CommentParser Comment = new CommentParser { Single = "#", NewLine = Environment.NewLine }; - - public static readonly Parser RuleObject = - from c1 in Comment.SingleLineComment.Optional() - from selector in Selector(XPath).Token() - let ctx = new Context{XPathSelector = selector} - from ws1 in Parse.WhiteSpace.Many().Optional() - from open in Parse.Char('{') - from ws2 in Parse.WhiteSpace.Many().Optional() - from c2 in Comment.SingleLineComment.Optional() - //from expressions in RuleObject.Select(n => (Rule)n).DelimitedBy(';') - //from expressions in ExprPart(new Context{XPathSelector = selector}).Or(RuleObject).Select(n => (Part)n).DelimitedBy(Parse.Char(';').Token()) - from expressions in ExprPart(ctx).Or(RuleObject).DelimitedBy(Parse.Char(';').Token()) - from tailingsemi in Parse.Char(';').Optional() - from ws3 in Parse.WhiteSpace.Many().Optional() - from close in Parse.Char('}') - select new Rule{XPathSelector = selector, Body = expressions, Context = ctx}; -} - - -class Program -{ - static void Main() - { - var input = "$(\"//Device/(Type[@ProductCode='#x0B583052' and @RevisionNo='#x00110000'])[1]/..\")"; - input = -@" -$(""/EtherCATInfo/Vendor/Name"") -{ - $(""Element"") { attr(""foo"", ""bar""); Sin(2); } - text(""Fake Beckhoff""); -} -"; - input = -@" -$(""/EtherCATInfo/Vendor/Name"") -{ - text(""Foobar "" .. text()); - $(""Element"") - { - attr(""foo"", ""bar""); - }; -} -"; - - input = @" -$(""//EtherCATInfo/Vendor/Name"") -{ - text(""Fake "" .. text()); - attr(""Crc"", hex(3735928559)); -} -"; - - input = @" -$(""//Device/Type[@ProductCode='#x0B583052' and @RevisionNo='#x00110000']/.."") -{ - $(""Name[@LcId='1033']"") { text(""EL2904, 4 Ch. Danger Output 24V, 0.5A, TwinUNSAFE""); }; - $(""RxPdo/Index[.='#x1600']/.."") - { - $(""Name"") { text(""FSoE Outputs""); }; - $(""Entry/Index[.='#x7000']"") { text(""#x4711""); }; - attr(""fid"", ""1""); - attr(""FaultId"", attr(""fid"")); - attr(""Crc32x"", hex(crc()+1)); - } -} -"; - var parsed = TrafoParser.RuleObject.Parse(input); - - XmlDocument doc = new XmlDocument(); - doc.Load("ELx9xx.xml"); - - parsed.Transform(doc); - - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - XmlWriter writer = XmlWriter.Create("El9xxFi.xml", settings); - - doc.WriteContentTo(writer); - - Console.WriteLine("Done."); } } \ No newline at end of file diff --git a/Transformation/ContextFunctions.cs b/Transformation/ContextFunctions.cs new file mode 100644 index 0000000..517d3ab --- /dev/null +++ b/Transformation/ContextFunctions.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using Sprache; +using XNeedle.Parser; +using XNeedle.Parser.Elements; + +namespace XNeedle.Transformation +{ + public class ContextFunctions + { + public static string hex(Context ctx, double value) + { + uint v = (uint)value; + return $"#x{v:X8}"; + } + + public static int GetLevel(XmlNode node) + { + int level = 0; + XmlNode? currentNode = node; + while (null != (currentNode = currentNode.ParentNode)) + level++; + + return level; + } + public static double crc(Context ctx) + { + if (ctx.Node is null) + { + return 0; + } + int crc = 0xAFFE; + + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.ConformanceLevel = ConformanceLevel.Document; + settings.OmitXmlDeclaration = true; + + // This isn't performant, but the whole document has to be rendered + // for an accurate data stream + string docrender = ""; + string fragrender = ""; + using (var sw = new StringWriter()) + { + using (var xw = XmlWriter.Create(sw, settings)) + { + if (ctx.Node.OwnerDocument is not null) + { + ctx.Node.OwnerDocument.WriteContentTo(xw); + } + } + docrender = sw.ToString(); + } + + settings.Indent = false; + settings.ConformanceLevel = ConformanceLevel.Fragment; + using (var sw = new StringWriter()) + { + using (var xw = XmlWriter.Create(sw, settings)) + { + ctx.Node.WriteTo(xw); + } + fragrender = sw.ToString(); + } + string tagname = ctx.Node.Name; + string anchor = XmlFragParser.OpeningTagAnchor(tagname).Parse(fragrender); + string preamble = $"<{tagname}{anchor}>"; + + //string data = FragXmlParser.ChecksumData(preamble, tagname).TryParse(docrender); + + Console.WriteLine("preamble: " + preamble); + + var crc32 = new System.IO.Hashing.Crc32(); + var bytes = Encoding.UTF8.GetBytes(""); + crc32.Append(bytes); + crc = BitConverter.ToInt32(crc32.GetCurrentHash()); + + return (double)crc; + } + + public static string text(Context ctx) + { + if (ctx.Node is null) return ""; + return ctx.Node.InnerText; + } + + public static string text(Context ctx, string txt) + { + if (ctx.Node is null) return ""; + return ctx.Node.InnerText = txt; + } + + public static string attr(Context ctx, string name, string value) + { + if (ctx.Node is null || ctx.Node.Attributes is null) return ""; + var attr = ctx.Node.Attributes[name]; + if (attr != null) + { + attr.Value = value; + } + else + { + if (ctx.Node.OwnerDocument is null) return ""; + var add = ctx.Node.OwnerDocument.CreateAttribute(name); + add.Value = value; + ctx.Node.Attributes.SetNamedItem(add); + } + return ""; + } + + public static string attr(Context ctx, string name) + { + if (ctx.Node?.Attributes?[name]?.Value is string val) + { + return val; + } + return ""; + } + } +} \ No newline at end of file diff --git a/Transformation/Rule.cs b/Transformation/Rule.cs new file mode 100644 index 0000000..30bfb12 --- /dev/null +++ b/Transformation/Rule.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Xml; +using XNeedle.Parser.Elements; + +namespace XNeedle.Transformation +{ + public class Rule : Part + { + public required string XPathSelector { get; set; } + public required IEnumerable Body; + + public void Transform(XmlDocument xml) + { + var context = new Context(); + context.Node = xml.DocumentElement; + Operate(context); + } + + public override void Operate(Context ctx) + { + if(ctx.Node is null) + { + return; + } + var nodes = ctx.Node.SelectNodes(XPathSelector); + if(nodes is null) + { + return; + } + foreach (XmlNode n in nodes) + { + var loopCtx = ctx.Clone(); + loopCtx.Node = n; + foreach (var b in Body) + { + b.Operate(loopCtx); + } + } + } + } +} \ No newline at end of file diff --git a/XNeedle.csproj b/XNeedle.csproj index aad51a1..2054cd1 100644 --- a/XNeedle.csproj +++ b/XNeedle.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable