XML and JSON
Reads XML and JSON files and shows the parsed result.
- XmlDocument and XmlElement
- JSON::parse and var
- Displaying parsed trees
Source: examples/Utilities/XMLandJSONDemo.h on GitHub.
cpp
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: XMLandJSONDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Reads XML and JSON files.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, vs2026, linux_make, androidstudio,
xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: XMLandJSONDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class XmlTreeItem final : public TreeViewItem
{
public:
XmlTreeItem (XmlElement& x) : xml (x) {}
String getUniqueName() const override
{
if (xml.getTagName().isEmpty())
return "unknown";
return xml.getTagName();
}
bool mightContainSubItems() override
{
return xml.getFirstChildElement() != nullptr;
}
void paintItem (Graphics& g, int width, int height) override
{
// if this item is selected, fill it with a background colour..
if (isSelected())
g.fillAll (Colours::blue.withAlpha (0.3f));
// use a "colour" attribute in the xml tag for this node to set the text colour..
g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000")));
g.setFont ((float) height * 0.7f);
// draw the xml element's tag name..
g.drawText (xml.getTagName(),
4, 0, width - 4, height,
Justification::centredLeft, true);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
{
// if we've not already done so, we'll now add the tree's sub-items. You could
// also choose to delete the existing ones and refresh them if that's more suitable
// in your app.
if (getNumSubItems() == 0)
{
// create and add sub-items to this node of the tree, corresponding to
// each sub-element in the XML..
for (auto* child : xml.getChildIterator())
if (child != nullptr)
addSubItem (new XmlTreeItem (*child));
}
}
else
{
// in this case, we'll leave any sub-items in the tree when the node gets closed,
// though you could choose to delete them if that's more appropriate for
// your application.
}
}
private:
XmlElement& xml;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem)
};
//==============================================================================
class JsonTreeItem final : public TreeViewItem
{
public:
JsonTreeItem (Identifier i, var value)
: identifier (i),
json (value)
{}
String getUniqueName() const override
{
return identifier.toString() + "_id";
}
bool mightContainSubItems() override
{
if (auto* obj = json.getDynamicObject())
return obj->getProperties().size() > 0;
return json.isArray();
}
void paintItem (Graphics& g, int width, int height) override
{
// if this item is selected, fill it with a background colour..
if (isSelected())
g.fillAll (Colours::blue.withAlpha (0.3f));
g.setColour (Colours::black);
g.setFont ((float) height * 0.7f);
// draw the element's tag name..
g.drawText (getText(),
4, 0, width - 4, height,
Justification::centredLeft, true);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
{
// if we've not already done so, we'll now add the tree's sub-items. You could
// also choose to delete the existing ones and refresh them if that's more suitable
// in your app.
if (getNumSubItems() == 0)
{
// create and add sub-items to this node of the tree, corresponding to
// the type of object this var represents
if (json.isArray())
{
for (int i = 0; i < json.size(); ++i)
{
auto& child = json[i];
jassert (! child.isVoid());
addSubItem (new JsonTreeItem ({}, child));
}
}
else if (auto* obj = json.getDynamicObject())
{
auto& props = obj->getProperties();
for (int i = 0; i < props.size(); ++i)
{
auto id = props.getName (i);
auto child = props[id];
jassert (! child.isVoid());
addSubItem (new JsonTreeItem (id, child));
}
}
}
}
else
{
// in this case, we'll leave any sub-items in the tree when the node gets closed,
// though you could choose to delete them if that's more appropriate for
// your application.
}
}
private:
Identifier identifier;
var json;
/** Returns the text to display in the tree.
This is a little more complex for JSON than XML as nodes can be strings, objects or arrays.
*/
String getText() const
{
String text;
if (identifier.isValid())
text << identifier.toString();
if (! json.isVoid())
{
if (text.isNotEmpty() && (! json.isArray()))
text << ": ";
if (json.isObject() && (! identifier.isValid()))
text << "[Array]";
else if (! json.isArray())
text << json.toString();
}
return text;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem)
};
//==============================================================================
class XMLandJSONDemo final : public Component,
private CodeDocument::Listener
{
public:
/** The type of database to parse. */
enum Type
{
xml,
json
};
XMLandJSONDemo()
{
setOpaque (true);
addAndMakeVisible (typeBox);
typeBox.addItem ("XML", 1);
typeBox.addItem ("JSON", 2);
typeBox.onChange = [this]
{
if (typeBox.getSelectedId() == 1)
reset (xml);
else
reset (json);
};
comboBoxLabel.attachToComponent (&typeBox, true);
addAndMakeVisible (codeDocumentComponent);
codeDocument.addListener (this);
resultsTree.setTitle ("Results");
addAndMakeVisible (resultsTree);
resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
resultsTree.setDefaultOpenness (true);
addAndMakeVisible (errorMessage);
errorMessage.setReadOnly (true);
errorMessage.setMultiLine (true);
errorMessage.setCaretVisible (false);
errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
typeBox.setSelectedId (1);
setSize (500, 500);
}
~XMLandJSONDemo() override
{
resultsTree.setRootItem (nullptr);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto area = getLocalBounds();
typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
codeDocumentComponent.setBounds (area.removeFromTop (area.getHeight() / 2).reduced (8));
resultsTree .setBounds (area.reduced (8));
errorMessage .setBounds (resultsTree.getBounds());
}
private:
ComboBox typeBox;
Label comboBoxLabel { {}, "Database Type:" };
CodeDocument codeDocument;
CodeEditorComponent codeDocumentComponent { codeDocument, nullptr };
TreeView resultsTree;
std::unique_ptr<TreeViewItem> rootItem;
std::unique_ptr<XmlElement> parsedXml;
TextEditor errorMessage;
void rebuildTree()
{
std::unique_ptr<XmlElement> openness;
if (rootItem != nullptr)
openness = rootItem->getOpennessState();
createNewRootNode();
if (openness != nullptr && rootItem != nullptr)
rootItem->restoreOpennessState (*openness);
}
void createNewRootNode()
{
// clear the current tree
resultsTree.setRootItem (nullptr);
rootItem.reset();
// try and parse the editor's contents
switch (typeBox.getSelectedItemIndex())
{
case xml: rootItem.reset (rebuildXml()); break;
case json: rootItem.reset (rebuildJson()); break;
default: rootItem.reset(); break;
}
// if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
if (rootItem != nullptr)
errorMessage.clear();
errorMessage.setVisible (! errorMessage.isEmpty());
resultsTree.setRootItem (rootItem.get());
}
/** Parses the editor's contents as XML. */
TreeViewItem* rebuildXml()
{
parsedXml.reset();
XmlDocument doc (codeDocument.getAllContent());
parsedXml = doc.getDocumentElement();
if (parsedXml.get() == nullptr)
{
auto error = doc.getLastParseError();
if (error.isEmpty())
error = "Unknown error";
errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
return nullptr;
}
return new XmlTreeItem (*parsedXml);
}
/** Parses the editor's contents as JSON. */
TreeViewItem* rebuildJson()
{
var parsedJson;
auto result = JSON::parse (codeDocument.getAllContent(), parsedJson);
if (! result.wasOk())
{
errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
return nullptr;
}
return new JsonTreeItem (Identifier(), parsedJson);
}
/** Clears the editor and loads some default text. */
void reset (Type type)
{
switch (type)
{
case xml: codeDocument.replaceAllContent (loadEntireAssetIntoString ("treedemo.xml")); break;
case json: codeDocument.replaceAllContent (loadEntireAssetIntoString ("juce_module_info")); break;
default: codeDocument.replaceAllContent ({}); break;
}
}
void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XMLandJSONDemo)
};