private TextToken GetToken(ITokenMaker tokenMaker, string stringValue) { if ((state & State.StartOfDocument) == State.StartOfDocument) { var next = ReadNoisyContent(); if (next == null) { state = State.ExpectedEndOfDocument; return(tokenMaker.CreateValueToken(stringValue)); } else { reader.PushBack(next.Value); PushBack(TextToken.Name(stringValue)); state = State.ObjectBeforeColon; containerStack.Push(ContainerType.Object); return(TextToken.StartObject); } } if ((state & (State.ObjectStart | State.ObjectAfterComma | State.ObjectAfterProperty)) != 0) { state = State.ObjectBeforeColon; return(tokenMaker.CreateNameToken(stringValue)); } else { ValidateAndModifyStateForValue("Invalid state to read a double quote: "); return(tokenMaker.CreateValueToken(stringValue)); } }
/// <summary> /// Returns the next text token in the stream. An EndDocument token is returned to indicate the end of the stream, /// after which point <c>Next()</c> should not be called again. /// </summary> /// <remarks>This implementation provides single-token buffering, and calls <see cref="NextImpl"/> if there is no buffered token.</remarks> /// <returns>The next token in the stream. This is never null.</returns> /// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception> /// <exception cref="InvalidTextException">The input text does not comply with RFC 7159</exception> internal virtual TextToken Next() { TextToken tokenToReturn; if (bufferedToken != null) { tokenToReturn = bufferedToken; bufferedToken = null; if (bufferedToken2 != null) { bufferedToken = bufferedToken2; bufferedToken2 = null; } } else { tokenToReturn = NextImpl(); } if (tokenToReturn.Type == TokenType.StartObject) { ObjectDepth++; } else if (tokenToReturn.Type == TokenType.EndObject) { ObjectDepth--; } return(tokenToReturn); }
private TextToken ConsumeLiteral(string text, TextToken exptectedToken) { var matchCount = 1; var macthCountToBe = text.Length; StringBuilder sb = null; while (true) { char?next = reader.Read(); if (next == null || char.IsWhiteSpace(next.Value) || next.Value == ':') { if (next != null && next.Value == ':') { reader.PushBack(next.Value); } if (sb == null && matchCount < macthCountToBe) { return(GetToken(text.Substring(0, matchCount))); } break; } if (sb == null) // trying to match { if (matchCount < macthCountToBe) { if (next.Value == text[matchCount]) { matchCount++; continue; } } sb = new StringBuilder(); sb.Append(text.Substring(0, matchCount)); sb.Append(next.Value); } else // read normal string { sb.Append(next.Value); } } if (matchCount == macthCountToBe) { ModifyStateForValue(); return(exptectedToken); } return(GetToken(sb.ToString())); }
private TextToken GetValueString(string stringValue) { if ((state & (State.ObjectStart | State.ObjectAfterComma)) != 0) { state = State.ObjectBeforeColon; return(TextToken.Name(stringValue)); } else { ValidateAndModifyStateForValue("Invalid state to read a double quote: "); return(TextToken.Value(stringValue)); } }
public TextToken CreateValueToken(string text) { try { var num = double.Parse(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); return(TextToken.Value(num)); } catch (OverflowException) { throw new InvalidTextException("Unable parse the number: " + text); } }
// TODO: Why do we allow a different token to be pushed back? It might be better to always remember the previous // token returned, and allow a parameterless Rewind() method (which could only be called once, just like the current PushBack). internal void PushBack(TextToken token) { if (bufferedToken != null) { throw new InvalidOperationException("Can't push back twice"); } bufferedToken = token; if (token.Type == TokenType.StartObject) { ObjectDepth--; } else if (token.Type == TokenType.EndObject) { ObjectDepth++; } }
public TextToken CreateNameToken(string text) { return(TextToken.Name(text)); }
public TextToken CreateValueToken(string text) { return(TextToken.Value(text)); }
/// <remarks> /// This method essentially just loops through characters skipping whitespace, validating and /// changing state (e.g. from ObjectBeforeColon to ObjectAfterColon) /// until it reaches something which will be a genuine token (e.g. a start object, or a value) at which point /// it returns the token. Although the method is large, it would be relatively hard to break down further... most /// of it is the large switch statement, which sometimes returns and sometimes doesn't. /// </remarks> protected override TextToken NextImpl() { if (state == State.ReaderExhausted) { throw new InvalidOperationException("Next() called after end of document"); } while (true) { var next = reader.Read(); if (next == null) { //ValidateState(State.ExpectedEndOfDocument, "Unexpected end of document in state: "); //state = State.ReaderExhausted; return(TextToken.EndDocument); } if (state == State.StartOfDocument) { reader.PushBack(next.Value); next = ReadNoisyContent(); if (next.Value == '{') { state = State.ArrayStart; containerStack.Push(ContainerType.Array); return(TextToken.StartArray); } else { if (next.Value != '\"') { reader.PushBack(next.Value); } containerStack.Push(ContainerType.Object); var name = ReadName(); if (reader.LastChar == null) { // single value state = State.ExpectedEndOfDocument; if ("null".Equals(name, StringComparison.OrdinalIgnoreCase)) { return(TextToken.Null); } else { return(TextToken.Value(name)); } } PushBack(TextToken.Name(name)); state = State.ObjectStart; return(TextToken.StartObject); } } else if (state == State.ObjectAfterProperty) { if (char.IsWhiteSpace(next.Value)) { continue; } if (next.Value != '}') { reader.PushBack(next.Value); var name = ReadName(); state = State.ObjectAfterColon; return(TextToken.Name(name)); } } else if (state == State.ObjectStart) { next = ReadNoisyContent(); if (next.Value != '}') { reader.PushBack(next.Value); var name = ReadName(); state = State.ObjectAfterColon; return(TextToken.Name(name)); } } switch (next.Value) { // Skip whitespace between tokens case ' ': case '\t': case '\r': case '\n': break; case ':': ValidateState(State.ObjectBeforeColon, "Invalid state to read a colon: "); state = State.ObjectAfterColon; break; case ',': ValidateState(State.ObjectAfterProperty | State.ArrayAfterValue, "Invalid state to read a comma: "); state = state == State.ObjectAfterProperty ? State.ObjectAfterComma : State.ArrayAfterComma; break; case '\'': case '"': return(GetValueString(next.Value)); case '{': ValidateState(ValueStates, "Invalid state to read an open brace: "); state = State.ObjectStart; containerStack.Push(ContainerType.Object); return(TextToken.StartObject); case '}': ValidateState(State.ObjectAfterProperty | State.ObjectStart, "Invalid state to read a close brace: "); PopContainer(); return(TextToken.EndObject); case '[': ValidateState(ValueStates, "Invalid state to read an open square bracket: "); state = State.ArrayStart; containerStack.Push(ContainerType.Array); return(TextToken.StartArray); case ']': ValidateState(State.ArrayAfterValue | State.ArrayStart, "Invalid state to read a close square bracket: "); PopContainer(); return(TextToken.EndArray); case 'n': // Start of null ConsumeLiteral("null"); ValidateAndModifyStateForValue("Invalid state to read a null literal: "); return(TextToken.Null); case 't': // Start of true ConsumeLiteral("true"); ValidateAndModifyStateForValue("Invalid state to read a true literal: "); return(TextToken.True); case 'f': // Start of false ConsumeLiteral("false"); ValidateAndModifyStateForValue("Invalid state to read a false literal: "); return(TextToken.False); case '-': // Start of a number case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': double number = ReadNumber(next.Value); ValidateAndModifyStateForValue("Invalid state to read a number token: "); return(TextToken.Value(number)); default: reader.PushBack(next.Value); return(GetValueString(ReadName())); } } }