Files
rippled/src/libxrpl/json/json_writer.cpp
2026-03-24 15:42:12 +00:00

731 lines
17 KiB
C++

#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <ios>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
namespace Json {
static bool
isControlCharacter(char ch)
{
return ch > 0 && ch <= 0x1F;
}
static bool
containsControlCharacter(char const* str)
{
while (*str != 0)
{
if (isControlCharacter(*(str++)))
return true;
}
return false;
}
static void
uintToString(unsigned int value, char*& current)
{
*--current = 0;
do
{
*--current = (value % 10) + '0';
value /= 10;
} while (value != 0);
}
std::string
valueToString(Int value)
{
char buffer[32];
char* current = buffer + sizeof(buffer);
bool isNegative = value < 0;
if (isNegative)
value = -value;
uintToString(UInt(value), current);
if (isNegative)
*--current = '-';
XRPL_ASSERT(current >= buffer, "Json::valueToString(Int) : buffer check");
return current;
}
std::string
valueToString(UInt value)
{
char buffer[32];
char* current = buffer + sizeof(buffer);
uintToString(value, current);
XRPL_ASSERT(current >= buffer, "Json::valueToString(UInt) : buffer check");
return current;
}
std::string
valueToString(double value)
{
// Allocate a buffer that is more than large enough to store the 16 digits
// of precision requested below.
char buffer[32];
// Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distinguish the
// concepts of reals and integers.
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005
// to avoid warning.
sprintf_s(buffer, sizeof(buffer), "%.16g", value);
#else
snprintf(buffer, sizeof(buffer), "%.16g", value);
#endif
return buffer;
}
std::string
valueToString(bool value)
{
return value ? "true" : "false";
}
std::string
valueToQuotedString(char const* value)
{
// Not sure how to handle unicode...
if (strpbrk(value, "\"\\\b\f\n\r\t") == nullptr && !containsControlCharacter(value))
return std::string("\"") + value + "\"";
// We have to walk value and escape any special characters.
// Appending to std::string is not efficient, but this should be rare.
// (Note: forward slashes are *not* rare, but I am not escaping them.)
unsigned maxsize = (strlen(value) * 2) + 3; // all-escaped+quotes+NULL
std::string result;
result.reserve(maxsize); // to avoid lots of mallocs
result += "\"";
for (char const* c = value; *c != 0; ++c)
{
switch (*c)
{
case '\"':
result += "\\\"";
break;
case '\\':
result += "\\\\";
break;
case '\b':
result += "\\b";
break;
case '\f':
result += "\\f";
break;
case '\n':
result += "\\n";
break;
case '\r':
result += "\\r";
break;
case '\t':
result += "\\t";
break;
// case '/':
// Even though \/ is considered a legal escape in JSON, a bare
// slash is also legal, so I see no reason to escape it.
// (I hope I am not misunderstanding something.
// blep notes: actually escaping \/ may be useful in javascript
// to avoid </ sequence. Should add a flag to allow this
// compatibility mode and prevent this sequence from occurring.
default:
if (isControlCharacter(*c))
{
std::ostringstream oss;
oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4)
<< static_cast<int>(*c);
result += oss.str();
}
else
{
result += *c;
}
break;
}
}
result += "\"";
return result;
}
// Class FastWriter
// //////////////////////////////////////////////////////////////////
std::string
FastWriter::write(Value const& root)
{
document_ = "";
writeValue(root);
return std::move(document_);
}
void
FastWriter::writeValue(Value const& value)
{
switch (value.type())
{
case nullValue:
document_ += "null";
break;
case intValue:
document_ += valueToString(value.asInt());
break;
case uintValue:
document_ += valueToString(value.asUInt());
break;
case realValue:
document_ += valueToString(value.asDouble());
break;
case stringValue:
document_ += valueToQuotedString(value.asCString());
break;
case booleanValue:
document_ += valueToString(value.asBool());
break;
case arrayValue: {
document_ += "[";
int size = value.size();
for (int index = 0; index < size; ++index)
{
if (index > 0)
document_ += ",";
writeValue(value[index]);
}
document_ += "]";
}
break;
case objectValue: {
Value::Members members(value.getMemberNames());
document_ += "{";
for (Value::Members::iterator it = members.begin(); it != members.end(); ++it)
{
std::string const& name = *it;
if (it != members.begin())
document_ += ",";
document_ += valueToQuotedString(name.c_str());
document_ += ":";
writeValue(value[name]);
}
document_ += "}";
}
break;
}
}
// Class StyledWriter
// //////////////////////////////////////////////////////////////////
StyledWriter::StyledWriter() : rightMargin_(74), indentSize_(3)
{
}
std::string
StyledWriter::write(Value const& root)
{
document_ = "";
addChildValues_ = false;
indentString_ = "";
writeValue(root);
document_ += "\n";
return document_;
}
void
StyledWriter::writeValue(Value const& value)
{
switch (value.type())
{
case nullValue:
pushValue("null");
break;
case intValue:
pushValue(valueToString(value.asInt()));
break;
case uintValue:
pushValue(valueToString(value.asUInt()));
break;
case realValue:
pushValue(valueToString(value.asDouble()));
break;
case stringValue:
pushValue(valueToQuotedString(value.asCString()));
break;
case booleanValue:
pushValue(valueToString(value.asBool()));
break;
case arrayValue:
writeArrayValue(value);
break;
case objectValue: {
Value::Members members(value.getMemberNames());
if (members.empty())
{
pushValue("{}");
}
else
{
writeWithIndent("{");
indent();
Value::Members::iterator it = members.begin();
while (true)
{
std::string const& name = *it;
Value const& childValue = value[name];
writeWithIndent(valueToQuotedString(name.c_str()));
document_ += " : ";
writeValue(childValue);
if (++it; it == members.end())
break;
document_ += ",";
}
unindent();
writeWithIndent("}");
}
}
break;
}
}
void
StyledWriter::writeArrayValue(Value const& value)
{
unsigned size = value.size();
if (size == 0)
{
pushValue("[]");
}
else
{
bool isArrayMultiLine = isMultilineArray(value);
if (isArrayMultiLine)
{
writeWithIndent("[");
indent();
bool hasChildValue = !childValues_.empty();
unsigned index = 0;
while (true)
{
Value const& childValue = value[index];
if (hasChildValue)
{
writeWithIndent(childValues_[index]);
}
else
{
writeIndent();
writeValue(childValue);
}
if (++index == size)
break;
document_ += ",";
}
unindent();
writeWithIndent("]");
}
else // output on a single line
{
XRPL_ASSERT(
childValues_.size() == size,
"Json::StyledWriter::writeArrayValue : child size match");
document_ += "[ ";
for (unsigned index = 0; index < size; ++index)
{
if (index > 0)
document_ += ", ";
document_ += childValues_[index];
}
document_ += " ]";
}
}
}
bool
StyledWriter::isMultilineArray(Value const& value)
{
int size = value.size();
bool isMultiLine = size * 3 >= rightMargin_;
childValues_.clear();
for (int index = 0; index < size && !isMultiLine; ++index)
{
Value const& childValue = value[index];
isMultiLine = isMultiLine ||
((childValue.isArray() || childValue.isObject()) && childValue.size() > 0);
}
if (!isMultiLine) // check if line length > max line length
{
childValues_.reserve(size);
addChildValues_ = true;
int lineLength = 4 + ((size - 1) * 2); // '[ ' + ', '*n + ' ]'
for (int index = 0; index < size; ++index)
{
writeValue(value[index]);
lineLength += int(childValues_[index].length());
}
addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= rightMargin_;
}
return isMultiLine;
}
void
StyledWriter::pushValue(std::string const& value)
{
if (addChildValues_)
{
childValues_.push_back(value);
}
else
{
document_ += value;
}
}
void
StyledWriter::writeIndent()
{
if (!document_.empty())
{
char last = document_[document_.length() - 1];
if (last == ' ') // already indented
return;
if (last != '\n') // Comments may add new-line
document_ += '\n';
}
document_ += indentString_;
}
void
StyledWriter::writeWithIndent(std::string const& value)
{
writeIndent();
document_ += value;
}
void
StyledWriter::indent()
{
indentString_ += std::string(indentSize_, ' ');
}
void
StyledWriter::unindent()
{
XRPL_ASSERT(
int(indentString_.size()) >= indentSize_,
"Json::StyledWriter::unindent : maximum indent size");
indentString_.resize(indentString_.size() - indentSize_);
}
// Class StyledStreamWriter
// //////////////////////////////////////////////////////////////////
StyledStreamWriter::StyledStreamWriter(std::string indentation)
: document_(nullptr), rightMargin_(74), indentation_(indentation)
{
}
void
StyledStreamWriter::write(std::ostream& out, Value const& root)
{
document_ = &out;
addChildValues_ = false;
indentString_ = "";
writeValue(root);
*document_ << "\n";
document_ = nullptr; // Forget the stream, for safety.
}
void
StyledStreamWriter::writeValue(Value const& value)
{
switch (value.type())
{
case nullValue:
pushValue("null");
break;
case intValue:
pushValue(valueToString(value.asInt()));
break;
case uintValue:
pushValue(valueToString(value.asUInt()));
break;
case realValue:
pushValue(valueToString(value.asDouble()));
break;
case stringValue:
pushValue(valueToQuotedString(value.asCString()));
break;
case booleanValue:
pushValue(valueToString(value.asBool()));
break;
case arrayValue:
writeArrayValue(value);
break;
case objectValue: {
Value::Members members(value.getMemberNames());
if (members.empty())
{
pushValue("{}");
}
else
{
writeWithIndent("{");
indent();
Value::Members::iterator it = members.begin();
while (true)
{
std::string const& name = *it;
Value const& childValue = value[name];
writeWithIndent(valueToQuotedString(name.c_str()));
*document_ << " : ";
writeValue(childValue);
if (++it == members.end())
break;
*document_ << ",";
}
unindent();
writeWithIndent("}");
}
}
break;
}
}
void
StyledStreamWriter::writeArrayValue(Value const& value)
{
unsigned size = value.size();
if (size == 0)
{
pushValue("[]");
}
else
{
bool isArrayMultiLine = isMultilineArray(value);
if (isArrayMultiLine)
{
writeWithIndent("[");
indent();
bool hasChildValue = !childValues_.empty();
unsigned index = 0;
while (true)
{
Value const& childValue = value[index];
if (hasChildValue)
{
writeWithIndent(childValues_[index]);
}
else
{
writeIndent();
writeValue(childValue);
}
if (++index == size)
break;
*document_ << ",";
}
unindent();
writeWithIndent("]");
}
else // output on a single line
{
XRPL_ASSERT(
childValues_.size() == size,
"Json::StyledStreamWriter::writeArrayValue : child size match");
*document_ << "[ ";
for (unsigned index = 0; index < size; ++index)
{
if (index > 0)
*document_ << ", ";
*document_ << childValues_[index];
}
*document_ << " ]";
}
}
}
bool
StyledStreamWriter::isMultilineArray(Value const& value)
{
int size = value.size();
bool isMultiLine = size * 3 >= rightMargin_;
childValues_.clear();
for (int index = 0; index < size && !isMultiLine; ++index)
{
Value const& childValue = value[index];
isMultiLine = isMultiLine ||
((childValue.isArray() || childValue.isObject()) && childValue.size() > 0);
}
if (!isMultiLine) // check if line length > max line length
{
childValues_.reserve(size);
addChildValues_ = true;
int lineLength = 4 + ((size - 1) * 2); // '[ ' + ', '*n + ' ]'
for (int index = 0; index < size; ++index)
{
writeValue(value[index]);
lineLength += int(childValues_[index].length());
}
addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= rightMargin_;
}
return isMultiLine;
}
void
StyledStreamWriter::pushValue(std::string const& value)
{
if (addChildValues_)
{
childValues_.push_back(value);
}
else
{
*document_ << value;
}
}
void
StyledStreamWriter::writeIndent()
{
/*
Some comments in this method would have been nice. ;-)
if ( !document_.empty() )
{
char last = document_[document_.length()-1];
if ( last == ' ' ) // already indented
return;
if ( last != '\n' ) // Comments may add new-line
*document_ << '\n';
}
*/
*document_ << '\n' << indentString_;
}
void
StyledStreamWriter::writeWithIndent(std::string const& value)
{
writeIndent();
*document_ << value;
}
void
StyledStreamWriter::indent()
{
indentString_ += indentation_;
}
void
StyledStreamWriter::unindent()
{
XRPL_ASSERT(
indentString_.size() >= indentation_.size(),
"Json::StyledStreamWriter::unindent : maximum indent size");
indentString_.resize(indentString_.size() - indentation_.size());
}
std::ostream&
operator<<(std::ostream& sout, Value const& root)
{
Json::StyledStreamWriter writer;
writer.write(sout, root);
return sout;
}
} // namespace Json