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;
}
}
}