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

XPathCompiler

public static class XPathCompiler
using System.Collections.Generic; using System.Xml; using System.Xml.XPath; namespace Castle.Components.DictionaryAdapter.Xml { public static class XPathCompiler { private enum Token { Name, 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.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) { return XmlConvert.IsWhitespaceChar(c); } private static bool IsNameStartChar(char c) { return XmlConvert.IsStartNCNameChar(c); } private static bool IsNameChar(char c) { return XmlConvert.IsNCNameChar(c); } } 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) { if (!ParseStep(source, out CompiledXPathStep step)) return false; path.FirstStep = step; CompiledXPathStep compiledXPathStep = step; path.Depth = 1; while (true) { if (source.Token == Token.EndOfInput) return true; if (source.Token != Token.StepSeparator) return false; if (compiledXPathStep.IsAttribute) return false; source.Consume(); if (!ParseStep(source, out step)) break; compiledXPathStep.NextStep = step; step.PreviousNode = compiledXPathStep; compiledXPathStep = step; path.Depth++; } return false; } private static bool ParseStep(Tokenizer source, out CompiledXPathStep step) { step = new CompiledXPathStep(); int index = source.Index; if (!ParseNodeCore(source, step)) return false; string consumedText = source.GetConsumedText(index); step.Path = XPathExpression.Compile(consumedText); return true; } private static bool ParseNodeCore(Tokenizer source, CompiledXPathNode node) { if (Consume(source, Token.AttributeStart)) node.IsAttribute = true; if (!ParseQualifiedName(source, node)) return false; if (!ParsePredicateList(source, node)) return false; return true; } private static bool ParsePredicateList(Tokenizer source, CompiledXPathNode node) { IList<CompiledXPathNode> dependencies = node.Dependencies; while (source.Token == Token.PredicateStart) { if (!ParsePredicate(source, dependencies)) return false; } return true; } private static bool ParsePredicate(Tokenizer source, IList<CompiledXPathNode> dependencies) { source.Consume(); if (!ParseAndExpression(source, dependencies)) return false; if (!Consume(source, Token.PredicateEnd)) return false; return true; } private static bool ParseAndExpression(Tokenizer source, IList<CompiledXPathNode> dependencies) { while (true) { if (!ParseExpression(source, out CompiledXPathNode node)) return false; dependencies.Add(node); if (source.Token != 0 || source.Text != "and") break; source.Consume(); } return true; } private static bool ParseExpression(Tokenizer source, out CompiledXPathNode node) { if (source.Token != 0 && source.Token != Token.AttributeStart) return ParseRightToLeftExpression(source, out node); return ParseLeftToRightExpression(source, out node); } private static bool ParseLeftToRightExpression(Tokenizer source, out CompiledXPathNode node) { if (!ParsePathExpression(source, out node)) return false; if (!Consume(source, Token.EqualsOperator)) return true; if (!ParseValue(source, out XPathExpression value)) return false; GetLastNode(node).Value = value; return true; } private static bool ParseRightToLeftExpression(Tokenizer source, out CompiledXPathNode node) { if (!ParseValue(source, out XPathExpression value)) return Try.Failure(out node); if (!Consume(source, Token.EqualsOperator)) return Try.Failure(out node); if (!ParsePathExpression(source, out node)) return false; GetLastNode(node).Value = value; return true; } private static bool ParsePathExpression(Tokenizer source, out CompiledXPathNode node) { if (!ParseNode(source, out node)) return false; CompiledXPathNode compiledXPathNode = node; while (true) { if (!Consume(source, Token.StepSeparator)) return true; if (compiledXPathNode.IsAttribute) return false; if (!ParseNode(source, out CompiledXPathNode node2)) break; compiledXPathNode.NextNode = node2; node2.PreviousNode = compiledXPathNode; compiledXPathNode = node2; } return false; } private static bool ParseNode(Tokenizer source, out CompiledXPathNode node) { node = new CompiledXPathNode(); return ParseNodeCore(source, node); } 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 CompiledXPathNode GetLastNode(CompiledXPathNode node) { while (true) { CompiledXPathNode nextNode = node.NextNode; if (nextNode == null) break; node = nextNode; } return node; } } }