From c8bed6445b244a5d9021dbd9a2ff19d80c03917b Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Mon, 15 Jun 2020 20:32:25 +0200 Subject: new json writer for LOK we shave about 3 memory copies off in the process, and make the class play nicely with our string types. Change-Id: I1f614fb35b1de499ac99e3b33ac638ad81054bed Reviewed-on: https://gerrit.libreoffice.org/c/core/+/96393 Tested-by: Noel Grandin Reviewed-by: Noel Grandin --- desktop/source/lib/init.cxx | 31 ++--- include/tools/json_writer.hxx | 86 ++++++++++++ include/vcl/ITiledRenderable.hxx | 4 +- sc/inc/chgtrack.hxx | 3 +- sc/inc/docuno.hxx | 2 +- sc/source/core/tool/chgtrack.cxx | 30 ++-- sc/source/ui/unoobj/docuno.cxx | 8 +- sw/inc/unotxdoc.hxx | 2 +- sw/source/uibase/uno/unotxdoc.cxx | 36 ++--- tools/CppunitTest_tools_test.mk | 1 + tools/Library_tl.mk | 1 + tools/qa/cppunit/test_json_writer.cxx | 57 ++++++++ tools/source/misc/json_writer.cxx | 253 ++++++++++++++++++++++++++++++++++ 13 files changed, 442 insertions(+), 72 deletions(-) create mode 100644 include/tools/json_writer.hxx create mode 100644 tools/qa/cppunit/test_json_writer.cxx create mode 100644 tools/source/misc/json_writer.cxx diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 8b3ba76bfaf9..2843c0f5c250 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -118,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -4658,47 +4659,41 @@ static char* getTrackedChanges(LibreOfficeKitDocument* pThis) LibLODocument_Impl* pDocument = static_cast(pThis); uno::Reference xRedlinesSupplier(pDocument->mxComponent, uno::UNO_QUERY); - std::stringstream aStream; + tools::JsonWriter aJson; // We want positions of the track changes also which is not possible from // UNO. Enable positioning information for text documents only for now, so // construct the tracked changes JSON from inside the sw/, not here using UNO if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT && xRedlinesSupplier.is()) { + auto redlinesNode = aJson.startNode("redlines"); uno::Reference xRedlines = xRedlinesSupplier->getRedlines()->createEnumeration(); - boost::property_tree::ptree aRedlines; for (size_t nIndex = 0; xRedlines->hasMoreElements(); ++nIndex) { uno::Reference xRedline(xRedlines->nextElement(), uno::UNO_QUERY); - boost::property_tree::ptree aRedline; - aRedline.put("index", nIndex); + auto redlineNode = aJson.startNode(""); + aJson.put("index", nIndex); OUString sAuthor; xRedline->getPropertyValue("RedlineAuthor") >>= sAuthor; - aRedline.put("author", sAuthor.toUtf8().getStr()); + aJson.put("author", sAuthor); OUString sType; xRedline->getPropertyValue("RedlineType") >>= sType; - aRedline.put("type", sType.toUtf8().getStr()); + aJson.put("type", sType); OUString sComment; xRedline->getPropertyValue("RedlineComment") >>= sComment; - aRedline.put("comment", sComment.toUtf8().getStr()); + aJson.put("comment", sComment); OUString sDescription; xRedline->getPropertyValue("RedlineDescription") >>= sDescription; - aRedline.put("description", sDescription.toUtf8().getStr()); + aJson.put("description", sDescription); util::DateTime aDateTime; xRedline->getPropertyValue("RedlineDateTime") >>= aDateTime; OUString sDateTime = utl::toISO8601(aDateTime); - aRedline.put("dateTime", sDateTime.toUtf8().getStr()); - - aRedlines.push_back(std::make_pair("", aRedline)); + aJson.put("dateTime", sDateTime); } - - boost::property_tree::ptree aTree; - aTree.add_child("redlines", aRedlines); - boost::property_tree::write_json(aStream, aTree); } else { @@ -4708,12 +4703,10 @@ static char* getTrackedChanges(LibreOfficeKitDocument* pThis) SetLastExceptionMsg("Document doesn't support tiled rendering"); return nullptr; } - OUString aTrackedChanges = pDoc->getTrackedChanges(); - aStream << aTrackedChanges.toUtf8(); + pDoc->getTrackedChanges(aJson); } - char* pJson = strdup(aStream.str().c_str()); - return pJson; + return aJson.extractData(); } diff --git a/include/tools/json_writer.hxx b/include/tools/json_writer.hxx new file mode 100644 index 000000000000..e811bb90e277 --- /dev/null +++ b/include/tools/json_writer.hxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include +#include +#include + +/** Simple JSON encoder designed specifically for LibreOfficeKit purposes. + * + * (1) Minimal allocations/re-allocations/copying + * (2) Small/simple JSON documents + * (3) ascii property names + */ +namespace tools +{ +class ScopedJsonWriterNode; + +class TOOLS_DLLPUBLIC JsonWriter +{ + friend class ScopedJsonWriterNode; + + int mSpaceAllocated; + std::unique_ptr maBuffer; + int mStartNodeCount; + char* mPos; + bool mbFirstFieldInNode; + +public: + JsonWriter(); + ~JsonWriter(); + + [[nodiscard]] ScopedJsonWriterNode startNode(const char*); + + void put(const char* pPropName, const OUString& rPropValue); + void put(const char* pPropName, const OString& rPropValue); + void put(const char* pPropName, const char* pPropVal); + void put(const char*, int); + + /** Hands ownership of the the underlying storage buffer to the caller, + * after this no more document modifications may be written. */ + char* extractData(); + +private: + void endNode(); + void addCommaBeforeField(); + + inline void ensureSpace(int noMoreBytesRequired) + { + int currentUsed = mPos - maBuffer.get(); + if (currentUsed + noMoreBytesRequired >= mSpaceAllocated) + { + auto newSize = std::max(mSpaceAllocated * 2, (currentUsed + noMoreBytesRequired) * 2); + auto pNew = new char[newSize]; + memcpy(pNew, maBuffer.get(), currentUsed); + maBuffer.reset(pNew); + mPos = maBuffer.get(); + } + } +}; + +/** + * Auto-closes the node. + */ +class ScopedJsonWriterNode +{ + friend class JsonWriter; + + JsonWriter& mrWriter; + + ScopedJsonWriterNode(JsonWriter& rWriter) + : mrWriter(rWriter) + { + } + +public: + ~ScopedJsonWriterNode() { mrWriter.endNode(); } +}; +}; +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/vcl/ITiledRenderable.hxx b/include/vcl/ITiledRenderable.hxx index 9e995216e573..e290c4960368 100644 --- a/include/vcl/ITiledRenderable.hxx +++ b/include/vcl/ITiledRenderable.hxx @@ -24,6 +24,7 @@ namespace com::sun::star::datatransfer::clipboard { class XClipboard; } namespace com::sun::star::uno { template class Reference; } namespace com::sun::star::uno { template class Sequence; } namespace vcl { class Window; } +namespace tools { class JsonWriter; } class VirtualDevice; @@ -230,9 +231,8 @@ public: /// Implementation for /// lok::Document::getCommandValues(".uno:AcceptTrackedChanges") when there /// is no matching UNO API. - virtual OUString getTrackedChanges() + virtual void getTrackedChanges(tools::JsonWriter&) { - return OUString(); } /// Implementation for diff --git a/sc/inc/chgtrack.hxx b/sc/inc/chgtrack.hxx index 544e2566fdba..146521538943 100644 --- a/sc/inc/chgtrack.hxx +++ b/sc/inc/chgtrack.hxx @@ -43,6 +43,7 @@ class ScFormulaCell; class ScChangeAction; class ScChangeTrack; class ScAppOptions; +namespace tools { class JsonWriter; } class ScActionColorChanger { @@ -1146,7 +1147,7 @@ public: SC_DLLPUBLIC ScChangeTrack* Clone( ScDocument* pDocument ) const; static void MergeActionState( ScChangeAction* pAct, const ScChangeAction* pOtherAct ); /// Get info about all ScChangeAction elements. - OUString GetChangeTrackInfo(); + void GetChangeTrackInfo(tools::JsonWriter&); }; #endif diff --git a/sc/inc/docuno.hxx b/sc/inc/docuno.hxx index 7c9c87a4f109..02bd4c04981d 100644 --- a/sc/inc/docuno.hxx +++ b/sc/inc/docuno.hxx @@ -375,7 +375,7 @@ public: virtual PointerStyle getPointer() override; /// @see vcl::ITiledRenderable::getTrackedChanges(). - OUString getTrackedChanges() override; + void getTrackedChanges(tools::JsonWriter&) override; /// @see vcl::ITiledRenderable::setClientVisibleArea(). virtual void setClientVisibleArea(const tools::Rectangle& rRectangle) override; diff --git a/sc/source/core/tool/chgtrack.cxx b/sc/source/core/tool/chgtrack.cxx index 3a45a5f3be83..f734224ce3fa 100644 --- a/sc/source/core/tool/chgtrack.cxx +++ b/sc/source/core/tool/chgtrack.cxx @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -4676,34 +4677,31 @@ void ScChangeTrack::MergeActionState( ScChangeAction* pAct, const ScChangeAction } /// Get info about a single ScChangeAction element. -static void lcl_getTrackedChange(ScDocument* pDoc, int nIndex, const ScChangeAction* pAction, boost::property_tree::ptree& rRedlines) +static void lcl_getTrackedChange(ScDocument* pDoc, int nIndex, const ScChangeAction* pAction, tools::JsonWriter& rRedlines) { if (pAction->GetType() == SC_CAT_CONTENT) { - boost::property_tree::ptree aRedline; - aRedline.put("index", nIndex); + auto redlinesNode = rRedlines.startNode(""); + rRedlines.put("index", nIndex); - const OUString& sAuthor = pAction->GetUser(); - aRedline.put("author", sAuthor.toUtf8().getStr()); + rRedlines.put("author", pAction->GetUser()); - aRedline.put("type", "Modify"); + rRedlines.put("type", "Modify"); - aRedline.put("comment", pAction->GetComment().toUtf8().getStr()); + rRedlines.put("comment", pAction->GetComment()); OUString aDescription; pAction->GetDescription(aDescription, pDoc, true); - aRedline.put("description", aDescription); + rRedlines.put("description", aDescription); OUString sDateTime = utl::toISO8601(pAction->GetDateTimeUTC().GetUNODateTime()); - aRedline.put("dateTime", sDateTime.toUtf8().getStr()); - - rRedlines.push_back(std::make_pair("", aRedline)); + rRedlines.put("dateTime", sDateTime); } } -OUString ScChangeTrack::GetChangeTrackInfo() +void ScChangeTrack::GetChangeTrackInfo(tools::JsonWriter& aRedlines) { - boost::property_tree::ptree aRedlines; + auto redlinesNode = aRedlines.startNode("redlines"); ScChangeAction* pAction = GetFirst(); if (pAction) @@ -4717,12 +4715,6 @@ OUString ScChangeTrack::GetChangeTrackInfo() lcl_getTrackedChange(pDoc, i++, pAction, aRedlines); } } - - boost::property_tree::ptree aTree; - aTree.add_child("redlines", aRedlines); - std::stringstream aStream; - boost::property_tree::write_json(aStream, aTree); - return OUString::fromUtf8(aStream.str().c_str()); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/unoobj/docuno.cxx b/sc/source/ui/unoobj/docuno.cxx index 4592bdb9c04d..7e5dd7fe33dd 100644 --- a/sc/source/ui/unoobj/docuno.cxx +++ b/sc/source/ui/unoobj/docuno.cxx @@ -940,17 +940,13 @@ PointerStyle ScModelObj::getPointer() return pGridWindow->GetPointer(); } -OUString ScModelObj::getTrackedChanges() +void ScModelObj::getTrackedChanges(tools::JsonWriter& rJson) { - OUString aRet; - if (pDocShell) { if (ScChangeTrack* pChangeTrack = pDocShell->GetDocument().GetChangeTrack()) - aRet = pChangeTrack->GetChangeTrackInfo(); + pChangeTrack->GetChangeTrackInfo(rJson); } - - return aRet; } void ScModelObj::setClientVisibleArea(const tools::Rectangle& rRectangle) diff --git a/sw/inc/unotxdoc.hxx b/sw/inc/unotxdoc.hxx index 8c6a1358520c..16780ce2edeb 100644 --- a/sw/inc/unotxdoc.hxx +++ b/sw/inc/unotxdoc.hxx @@ -436,7 +436,7 @@ public: /// @see vcl::ITiledRenderable::getPointer(). virtual PointerStyle getPointer() override; /// @see vcl::ITiledRenderable::getTrackedChanges(). - OUString getTrackedChanges() override; + void getTrackedChanges(tools::JsonWriter&) override; /// @see vcl::ITiledRenderable::getTrackedChangeAuthors(). OUString getTrackedChangeAuthors() override; diff --git a/sw/source/uibase/uno/unotxdoc.cxx b/sw/source/uibase/uno/unotxdoc.cxx index 15be728d74af..0d934c32f39c 100644 --- a/sw/source/uibase/uno/unotxdoc.cxx +++ b/sw/source/uibase/uno/unotxdoc.cxx @@ -161,6 +161,7 @@ #include #include #include +#include #define TWIPS_PER_PIXEL 15 @@ -3255,9 +3256,9 @@ PointerStyle SwXTextDocument::getPointer() return pWrtShell->GetView().GetEditWin().GetPointer(); } -OUString SwXTextDocument::getTrackedChanges() +void SwXTextDocument::getTrackedChanges(tools::JsonWriter& rJson) { - boost::property_tree::ptree aTrackedChanges; + auto redlinesNode = rJson.startNode("redlines"); // Disable since usability is very low beyond some small number of changes. static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr; @@ -3267,19 +3268,17 @@ OUString SwXTextDocument::getTrackedChanges() = pDocShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); for (SwRedlineTable::size_type i = 0; i < rRedlineTable.size(); ++i) { - boost::property_tree::ptree aTrackedChange; - aTrackedChange.put("index", rRedlineTable[i]->GetId()); - aTrackedChange.put("author", rRedlineTable[i]->GetAuthorString(1).toUtf8().getStr()); - aTrackedChange.put("type", SwRedlineTypeToOUString( - rRedlineTable[i]->GetRedlineData().GetType()) - .toUtf8() - .getStr()); - aTrackedChange.put("comment", - rRedlineTable[i]->GetRedlineData().GetComment().toUtf8().getStr()); - aTrackedChange.put("description", rRedlineTable[i]->GetDescr().toUtf8().getStr()); + auto redlineNode = rJson.startNode(""); + rJson.put("index", rRedlineTable[i]->GetId()); + rJson.put("author", rRedlineTable[i]->GetAuthorString(1)); + rJson.put("type", SwRedlineTypeToOUString( + rRedlineTable[i]->GetRedlineData().GetType())); + rJson.put("comment", + rRedlineTable[i]->GetRedlineData().GetComment()); + rJson.put("description", rRedlineTable[i]->GetDescr()); OUString sDateTime = utl::toISO8601( rRedlineTable[i]->GetRedlineData().GetTimeStamp().GetUNODateTime()); - aTrackedChange.put("dateTime", sDateTime.toUtf8().getStr()); + rJson.put("dateTime", sDateTime); SwContentNode* pContentNd = rRedlineTable[i]->GetContentNode(); SwView* pView = dynamic_cast(SfxViewShell::Current()); @@ -3299,19 +3298,10 @@ OUString SwXTextDocument::getTrackedChanges() aRects.push_back(rNextRect.SVRect().toString()); const OString sRects = comphelper::string::join("; ", aRects); - aTrackedChange.put("textRange", sRects.getStr()); + rJson.put("textRange", sRects); } - - aTrackedChanges.push_back(std::make_pair("", aTrackedChange)); } } - - boost::property_tree::ptree aTree; - aTree.add_child("redlines", aTrackedChanges); - std::stringstream aStream; - boost::property_tree::write_json(aStream, aTree); - - return OUString::fromUtf8(aStream.str().c_str()); } OUString SwXTextDocument::getTrackedChangeAuthors() diff --git a/tools/CppunitTest_tools_test.mk b/tools/CppunitTest_tools_test.mk index 5672be53353f..46a6cf5242cd 100644 --- a/tools/CppunitTest_tools_test.mk +++ b/tools/CppunitTest_tools_test.mk @@ -19,6 +19,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,tools_test, \ tools/qa/cppunit/test_time \ tools/qa/cppunit/test_fract \ tools/qa/cppunit/test_inetmime \ + tools/qa/cppunit/test_json_writer \ tools/qa/cppunit/test_pathutils \ tools/qa/cppunit/test_reversemap \ tools/qa/cppunit/test_stream \ diff --git a/tools/Library_tl.mk b/tools/Library_tl.mk index 233e75db0050..ecad06913ed2 100644 --- a/tools/Library_tl.mk +++ b/tools/Library_tl.mk @@ -69,6 +69,7 @@ $(eval $(call gb_Library_add_exception_objects,tl,\ tools/source/memtools/multisel \ tools/source/misc/cpuid \ tools/source/misc/extendapplicationenvironment \ + tools/source/misc/json_writer \ tools/source/ref/globname \ tools/source/ref/ref \ tools/source/stream/stream \ diff --git a/tools/qa/cppunit/test_json_writer.cxx b/tools/qa/cppunit/test_json_writer.cxx new file mode 100644 index 000000000000..c4e9331c8d17 --- /dev/null +++ b/tools/qa/cppunit/test_json_writer.cxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include + +namespace +{ +class JsonWriterTest : public test::BootstrapFixture +{ +public: + JsonWriterTest() + : BootstrapFixture(true, false) + { + } + + virtual void setUp() override {} + + void test1(); + + CPPUNIT_TEST_SUITE(JsonWriterTest); + CPPUNIT_TEST(test1); + CPPUNIT_TEST_SUITE_END(); +}; + +void JsonWriterTest::test1() +{ + tools::JsonWriter aJson; + + { + auto testNode = aJson.startNode("node"); + aJson.put("oustring", OUString("val1")); + aJson.put("ostring", OString("val2")); + aJson.put("charptr", "val3"); + aJson.put("int", 12); + } + + std::unique_ptr result(aJson.extractData()); + + CPPUNIT_ASSERT_EQUAL(std::string("{ \"node\": { \"oustring\": \"val1\", \"ostring\": \"val2\", " + "\"charptr\": \"val3\", \"int\": 12}}"), + std::string(result.get())); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(JsonWriterTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/misc/json_writer.cxx b/tools/source/misc/json_writer.cxx new file mode 100644 index 000000000000..a233381038c4 --- /dev/null +++ b/tools/source/misc/json_writer.cxx @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +namespace tools +{ +/** These buffers are short-lived, so rather waste some space and avoid the cost of + * repeated calls into the allocator */ +constexpr int DEFAULT_BUFFER_SIZE = 2048; + +JsonWriter::JsonWriter() + : mSpaceAllocated(DEFAULT_BUFFER_SIZE) + , maBuffer(new char[mSpaceAllocated]) + , mStartNodeCount(0) + , mPos(maBuffer.get()) +{ + *mPos = '{'; + ++mPos; + *mPos = ' '; + ++mPos; +} + +JsonWriter::~JsonWriter() { assert(!maBuffer && "forgot to extract data?"); } + +ScopedJsonWriterNode JsonWriter::startNode(const char* pNodeName) +{ + auto len = strlen(pNodeName); + ensureSpace(len + 4); + *mPos = '"'; + ++mPos; + memcpy(mPos, pNodeName, len); + mPos += len; + strncpy(mPos, "\": { ", 5); + mPos += 5; + mStartNodeCount++; + mbFirstFieldInNode = true; + return ScopedJsonWriterNode(*this); +} + +void JsonWriter::endNode() +{ + assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere"); + --mStartNodeCount; + ensureSpace(1); + *mPos = '}'; + ++mPos; +} + +void JsonWriter::put(const char* pPropName, const OUString& rPropVal) +{ + addCommaBeforeField(); + + auto nPropNameLength = strlen(pPropName); + auto nWorstCasePropValLength = rPropVal.getLength() * 2; + ensureSpace(nPropNameLength + nWorstCasePropValLength + 6); + *mPos = '"'; + ++mPos; + memcpy(mPos, pPropName, nPropNameLength); + mPos += nPropNameLength; + strncpy(mPos, "\": \"", 4); + mPos += 4; + + // Convert from UTF-16 to UTF-8 and perform escaping + for (int i = 0; i < rPropVal.getLength(); ++i) + { + sal_Unicode ch = rPropVal[i]; + if (ch == '\\') + { + *mPos = static_cast(ch); + ++mPos; + *mPos = static_cast(ch); + ++mPos; + } + else if (ch == '"') + { + *mPos = '\\'; + ++mPos; + *mPos = static_cast(ch); + ++mPos; + } + else if (ch <= 0x7F) + { + *mPos = static_cast(ch); + ++mPos; + } + else if (ch <= 0x7FF) + { + *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */ + ++mPos; + *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */ + ++mPos; + } + else + { + *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */ + ++mPos; + *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */ + ++mPos; + *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */ + ++mPos; + } + } + + *mPos = '"'; + ++mPos; +} + +void JsonWriter::put(const char* pPropName, const OString& rPropVal) +{ + addCommaBeforeField(); + + auto nPropNameLength = strlen(pPropName); + auto nWorstCasePropValLength = rPropVal.getLength(); + ensureSpace(nPropNameLength + nWorstCasePropValLength + 6); + *mPos = '"'; + ++mPos; + memcpy(mPos, pPropName, nPropNameLength); + mPos += nPropNameLength; + strncpy(mPos, "\": \"", 4); + mPos += 4; + + // copy and perform escaping + for (int i = 0; i < rPropVal.getLength(); ++i) + { + char ch = rPropVal[i]; + if (ch == '\\') + { + *mPos = ch; + ++mPos; + *mPos = ch; + ++mPos; + } + else if (ch == '"') + { + *mPos = '\\'; + ++mPos; + *mPos = ch; + ++mPos; + } + else + { + *mPos = ch; + ++mPos; + } + } + + *mPos = '"'; + ++mPos; +} + +void JsonWriter::put(const char* pPropName, const char* pPropVal) +{ + addCommaBeforeField(); + + auto nPropNameLength = strlen(pPropName); + auto nPropValLength = strlen(pPropVal); + auto nWorstCasePropValLength = nPropValLength * 2; + ensureSpace(nPropNameLength + nWorstCasePropValLength + 6); + *mPos = '"'; + ++mPos; + memcpy(mPos, pPropName, nPropNameLength); + mPos += nPropNameLength; + strncpy(mPos, "\": \"", 4); + mPos += 4; + + // copy and perform escaping + for (;;) + { + char ch = *pPropVal; + if (!ch) + break; + ++pPropVal; + if (ch == '\\') + { + *mPos = ch; + ++mPos; + *mPos = ch; + ++mPos; + } + else if (ch == '"') + { + *mPos = '\\'; + ++mPos; + *mPos = ch; + ++mPos; + } + else + { + *mPos = ch; + ++mPos; + } + } + + *mPos = '"'; + ++mPos; +} + +void JsonWriter::put(const char* pPropName, int nPropVal) +{ + addCommaBeforeField(); + + auto nPropNameLength = strlen(pPropName); + auto nWorstCasePropValLength = 32; + ensureSpace(nPropNameLength + nWorstCasePropValLength + 6); + *mPos = '"'; + ++mPos; + memcpy(mPos, pPropName, nPropNameLength); + mPos += nPropNameLength; + strncpy(mPos, "\": ", 3); + mPos += 3; + + mPos += sprintf(mPos, "%d", nPropVal); +} + +void JsonWriter::addCommaBeforeField() +{ + if (mbFirstFieldInNode) + mbFirstFieldInNode = false; + else + { + *mPos = ','; + ++mPos; + *mPos = ' '; + ++mPos; + } +} + +/** Hands ownership of the the underlying storage buffer to the caller, + * after this no more document modifications may be written. */ +char* JsonWriter::extractData() +{ + assert(mStartNodeCount == 0 && "did not close all nodes"); + assert(maBuffer && "data already extracted"); + // add closing brace + *mPos = '}'; + ++mPos; + // null-terminate + *mPos = 0; + mPos = nullptr; + return maBuffer.release(); +} + +} // namespace tools +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ -- cgit v1.2.3