public void StringValue_Update_SourceIsChangedProperly() { TrackedStringManager source = new TrackedStringManager("test 123"); TrackedString number = source.Track(5, 3); Assert.AreEqual("123", number.StringValue); number.StringValue = "456"; Assert.AreEqual("test 456", source.Text); }
public static void AssertIsValid(this TrackedStringManager mgr) { int last = -1; foreach (TrackedString ts in mgr) { //Assert.IsTrue(ts.OffsetInSource >= last, "Offset was less last! Source is out of order."); last = ts.OffsetInSource; string stringValueAfterOffsetChange = mgr.Text.Substring(ts.OffsetInSource, ts.StringValue.Length); Assert.AreEqual(ts.StringValue, stringValueAfterOffsetChange); } }
public TrackedString CreateTS(TrackedStringManager tracker) { string target = tracker.Text.SubstringWithIndices(this.StartIndex, this.EndIndex); int startOffset = 0; // If it's unquoted text, then we need to trim if (!this.IsInsideQuotes) { target = JsonHelper.TrimUnquotedValue(target, out startOffset); } return(tracker.Track(this.StartIndex + startOffset, target.Length)); }
/// <summary> /// Base class constructor /// </summary> internal JsonValue(TrackedStringManager source) : base(source) { // Track value types manually with our source. This is done because values aren't parsed, // they're created in documents or arrays and so don't have a chance to know if they need // to be tracked or not (done at the end of parse in document and array). // // If we are in placeholder mode, then we are *not* adding it yet as we don't know the whole // range of the json value until we're done building our placeholders, but we need to allocate // it in order to process it. if (this.IsValue && !this.Source.IsInPlaceholderMode) { this.Source.Track(this); } }
private static Json RunParse(TrackedStringManager tracker, ParserRules rules) { Json output = new Json(tracker); try { if (tracker.Text == null) { output.AddError("Null source text provided!"); tracker.HasChanges = false; return(output); } JsonTextIndexer indexer = new JsonTextIndexer(tracker.Text, rules); // TODO: Handle case where root is document without braces( ie "item : value, item2 : value2") int indexOfFirstOpenBracket = indexer.NextAny(0, '{', '['); // ============ No Brackets =========== if (indexOfFirstOpenBracket == -1) { output.Data = new JsonValue(output.TextTracking, 0, indexer.NextAny(0, '}', ']'), false); tracker.HasChanges = false; return(output); } // ============ Has Brackets =========== char nextBracket = output.TextTracking.Text[indexOfFirstOpenBracket]; if (nextBracket == '{') { output.Data = new JsonDocument(indexer, output.TextTracking, indexOfFirstOpenBracket, out int endIndex); tracker.HasChanges = false; return(output); } else // if(nextBracket == '[') { output.Data = new JsonArray(indexer, output.TextTracking, indexOfFirstOpenBracket, out int endIndex); tracker.HasChanges = false; return(output); } } catch (Exception exc) { output.AddError(exc.ToString()); } return(output); }
// ===================[ Construction & Parsing ]=========================== /// <summary> /// Constructor for parsing /// </summary> internal JsonArray(JsonTextIndexer indexer, TrackedStringManager source, int startIndex, out int endIndex) : base(source) { endIndex = -1; int nextEval = indexer.NextAny(startIndex + 1); int lastStart = startIndex + 1; int quoteStart = -1; bool lastWasUseful; while (nextEval != -1) { lastWasUseful = true; switch (source.Text[nextEval]) { case ']': if (quoteStart != -1) { break; } // There can be an unquoted array item inbetween the last comma'd item and the end // bracket. To make sure we handle that case, but don't add empty elements, we're // going to check it's an empty string before we add it. // // While empty keys and values will be supported (keys as long as they are unique) // they must be quoted so we can tell when the item is supposed to end. if (lastStart != nextEval) { this.Add(new JsonValue(source, lastStart, nextEval - 1, false)); } endIndex = nextEval; break; case '[': { int endOfArr; this.Add(new JsonArray(indexer, source, nextEval, out endOfArr)); if (endOfArr == -1) { throw new FormatException($"Invalid json format provided, issue starts with array that begins at text index {nextEval}"); } int peekAheadInd = indexer.NextAny(endOfArr + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } else { nextEval = endOfArr; } } break; case '{': { if (quoteStart != -1) { break; } int endOfDoc; this.Add(new JsonDocument(indexer, source, nextEval, out endOfDoc)); if (endOfDoc == -1) { throw new FormatException($"Invalid json format provided, issue starts with document that begins at text index {nextEval}"); } int peekAheadInd = indexer.NextAny(endOfDoc + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } else { nextEval = endOfDoc; } } break; case ',': if (quoteStart != -1) { break; } // We're safe to assume we can add this here because commas after docs are covered above this.Add(new JsonValue(source, lastStart, nextEval - 1, false)); break; case '\"': if (quoteStart != -1) { this.Add(new JsonValue(source, quoteStart, nextEval - 1, true)); quoteStart = -1; int peekAheadInd = indexer.NextAny(nextEval + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } } else { quoteStart = nextEval + 1; } break; default: lastWasUseful = false; break; } if (endIndex != -1) { break; } if (lastWasUseful) { lastStart = nextEval + 1; } nextEval = indexer.NextAny(nextEval + 1); } this.ResetStringValue(startIndex, endIndex); this.Source.Track(this); }
/// <summary> /// Constructor for building /// </summary> internal JsonArray(TrackedStringManager source, int startIndex) : base(source) { this.OffsetInSource = startIndex; }
/// <summary> /// Parses the text from the passed in <see cref="TrackedStringManager"/>, and returns a non-<c>null</c> <see cref="Json"/> instance. /// </summary> /// <param name="jsonText">Some json text.</param> /// <param name="rules">Parser rules</param> /// <returns>A non-<c>null</c> <see cref="Json"/> instance</returns> public static Json ParseText(TrackedStringManager source, ParserRules rules = null) { return(RunParse(source, rules)); }
internal Json(TrackedStringManager tracker) { this.TextTracking = tracker; }
// =========================[ Constructor & Parsing ]=============================== /// <summary> /// Constructor for parsing /// </summary> internal JsonDocument(JsonTextIndexer textIndexer, TrackedStringManager source, int startIndex, out int endIndex) : base(source) { endIndex = -1; int nextEval = textIndexer.NextAny(startIndex + 1); JsonHelper.IndexTrackingHelper keyIndexTracker = new JsonHelper.IndexTrackingHelper(); int lastStart = startIndex + 1; int insideQuoteStart = -1; bool lastWasUseful; while (nextEval != -1) { lastWasUseful = true; switch (source.Text[nextEval]) { case '}': if (insideQuoteStart != -1) { break; } if (keyIndexTracker) { this.Add(keyIndexTracker.CreateTS(source), new JsonValue(source, lastStart, nextEval - 1, false)); } endIndex = nextEval; break; case '{': { if (insideQuoteStart != -1) { break; } int endOfDoc; if (!keyIndexTracker) { throw new FormatException("Can't read json format - error found around offset: " + nextEval.ToString()); } this.Add(keyIndexTracker.CreateTS(source), new JsonDocument(textIndexer, source, nextEval, out endOfDoc)); if (endOfDoc == -1) { throw new FormatException($"Invalid json format provided, issue starts with document that begins at text index {nextEval}"); } nextEval = endOfDoc; keyIndexTracker.Reset(); } break; case '[': { if (insideQuoteStart != -1) { break; } if (!keyIndexTracker) { throw new FormatException("Can't read json format - attempted to add empty string key"); } this.Add(keyIndexTracker.CreateTS(source), new JsonArray(textIndexer, source, nextEval, out int endOfArr)); if (endOfArr == -1) { throw new FormatException($"Invalid json format provided, issue starts with array that begins at text index {nextEval}"); } nextEval = endOfArr; keyIndexTracker.Reset(); } break; case ':': if (insideQuoteStart != -1) { break; } keyIndexTracker.StartIndex = lastStart; keyIndexTracker.EndIndex = nextEval - 1; break; case '\"': // Start quote if (insideQuoteStart == -1) { insideQuoteStart = nextEval + 1; } // End quote else { // Peek ahead so we can decide if it's a key or a value // If the next thing is a colon, then it's a key, otherwise it's a value int peekAheadInd = textIndexer.NextAny(nextEval + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ':') { keyIndexTracker.StartIndex = insideQuoteStart; keyIndexTracker.EndIndex = nextEval - 1; // One before the quote that was just found keyIndexTracker.IsInsideQuotes = true; nextEval = peekAheadInd; } else { if (!keyIndexTracker) { string quote = source.Text.Substring(insideQuoteStart, nextEval - 1); throw new FormatException("Json parsing failed due to incorrect string formatting around string: " + quote); } this.Add(keyIndexTracker.CreateTS(source), new JsonValue(source, insideQuoteStart, nextEval - 1, true)); keyIndexTracker.Reset(); } insideQuoteStart = -1; } break; case ',': if (insideQuoteStart != -1) { break; } if (keyIndexTracker) { var endValue = new JsonValue(source, lastStart, nextEval - 1, false); if (endValue.StringValue.Length != 0) { this.Add(keyIndexTracker.CreateTS(source), endValue); keyIndexTracker.Reset(); } } break; default: lastWasUseful = false; break; } if (endIndex != -1) { break; } if (lastWasUseful) { lastStart = nextEval + 1; } nextEval = textIndexer.NextAny(nextEval + 1); } if (nextEval == -1) { throw new FormatException("Json was improperly formatted"); } this.ResetStringValue(startIndex, endIndex); this.Source.Track(this); }
/// <summary> /// Constructor (for building) /// </summary> internal JsonValue(TrackedStringManager source, int start, string value) : this(source) { this.SetValueInternal(value); this.OffsetInSource = start; }
/// <summary> /// Constructor (for parsing) /// </summary> internal JsonValue(TrackedStringManager source, int start, int end, bool isInsideQuote) : this(source) { this.ResetStringValue(start, end, isInsideQuote); }