C#Text Parser就像GraphQL一样

时间:2018-05-15 21:41:05

标签: c# string parsing

我试图在C#中分割出以下格式,并且在循环中陷入困境。

{
  books{
    isbn
    name
    author{
      id
      name
      birthdate
    }
  }
}

我本质上希望能够识别像书籍和作者这样的关键对象,其中isbn,name,id等是该对象的属性,同时也知道在这种情况下,作者链接到该书的可能性是像出版商这样的东西也可以作为书籍的子对象。

有关如何做到这一点的任何线索。我一直在完成所有循环并且无法正确分组。

1 个答案:

答案 0 :(得分:2)

有很多方法可以处理解析。你可以使用词法分析器,你可以使用解析器组合器。

在这种情况下,我会写一个简单的递归下降解析器。 你基本上必须跟踪打开和关闭大括号,并在你去的时候创建一个对象树。

通过调用GraphObject.Read(inputString)解析字符串。 您可以在this dotnetfiddle尝试此代码。

// A object can have to kinds of properties, simple values or sub-objects
public enum GraphElementType {
    @Object,    
    Value
}

// To make it easy to keep all values and objects in order, we define a single base class for both
// We define all elements to have a name, and set a static name for the root element
public abstract class GraphElement
{
    public static readonly string ROOT_NAME = "<ROOT>";

    public GraphElementType ElementType { get; }
    public string Name {get;}

    protected GraphElement(GraphElementType type, string name) {
        ElementType = type;
        Name = name;
    }
}

public class GraphValue : GraphElement
{
    public GraphValue(string name) : base(GraphElementType.Value, name) { }
}

public class GraphObject : GraphElement {   
    public List<GraphElement> Properties {get;} = new List<GraphElement>();

    public GraphObject(string name) : base(GraphElementType.Object, name) { }


    public static GraphObject Read(string graphString)
    {
        return Read(new StringReader(graphString));
    }

    public static GraphObject Read(TextReader reader)
    {
        var root = Read(GraphElement.ROOT_NAME, reader);        
        if(ConsumeWhitespaceAndPeek(reader) != -1) {
            throw new ApplicationException("Unexpected content after root object");
        }       
        return root;
    }

    // Read an object with the given name from the given reader.
    // The reader must be positioned at the opening brace of the object.
    protected static GraphObject Read(string name, TextReader reader)
    {
        // Consume opening '{'
        if (reader.Read() != '{') throw new ApplicationException("Invalid object start");

        var o = new GraphObject(name);      
        while(true)
        {
            var next = ConsumeWhitespaceAndPeek(reader);
            if (next == -1) throw new ApplicationException("Unexpected end of input, unclosed object");

            if (next == '}')  {
                reader.Read(); // consume closing '}'
                return o;
            }

            var str = ConsumeUntilWhitespaceOrBrace(reader);
            next = ConsumeWhitespaceAndPeek(reader);         

            if (str.Length > 0 && next  == '{')
            {
                o.Properties.Add(Read(str, reader));
            }
            else if (str.Length > 0)
            {
                o.Properties.Add(new GraphValue(str));
            }
        }
    }
    // Helper method: Collect all non-whitespace, non-brace characters into a string
    private static string ConsumeUntilWhitespaceOrBrace(TextReader reader) {
        var b = new StringBuilder();

        var next = reader.Peek();
        while (next != -1 && !Char.IsWhiteSpace((char)next) && next != '}' && next != '{') {
            b.Append((char)next);
            reader.Read();
            next = reader.Peek();
        }

        return b.ToString();
    }

    // Helper method: Advance reader past any whitespace
    private static int ConsumeWhitespaceAndPeek(TextReader reader) {
        var next = reader.Peek();
        while(next != -1 && Char.IsWhiteSpace((char)next)) {
            reader.Read();
            next = reader.Peek();
        }

        return next;
    }
}