I have reworked the code from my previous (linked) post.
For reference:
I have a string which contains JSON-like object flattened (stringify()
ed).
I need to JSON-ify it back. It's a custom format, so I wrote function to JSON-ify the string.
Please review it. Also one particular thing that I don't like is previousIsClosingBracket
variable. I have added it for handling correct formatting of the last example. Is there a better way to detect such cases and have more beautiful solution?
#include <string>
#include <unordered_set>
#include <vector>
#include <iostream>
struct WordAtLevel
{
std::string word;
std::size_t level;
};
using tWordLevels = std::vector<WordAtLevel>;
class Formatter
{
public:
explicit Formatter(std::string tclString);
std::string formatted() const;
private:
tWordLevels parse() const;
static std::string build(tWordLevels wordAtLevels);
std::string readUntilDelimiter() const;
static bool isClosingBrackets(const std::string &str);
static std::string indentation(const std::size_t tabCount);
std::string m_tclString;
std::size_t m_strLen = 0;
mutable std::size_t m_idx = 0;
static constexpr int tabWidth = 4;
mutable bool m_bPrevWordReserved = false;
const std::unordered_set<std::string> m_reservedWords{ "coords", "comment" };
};
Formatter::Formatter(std::string tclString)
: m_tclString(std::move(tclString))
, m_strLen(m_tclString.length())
{
}
std::string Formatter::formatted() const
{
const tWordLevels wordLevels = parse();
return build(wordLevels);
}
tWordLevels Formatter::parse() const
{
tWordLevels wordAtLevels;
int level = 0;
for (m_idx = 0; m_idx < m_strLen; ++m_idx)
{
switch (auto ch = m_tclString[m_idx])
{
case '}':
{
if (m_bPrevWordReserved)
m_bPrevWordReserved = false;
else
--level;
wordAtLevels.emplace_back("}", level);
break;
}
case '{':
wordAtLevels.emplace_back("{", level);
if (!m_bPrevWordReserved)
++level;
[[fallthrough]];
default:
{
std::string word = readUntilDelimiter();
if (!word.empty())
{
if (m_reservedWords.contains(word))
m_bPrevWordReserved = true;
wordAtLevels.emplace_back(word, level);
}
break;
}
}
}
return wordAtLevels;
}
std::string Formatter::build(tWordLevels wordAtLevels)
{
std::string result;
const auto wordCount = wordAtLevels.size();
bool previousIsClosingBracket = false;
for (std::size_t idx = 0; idx < wordCount; ++idx)
{
const auto &[word, level] = wordAtLevels[idx];
result += indentation(level);
result += word;
previousIsClosingBracket = isClosingBrackets(word);
while (++idx < wordCount)
{
const auto &[nextWord, nextLevel] = wordAtLevels[idx];
if (nextLevel != level)
break;
if (previousIsClosingBracket && !isClosingBrackets(nextWord))
break;
result += ' ';
result += nextWord;
}
result += '\n';
--idx;
}
return result;
}
std::string Formatter::readUntilDelimiter() const
{
std::string word;
++m_idx;
while (m_idx < m_strLen)
{
const auto ch = m_tclString[m_idx];
if (ch == ' ' || ch == '{' || ch == '}')
{
--m_idx;
break;
}
++m_idx;
word += ch;
}
return word;
}
bool Formatter::isClosingBrackets(const std::string& str)
{
return str == std::string_view("}");
}
std::string Formatter::indentation(const std::size_t tabCount)
{
return std::string(tabCount * tabWidth, ' ');
}
int main()
{
const Formatter f1("{data {a {obj {coords {10 10} comment {} radius 260 type circle}}}}");
std::cout << f1.formatted() << '\n';
const Formatter f2("{data {b {obj {coords {-95 -85 -70 -85 -75 -95} comment {abc} type polygon}}}}");
std::cout << f2.formatted() << '\n';
const Formatter f3("{data {c {obj {coords {-55 -65 -70 -65 -25 -64} comment {abc def} type polygon}}}}");
std::cout << f3.formatted() << '\n';
const Formatter f4("{data {d {obj {coords {59 60} comment {} type point}} e {obj {coords {928 324} comment {} type point}}}}");
std::cout << f4.formatted() << '\n';
}