diff --git a/include/json/reader.h b/include/json/reader.h index 46975d86f..67549a1fe 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -395,6 +395,14 @@ bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, */ JSON_API IStream& operator>>(IStream&, Value&); +/** Line and column within a document (1-based). */ +struct DocumentLocation { + int line; + int column; +}; + +/** Return the location of a character within a string. */ +DocumentLocation JSON_API locateInDocument(char const* beginDoc, size_t offset); } // namespace Json #pragma pack(pop) diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 1ac5e81ab..26cf82957 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -608,7 +608,7 @@ bool Reader::decodeDouble(Token& token, Value& decoded) { value = -std::numeric_limits::infinity(); else if (!std::isinf(value)) return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -767,24 +767,9 @@ Reader::Char Reader::getNextChar() { void Reader::getLocationLineAndColumn(Location location, int& line, int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; + auto loc = locateInDocument(document_.data(), location - document_.data()); + line = loc.line; + column = loc.column; } String Reader::getLocationLineAndColumn(Location location) const { @@ -1660,7 +1645,7 @@ bool OurReader::decodeDouble(Token& token, Value& decoded) { value = -std::numeric_limits::infinity(); else if (!std::isinf(value)) return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -1991,6 +1976,28 @@ bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, return reader->parse(begin, end, root, errs); } +DocumentLocation locateInDocument(char const* beginDoc, size_t offset) { + int line = 1; + int col = 1; + const char* last = beginDoc + offset; + for (; beginDoc != last; ++beginDoc) { + switch (*beginDoc) { + case '\r': + if (beginDoc + 1 != last && beginDoc[1] == '\n') + continue; // consume CRLF as a single token. + [[fallthrough]]; // CR without a following LF is same as LF + case '\n': + col = 1; + ++line; + break; + default: + ++col; + break; + } + } + return {line, col}; +} + IStream& operator>>(IStream& sin, Value& root) { CharReaderBuilder b; String errs; diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index d0f5364ac..aaac6d06e 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -3903,6 +3903,36 @@ JSONTEST_FIXTURE_LOCAL(FuzzTest, fuzzDoesntCrash) { example.size())); } +struct LocateInDocumentTest : JsonTest::TestCase { + void testLocateInDocument(const char* doc, size_t location, int row, + int column) { + Json::DocumentLocation actualLocation = + Json::locateInDocument(doc, location); + JSONTEST_ASSERT_EQUAL(row, actualLocation.line); + JSONTEST_ASSERT_EQUAL(column, actualLocation.column); + } +}; + +JSONTEST_FIXTURE_LOCAL(LocateInDocumentTest, locateInDocTest) { + const std::string example1 = "line 1\nline 2\r\nline 3\rline 4"; + const char* example2 = "\nline 2\r\r\n\nline 5\0 \n\rline 7\r\n"; + struct TestSpec { + const char* doc; + size_t offset; + int line; + int column; + }; + const TestSpec specs[] = { + {example1.data(), example1.find("line 1"), 1, 1}, + {example1.data(), example1.find("e 4"), 4, 4}, + // string terminates at \0, can't use find() + {example2, 21, 7, 1}, + }; + for (const auto& spec : specs) { + testLocateInDocument(spec.doc, spec.offset, spec.line, spec.column); + } +} + int main(int argc, const char* argv[]) { JsonTest::Runner runner;