<PackageReference Include="Castle.Core" Version="3.2.2" />

XPathCompiler

public static class XPathCompiler
using System; using System.Collections.Generic; using System.Xml.XPath; namespace Castle.Components.DictionaryAdapter.Xml { public static class XPathCompiler { private enum Token { Name, SelfReference, StepSeparator, NameSeparator, AttributeStart, VariableStart, EqualsOperator, PredicateStart, PredicateEnd, StringLiteral, EndOfInput, Error } private class Tokenizer { private enum State { Initial, Name, SingleQuoteString, DoubleQuoteString, Failed } private readonly string input; private State state; private Token token; private int index; private int start; private int prior; public Token Token => token; public string Text => input.Substring(start, index - start + 1); public int Index => start; public Tokenizer(string input) { this.input = input; state = State.Initial; index = -1; Consume(); } public string GetConsumedText(int start) { return input.Substring(start, prior - start + 1); } public void Consume() { prior = index; while (true) { char c = ReadChar(); switch (state) { case State.Initial: start = index; switch (c) { case '.': token = Token.SelfReference; return; case '/': token = Token.StepSeparator; return; case ':': token = Token.NameSeparator; return; case '@': token = Token.AttributeStart; return; case '$': token = Token.VariableStart; return; case '=': token = Token.EqualsOperator; return; case '[': token = Token.PredicateStart; return; case ']': token = Token.PredicateEnd; return; case '': token = Token.EndOfInput; return; case '\'': state = State.SingleQuoteString; break; case '"': state = State.DoubleQuoteString; break; default: if (IsNameStartChar(c)) state = State.Name; else if (!IsWhitespace(c)) { state = State.Failed; } break; } break; case State.Name: if (!IsNameChar(c)) { RewindChar(); token = Token.Name; state = State.Initial; return; } break; case State.SingleQuoteString: if (c == '\'') { token = Token.StringLiteral; state = State.Initial; return; } break; case State.DoubleQuoteString: if (c == '"') { token = Token.StringLiteral; state = State.Initial; return; } break; case State.Failed: token = Token.Error; return; } } } private char ReadChar() { if (++index >= input.Length) return ''; return input[index]; } private void RewindChar() { index--; } private static bool IsWhitespace(char c) { if (' ' != c && '\t' != c && '\r' != c) return '\n' == c; return true; } private static bool IsNameStartChar(char c) { if (('A' > c || c > 'Z') && ('a' > c || c > 'z') && '_' != c && ('À' > c || c > 'Ö') && ('Ø' > c || c > 'ö') && ('ø' > c || c > '˿') && ('Ͱ' > c || c > 'ͽ') && ('Ϳ' > c || c > '῿') && ('‌' > c || c > '‍') && ('⁰' > c || c > '↏') && ('Ⰰ' > c || c > '⿯') && ('、' > c || c > '퟿') && ('豈' > c || c > '﷏')) { if ('ﷰ' <= c) return c <= '�'; return false; } return true; } private static bool IsNameChar(char c) { if (!IsNameStartChar(c) && '-' != c && '.' != c && ('0' > c || c > '9') && '·' != c && ('̀' > c || c > 'ͯ')) { if ('‿' <= c) return c <= '⁀'; return false; } return true; } } private static readonly Func<CompiledXPathNode> NodeFactory = () => new CompiledXPathNode(); private static readonly Func<CompiledXPathStep> StepFactory = () => new CompiledXPathStep(); public static CompiledXPath Compile(string path) { if (path == null) throw Error.ArgumentNull("path"); CompiledXPath compiledXPath = new CompiledXPath(); compiledXPath.Path = XPathExpression.Compile(path); Tokenizer source = new Tokenizer(path); if (!ParsePath(source, compiledXPath)) compiledXPath.MakeNotCreatable(); compiledXPath.Prepare(); return compiledXPath; } private static bool ParsePath(Tokenizer source, CompiledXPath path) { CompiledXPathStep step = null; do { if (!ParseStep(source, path, ref step)) return false; if (source.Token == Token.EndOfInput) return true; if (!Consume(source, Token.StepSeparator)) return false; } while (!step.IsAttribute); return false; } private static bool ParseStep(Tokenizer source, CompiledXPath path, ref CompiledXPathStep step) { CompiledXPathStep compiledXPathStep = step; int index = source.Index; if (!ParseNodeCore(source, StepFactory, ref step)) return false; if (step != compiledXPathStep) { string consumedText = source.GetConsumedText(index); step.Path = XPathExpression.Compile(consumedText); if (compiledXPathStep == null) path.FirstStep = step; else LinkNodes(compiledXPathStep, step); path.Depth++; } return true; } private static bool ParseNodeCore<TNode>(Tokenizer source, Func<TNode> factory, ref TNode node) where TNode : CompiledXPathNode { if (!Consume(source, Token.SelfReference)) { node = factory(); if (Consume(source, Token.AttributeStart)) node.IsAttribute = true; if (!ParseQualifiedName(source, node)) return false; } if (node != null) return ParsePredicateList(source, node); return source.Token != Token.PredicateStart; } private static bool ParsePredicateList(Tokenizer source, CompiledXPathNode parent) { while (Consume(source, Token.PredicateStart)) { if (!ParsePredicate(source, parent)) return false; } return true; } private static bool ParsePredicate(Tokenizer source, CompiledXPathNode parent) { if (!ParseAndExpression(source, parent)) return false; if (!Consume(source, Token.PredicateEnd)) return false; return true; } private static bool ParseAndExpression(Tokenizer source, CompiledXPathNode parent) { while (true) { if (!ParseExpression(source, parent)) return false; if (source.Token != 0 || source.Text != "and") break; source.Consume(); } return true; } private static bool ParseExpression(Tokenizer source, CompiledXPathNode parent) { if (source.Token != 0 && source.Token != Token.AttributeStart && source.Token != Token.SelfReference) return ParseRightToLeftExpression(source, parent); return ParseLeftToRightExpression(source, parent); } private static bool ParseLeftToRightExpression(Tokenizer source, CompiledXPathNode parent) { if (!ParseNestedPath(source, parent, out CompiledXPathNode node)) return false; if (!Consume(source, Token.EqualsOperator)) return true; if (!ParseValue(source, out XPathExpression value)) return false; node.Value = value; return true; } private static bool ParseRightToLeftExpression(Tokenizer source, CompiledXPathNode parent) { if (!ParseValue(source, out XPathExpression value)) return false; if (!Consume(source, Token.EqualsOperator)) return false; if (!ParseNestedPath(source, parent, out CompiledXPathNode node)) return false; node.Value = value; return true; } private static bool ParseNestedPath(Tokenizer source, CompiledXPathNode parent, out CompiledXPathNode node) { node = null; while (true) { if (!ParseNode(source, parent, ref node)) return false; if (!Consume(source, Token.StepSeparator)) break; if (node.IsAttribute) return false; } if (node == null) { IList<CompiledXPathNode> dependencies = parent.Dependencies; if (dependencies.Count != 0) return false; dependencies.Add(node = NodeFactory()); } return true; } private static bool ParseNode(Tokenizer source, CompiledXPathNode parent, ref CompiledXPathNode node) { CompiledXPathNode compiledXPathNode = node; if (!ParseNodeCore(source, NodeFactory, ref node)) return false; if (node != compiledXPathNode) { if (compiledXPathNode == null) parent.Dependencies.Add(node); else LinkNodes(compiledXPathNode, node); } return true; } private static bool ParseValue(Tokenizer source, out XPathExpression value) { int index = source.Index; if (!Consume(source, Token.StringLiteral) && (!Consume(source, Token.VariableStart) || !ParseQualifiedName(source, null))) return Try.Failure(out value); string consumedText = source.GetConsumedText(index); value = XPathExpression.Compile(consumedText); return true; } private static bool ParseQualifiedName(Tokenizer source, CompiledXPathNode node) { if (!ParseName(source, out string name)) return false; if (!Consume(source, Token.NameSeparator)) { if (node != null) node.LocalName = name; return true; } if (!ParseName(source, out string name2)) return false; if (node != null) { node.Prefix = name; node.LocalName = name2; } return true; } private static bool ParseName(Tokenizer source, out string name) { if (source.Token != 0) return Try.Failure(out name); name = source.Text; source.Consume(); return true; } private static bool Consume(Tokenizer source, Token token) { if (source.Token != token) return false; source.Consume(); return true; } private static void LinkNodes(CompiledXPathNode previous, CompiledXPathNode next) { previous.NextNode = next; next.PreviousNode = previous; } } }