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