diff --git a/src/NppJsonViewer/JsonViewDlg.h b/src/NppJsonViewer/JsonViewDlg.h index 7ac9947..66bc4ff 100644 --- a/src/NppJsonViewer/JsonViewDlg.h +++ b/src/NppJsonViewer/JsonViewDlg.h @@ -12,9 +12,12 @@ #include "ScintillaEditor.h" #include "JsonHandler.h" #include "JsonNode.h" +#include "TreeHandler.h" -class JsonViewDlg : public DockingDlgInterface +class JsonViewDlg + : public DockingDlgInterface + , public TreeHandler { enum class eButton { @@ -44,9 +47,9 @@ class JsonViewDlg : public DockingDlgInterface void HandleTabActivated(); void UpdateTitle(); - HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text); - HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos); - void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray); + HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) override; + HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) override; + void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) override; private: void DrawJsonTree(); diff --git a/src/NppJsonViewer/NPPJSONViewer.vcxproj b/src/NppJsonViewer/NPPJSONViewer.vcxproj index 9665dfa..fe14d99 100644 --- a/src/NppJsonViewer/NPPJSONViewer.vcxproj +++ b/src/NppJsonViewer/NPPJSONViewer.vcxproj @@ -197,6 +197,7 @@ + diff --git a/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters b/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters index f7cdb05..e3a4ea1 100644 --- a/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters +++ b/src/NppJsonViewer/NPPJSONViewer.vcxproj.filters @@ -57,6 +57,9 @@ ThirdParty\npp + + Header Files + diff --git a/src/NppJsonViewer/RapidJsonHandler.cpp b/src/NppJsonViewer/RapidJsonHandler.cpp index 72b5b0d..6d0314d 100644 --- a/src/NppJsonViewer/RapidJsonHandler.cpp +++ b/src/NppJsonViewer/RapidJsonHandler.cpp @@ -1,5 +1,5 @@ #include "RapidJsonHandler.h" -#include "JsonViewDlg.h" +#include "TreeHandler.h" const char* const STR_NULL = "null"; const char* const STR_TRUE = "true"; @@ -69,7 +69,7 @@ bool RapidJsonHandler::String(const Ch* str, unsigned /*length*/, bool /*copy*/) // handle case, when there is only a value in input if (m_NodeStack.empty()) { - m_dlg->InsertToTree(m_treeRoot, str); + m_pTreeHandler->InsertToTree(m_treeRoot, str); return true; } @@ -109,14 +109,14 @@ bool RapidJsonHandler::StartObject() HTREEITEM newNode = nullptr; if (parent->node.type != JsonNodeType::ARRAY) { - newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); + newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); m_jsonLastKey.clear(); } else { // It is an array std::string arr = "[" + std::to_string(parent->counter) + "]"; - newNode = m_dlg->InsertToTree(parent->subRoot, arr); + newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr); } parent->counter++; @@ -158,14 +158,14 @@ bool RapidJsonHandler::StartArray() HTREEITEM newNode; if (parent->node.type != JsonNodeType::ARRAY) { - newNode = m_dlg->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); + newNode = m_pTreeHandler->InsertToTree(parent->subRoot, m_jsonLastKey.strKey, m_jsonLastKey.pos); m_jsonLastKey.clear(); } else { // It is an array std::string arr = "[" + std::to_string(parent->counter) + "]"; - newNode = m_dlg->InsertToTree(parent->subRoot, arr); + newNode = m_pTreeHandler->InsertToTree(parent->subRoot, arr); } parent->counter++; @@ -210,9 +210,9 @@ void RapidJsonHandler::InsertToTree(TreeNode* node, const char* const str, bool // Insert item to tree if (bQuote) - m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : \"" + node->node.value + "\"", node->node.key.pos); + m_pTreeHandler->InsertToTree(node->subRoot, node->node.key.strKey + " : \"" + node->node.value + "\"", node->node.key.pos); else - m_dlg->InsertToTree(node->subRoot, node->node.key.strKey + " : " + node->node.value, node->node.key.pos); + m_pTreeHandler->InsertToTree(node->subRoot, node->node.key.strKey + " : " + node->node.value, node->node.key.pos); } void RapidJsonHandler::AppendNodeCount(unsigned elementCount, bool bArray) @@ -223,7 +223,7 @@ void RapidJsonHandler::AppendNodeCount(unsigned elementCount, bool bArray) m_NodeStack.pop(); if (node->subRoot && node->subRoot != m_treeRoot) - m_dlg->AppendNodeCount(node->subRoot, elementCount, bArray); + m_pTreeHandler->AppendNodeCount(node->subRoot, elementCount, bArray); delete node; } diff --git a/src/NppJsonViewer/RapidJsonHandler.h b/src/NppJsonViewer/RapidJsonHandler.h index a912276..90416dd 100644 --- a/src/NppJsonViewer/RapidJsonHandler.h +++ b/src/NppJsonViewer/RapidJsonHandler.h @@ -10,7 +10,7 @@ #include "JsonNode.h" #include "TrackingStream.h" -class JsonViewDlg; +class TreeHandler; struct TreeNode { @@ -26,13 +26,13 @@ class RapidJsonHandler : public rapidjson::BaseReaderHandler, std::stack m_NodeStack; TrackingStreamSharedPtr m_pTS; - JsonViewDlg* m_dlg = nullptr; - HTREEITEM m_treeRoot = nullptr; + TreeHandler* m_pTreeHandler = nullptr; + HTREEITEM m_treeRoot = nullptr; public: - RapidJsonHandler(JsonViewDlg* dlg, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr) + RapidJsonHandler(TreeHandler* handler, HTREEITEM treeRoot, TrackingStreamSharedPtr pTS = nullptr) : m_pTS(pTS ? pTS->GetShared() : nullptr) - , m_dlg(dlg) + , m_pTreeHandler(handler) , m_treeRoot(treeRoot) { } diff --git a/src/NppJsonViewer/TreeHandler.h b/src/NppJsonViewer/TreeHandler.h new file mode 100644 index 0000000..d33c3f7 --- /dev/null +++ b/src/NppJsonViewer/TreeHandler.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +#include "JsonNode.h" + +class TreeHandler +{ +public: + virtual ~TreeHandler() = default; + + virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text) = 0; + virtual HTREEITEM InsertToTree(HTREEITEM parent, const std::string& text, const Position& pos) = 0; + virtual void AppendNodeCount(HTREEITEM node, unsigned elementCount, bool bArray) = 0; +}; diff --git a/tests/UnitTest/NavigationTest.cpp b/tests/UnitTest/NavigationTest.cpp new file mode 100644 index 0000000..142260a --- /dev/null +++ b/tests/UnitTest/NavigationTest.cpp @@ -0,0 +1,228 @@ +#include + +#include +#include +#include + +#include "TreeHandler.h" +#include "JsonHandler.h" +#include "TrackingStream.h" +#include "RapidJsonHandler.h" + + +class TreeHandlerTest : public TreeHandler +{ + std::unordered_map m_mapKeyPosition; + +public: + virtual ~TreeHandlerTest() = default; + + + // Inherited via TreeHandler + HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text) override + { + m_mapKeyPosition[text] = {}; + return HTREEITEM(); + } + + HTREEITEM InsertToTree(HTREEITEM /*parent*/, const std::string& text, const Position& pos) override + { + m_mapKeyPosition[text] = pos; + return HTREEITEM(); + } + + void AppendNodeCount(HTREEITEM /*node*/, unsigned /*elementCount*/, bool /*bArray*/) override {} + + auto GetPosition(const std::string& text) const -> std::optional + { + auto find = m_mapKeyPosition.find(text); + + std::optional retVal = std::nullopt; + if (find != m_mapKeyPosition.cend()) + retVal = find->second; + + return retVal; + } +}; + +namespace JsonNavigation +{ + using KeyPos = std::pair; + struct TestData + { + std::string input; + std::vector output; + }; + + class NavigationTest : public ::testing::Test + { + protected: + TestData m_testData; + JsonHandler m_jsonHandler {{}}; + TreeHandlerTest m_TreeHandler; + TrackingStreamSharedPtr m_pTSS = nullptr; + std::unique_ptr m_pHandler; + }; + + TEST_F(NavigationTest, StringKey) + { + const std::string jsonText = R"({ + "key1" : "value1", + "key2" : "value2", + "keyLong" : "Value very very long", + "k" : "V" +})"; + + m_testData.input = jsonText; + m_testData.output = { + {R"(key1 : "value1")", Position {.nLine = 1, .nColumn = 5, .nKeyLength = 4}}, + {R"(key2 : "value2")", Position {.nLine = 2, .nColumn = 5, .nKeyLength = 4}}, + {R"(keyLong : "Value very very long")", Position {.nLine = 3, .nColumn = 5, .nKeyLength = 7}}, + {R"(k : "V")", Position {.nLine = 4, .nColumn = 5, .nKeyLength = 1}}, + }; + + m_pTSS = std::make_shared(jsonText); + m_pHandler = std::make_unique(&m_TreeHandler, nullptr, m_pTSS); + rapidjson::StringBuffer sb; + + Result res = m_jsonHandler.ParseJson(jsonText, sb, *m_pHandler, m_pTSS); + ASSERT_TRUE(res.success); + + for (const auto& each : m_testData.output) + { + const auto pos = m_TreeHandler.GetPosition(each.first); + ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl; + + auto posValue = pos.value(); + ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl; + } + } + + TEST_F(NavigationTest, StringKeyCompressed) + { + const std::string jsonText = R"({"key1":"value1","key2":"value2","keyLong":"Value very very long","k":"V"})"; + + m_testData.input = jsonText; + m_testData.output = { + {R"(key1 : "value1")", Position {.nLine = 0, .nColumn = 2, .nKeyLength = 4}}, + {R"(key2 : "value2")", Position {.nLine = 0, .nColumn = 18, .nKeyLength = 4}}, + {R"(keyLong : "Value very very long")", Position {.nLine = 0, .nColumn = 34, .nKeyLength = 7}}, + {R"(k : "V")", Position {.nLine = 0, .nColumn = 67, .nKeyLength = 1}}, + }; + + m_pTSS = std::make_shared(jsonText); + m_pHandler = std::make_unique(&m_TreeHandler, nullptr, m_pTSS); + rapidjson::StringBuffer sb; + + Result res = m_jsonHandler.ParseJson(jsonText, sb, *m_pHandler, m_pTSS); + ASSERT_TRUE(res.success); + + for (const auto& each : m_testData.output) + { + const auto pos = m_TreeHandler.GetPosition(each.first); + ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl; + + auto posValue = pos.value(); + ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl; + } + } + + TEST_F(NavigationTest, MixedKeys) + { + const std::string jsonText = R"({ + // this is comment + "Id": 100000000302052988.0, + "num": [12.148681171238422,42.835353759876654], + "timeInfo":1.234e5, + "FreeTextInput":"\u003Cscript\u003Ealert(\u0022links\u0022);\u003C/script\u003E", + "text1":["Hello", "World"], + "text2":[true, false, true], + "text3":[null, null, null, "power"], + "Test":[ + { + "ok": [ + "HelloWorld" + ] + }, + { + "Tata": [ + "TataByeBye" + ] + } + ], + "Nan": NaN, + "Inf":[ + -Infinity, + Infinity, + -Infinity, + Infinity + ], + "Object":{"Test1":"Test1", "Test2":"Test2"} +})"; + + m_testData.input = jsonText; + m_testData.output = { + {R"(Id : 100000000302052988.0)", Position {.nLine = 2, .nColumn = 3, .nKeyLength = 2}}, + {R"(num)", Position {.nLine = 3, .nColumn = 3, .nKeyLength = 3}}, + {R"([0] : 12.148681171238422)", Position {.nLine = 3, .nColumn = 10, .nKeyLength = 18}}, + {R"([1] : 42.835353759876654)", Position {.nLine = 3, .nColumn = 29, .nKeyLength = 18}}, + {R"(timeInfo : 1.234e5)", Position {.nLine = 4, .nColumn = 3, .nKeyLength = 8}}, + {R"(FreeTextInput : "")", Position {.nLine = 5, .nColumn = 3, .nKeyLength = 13}}, + + {R"(text1)", Position {.nLine = 6, .nColumn = 3, .nKeyLength = 5}}, + {R"([0] : "Hello")", Position {.nLine = 6, .nColumn = 12, .nKeyLength = 5}}, + {R"([1] : "World")", Position {.nLine = 6, .nColumn = 21, .nKeyLength = 5}}, + + {R"(text2)", Position {.nLine = 7, .nColumn = 3, .nKeyLength = 5}}, + {R"([0] : true)", Position {.nLine = 7, .nColumn = 11, .nKeyLength = 4}}, + {R"([1] : false)", Position {.nLine = 7, .nColumn = 17, .nKeyLength = 5}}, + {R"([2] : true)", Position {.nLine = 7, .nColumn = 24, .nKeyLength = 4}}, + + {R"(text3)", Position {.nLine = 8, .nColumn = 3, .nKeyLength = 5}}, + {R"([0] : null)", Position {.nLine = 8, .nColumn = 11, .nKeyLength = 4}}, + {R"([1] : null)", Position {.nLine = 8, .nColumn = 17, .nKeyLength = 4}}, + {R"([2] : null)", Position {.nLine = 8, .nColumn = 23, .nKeyLength = 4}}, + {R"([3] : "power")", Position {.nLine = 8, .nColumn = 30, .nKeyLength = 5}}, + + {R"(Test)", Position {.nLine = 9, .nColumn = 3, .nKeyLength = 4}}, + {R"(ok)", Position {.nLine = 11, .nColumn = 7, .nKeyLength = 2}}, + {R"([0] : "HelloWorld")", Position {.nLine = 12, .nColumn = 9, .nKeyLength = 10}}, + {R"(Tata)", Position {.nLine = 16, .nColumn = 7, .nKeyLength = 4}}, + {R"([0] : "TataByeBye")", Position {.nLine = 17, .nColumn = 9, .nKeyLength = 10}}, + + {R"(Nan : NaN)", Position {.nLine = 21, .nColumn = 3, .nKeyLength = 3}}, + + {R"(Inf)", Position {.nLine = 22, .nColumn = 3, .nKeyLength = 3}}, + {R"([0] : -Infinity)", Position {.nLine = 23, .nColumn = 4, .nKeyLength = 9}}, + {R"([1] : Infinity)", Position {.nLine = 24, .nColumn = 4, .nKeyLength = 8}}, + {R"([2] : -Infinity)", Position {.nLine = 25, .nColumn = 4, .nKeyLength = 9}}, + {R"([3] : Infinity)", Position {.nLine = 26, .nColumn = 4, .nKeyLength = 8}}, + + {R"(Object)", Position {.nLine = 28, .nColumn = 3, .nKeyLength = 6}}, + {R"(Test1 : "Test1")", Position {.nLine = 28, .nColumn = 13, .nKeyLength = 5}}, + {R"(Test2 : "Test2")", Position {.nLine = 28, .nColumn = 30, .nKeyLength = 5}}, + }; + + m_pTSS = std::make_shared(jsonText); + m_pHandler = std::make_unique(&m_TreeHandler, nullptr, m_pTSS); + rapidjson::StringBuffer sb; + + Result res = m_jsonHandler.ParseJson(jsonText, sb, *m_pHandler, m_pTSS); + ASSERT_TRUE(res.success); + + for (const auto& each : m_testData.output) + { + const auto pos = m_TreeHandler.GetPosition(each.first); + ASSERT_TRUE(pos.has_value()) << "For key: " << each.first << std::endl; + + auto posValue = pos.value(); + ASSERT_EQ(posValue.nLine, each.second.nLine) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nColumn, each.second.nColumn) << "For key: " << each.first << std::endl; + ASSERT_EQ(posValue.nKeyLength, each.second.nKeyLength) << "For key: " << each.first << std::endl; + } + } +} // namespace JsonNavigation diff --git a/tests/UnitTest/UnitTest.vcxproj b/tests/UnitTest/UnitTest.vcxproj index 5e76f01..173906b 100644 --- a/tests/UnitTest/UnitTest.vcxproj +++ b/tests/UnitTest/UnitTest.vcxproj @@ -154,10 +154,12 @@ + + @@ -170,6 +172,7 @@ + diff --git a/tests/UnitTest/UnitTest.vcxproj.filters b/tests/UnitTest/UnitTest.vcxproj.filters index 1a45959..2b82d69 100644 --- a/tests/UnitTest/UnitTest.vcxproj.filters +++ b/tests/UnitTest/UnitTest.vcxproj.filters @@ -54,6 +54,12 @@ Source Files + + Source Files + + + Source Files\NppJsonViewer + @@ -62,5 +68,8 @@ Source Files\NppJsonViewer + + Source Files\NppJsonViewer + \ No newline at end of file