Expose getLocationLineAndColumn() as a public API

Allows better use of the offset data returned by getStructuredErrors()
This commit is contained in:
Martin Duffy 2022-07-25 11:42:07 -04:00
parent 42e892d96e
commit 459332db7c
4 changed files with 69 additions and 24 deletions

View File

@ -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)

View File

@ -437,7 +437,7 @@ public:
/// \post type() is arrayValue
void resize(ArrayIndex newSize);
//@{
///@{
/// Access an array element (zero based index). If the array contains less
/// than index element, then null value are inserted in the array so that
/// its size is index+1.
@ -445,15 +445,15 @@ public:
/// this from the operator[] which takes a string.)
Value& operator[](ArrayIndex index);
Value& operator[](int index);
//@}
///@}
//@{
///@{
/// Access an array element (zero based index).
/// (You may need to say 'value[0u]' to get your compiler to distinguish
/// this from the operator[] which takes a string.)
const Value& operator[](ArrayIndex index) const;
const Value& operator[](int index) const;
//@}
///@}
/// If the array contains at least index+1 elements, returns the element
/// value, otherwise returns defaultValue.

View File

@ -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 {
@ -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;

View File

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