In progress XML parser (xml writer class)

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
0
down vote

favorite












I was wondering if someone wouldn't mind reviewing this XML writer code that I've been working on. It should be fully functional, and includes an example of how to use it.



ConsoleApplication1.cpp



// ConsoleApplication1.cpp : Defines the entry point for the console application. 
//

#include "stdafx.h"
#include "XMLReader.h"
#include "XMLWriter.h"
#include "XMLErr.h"
#include <Windows.h>

int main()

XMLWriter doc("test.xml");
doc.CreateDecl("1.0", "utf-8", "true");
doc.CreateRootNode("XML", "id", "test");
XMLNode* childNode = new XMLNode("ACHIEVEMENT");
doc.InsertNode(childNode);
doc.InsertAttribute(childNode, "TestAttr", "test");

XMLNode* newNode = new XMLNode("TEST", "testinnertext");
childNode->AddChildNode(newNode);

XMLAttribute* attr = new XMLAttribute("test", "0");
newNode->AddAttribute(attr);

doc.WriteDoc();
doc.Close();



XMLWriter.cpp



#include "stdafx.h" 
#include "XMLWriter.h"

XMLWriter::XMLWriter(const char* fileName)

file = std::ofstream(fileName);


XMLWriter::~XMLWriter()




void XMLWriter::Close()

if (file.is_open())
file.close();

rootNode->Close();


void XMLWriter::Save()



void XMLWriter::CreateDecl(const char* version, const char* encoding, bool standalone)

file << "<?xml version=" << """ << version << "" ";
if (encoding != nullptr)
file << "encoding=" << """ << encoding << "" ";
if (standalone != false)
file << "standalone=" << """ << "true" << """;

file << "?>" << "n";


void XMLWriter::CreateRootNode(const char* rootName, const char* attrName, const char* attrValue)

rootNode = new XMLNode();

rootNode->SetName(rootName);
if (attrName != nullptr && attrValue != nullptr)

XMLAttribute* attribute = new XMLAttribute(attrName,attrValue);
attribute->SetName(attrName);
attribute->SetValue(attrValue);
rootNode->AddAttribute(attribute);

//file << "<" << rootName << " " << attrName << "=" << """ << attrValue << """ << ">" << "n";


else
//file << "<" << rootName << ">"; //<< "n" << "</" << rootName << ">";



void XMLWriter::InsertNode(XMLNode* child)

rootNode->AddChildNode(child);


void XMLWriter::InsertNode(const char* nodeName, const char* nodeValue)

XMLNode* node = new XMLNode(nodeName, nodeValue);

rootNode->AddChildNode(node);


void XMLWriter::InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue)

target->AddAttribute(attrName, attrValue);


void XMLWriter::WriteDoc()

//TODO: get this gross ass shit functional
file << "<" << rootNode->GetName();
for (auto it : rootNode->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;

file << ">";

WriteNodesRecursively(rootNode);

file << "</" << rootNode->GetName() << ">";


void XMLWriter::WriteNodesRecursively(XMLNode* currNode)

for (auto itor : currNode->GetChildNodes())

file << "n";

file << "t" << "<" << itor->GetName();
for (auto it : itor->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;


if (itor->GetInnerText().length() > 0 && itor->GetChildNodes().size() == 0)

file << ">";
file << itor->GetInnerText();
file << "</" << itor->GetName() << ">" << "n";

else if (itor->GetChildNodes().size() > 0)

file << ">";
WriteNodesRecursively(itor);
file << "t" << "</" << itor->GetName() << ">" << "n";






XMLWriter.h



#pragma once 
#include "XMLNode.h"
#include "XMLAttribute.h"

class XMLWriter

private:
std::ofstream file;
XMLNode* rootNode;
public:
XMLWriter() = default;
XMLWriter(const char* fileName);
~XMLWriter();
void Close();
void Save();
void WriteDoc();
void WriteNodesRecursively(XMLNode* node);
public:

XMLNode * GetRootNode() return rootNode;
void CreateDecl(const char* version, const char* encoding = 0, bool standalone = 0);
void CreateRootNode(const char* nodeName, const char* attrName = 0, const char* attrValue = 0);

///All 3 of these are used only to add a childnode to rootnode, otherwise use XMLNodes AddChildNode function
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void InsertNode(XMLNode* child); //
void InsertNode(const char* nodeName, const char* attrValue = 0); //
void InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue); //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;


XMLNode.cpp



#include "stdafx.h" 
#include "XMLNode.h"

XMLNode::XMLNode(const char* nodeName, const char* nodeInnerText)

name = nodeName;
if (nodeInnerText != nullptr)
innerText = nodeInnerText;


XMLNode::~XMLNode()




///this is called from XMLReader, delete child node and any child nodes that exists within the instance
void XMLNode::Close()

for (auto& attribute : attributes)
delete attribute;

for (auto& childNode : childNodes)
childNode->Close();

attributes.clear();
childNodes.clear();

delete this;


void XMLNode::AddAttribute(XMLAttribute* attribute)

attributes.push_back(attribute);



void XMLNode::AddAttribute(const char* attrName, const char* attrValue)

XMLAttribute* attr = new XMLAttribute(attrName, attrValue);

attributes.push_back(attr);



void XMLNode::AddChildNode(XMLNode* childNode)

childNodes.push_back(childNode);


XMLAttribute* XMLNode::GetAttribute(const std::string attrName)

for (auto attr : attributes)

if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0)
return attr;

return nullptr;


XMLNode* XMLNode::GetChildNode(int index)

if (childNodes.empty())
return nullptr;

return childNodes.at(index);


bool XMLNode::FindChild(XMLNode* node)

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



XMLNode.h



#pragma once 
#include "XMLAttribute.h"

class XMLNode


private:
std::string name;
std::string innerText;
std::vector<XMLAttribute*> attributes;
std::vector<XMLNode*> childNodes;
public:
XMLNode() = default;
XMLNode(const char* nodeName, const char* nodeInnerText = 0);
~XMLNode();

void Close();

void SetName(const std::string value) name = value;
const std::string GetName() const return name;

void SetInnerText(const std::string value) innerText = value;
const std::string GetInnerText() const return innerText;

//give users a choice of either adding a pointer to the function, or creating the pointer in the function, then adding the pointer to the node
void AddAttribute(XMLAttribute* attribute);
void AddAttribute(const char* attrName, const char* attrValue);
void AddChildNode(XMLNode* childNode);
//

XMLAttribute* GetAttribute(const std::string attrName);
XMLNode* GetChildNode(int index);

const std::vector<XMLAttribute*>& GetAttributes() return attributes;

bool FindChild(XMLNode* node);

const std::vector<XMLNode*>& GetChildNodes() const return childNodes;
;


XMLAttribute.cpp



#include "stdafx.h" 
#include "XMLAttribute.h"

XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue)

name = attrName;
value = attrValue;


bool XMLAttribute::CheckValue()

for (auto c : value)

if (!isdigit(c))

throw(std::exception("Error, value is not an integer, or contains a non-numeric value"));


return true;


const int XMLAttribute::AsInt()

if (!CheckValue())
throw std::exception("Error, attributes value contains a non-numerical character");

if (std::stoi(value) > INT_MAX

const unsigned int XMLAttribute::AsUInt()


const float XMLAttribute::AsFloat()
std::stof(value) < FLT_MIN)
throw(std::out_of_range::exception("Error, value is outside the range of a float"));

return std::stof(value);


const double XMLAttribute::AsDouble()



XMLAttribute.h



#pragma once 


class XMLAttribute

private:
std::string value;
std::string name;
public:
XMLAttribute(const char* attrName, const char* attrValue);
void SetValue(const std::string inValue) value = inValue;
void SetName(const std::string inName) name = inName;
const std::string GetValue() const return value;
const std::string GetName() const return name;

///verifies that the string contains only numeric characters
bool CheckValue();

///conversion functions for the attributes value
///////////////////////////////////////////////////////////////////////////////////////
const int AsInt(); //
const unsigned int AsUInt(); //
const float AsFloat(); //
const double AsDouble(); //
///////////////////////////////////////////////////////////////////////////////////////

;






share|improve this question

















  • 1




    When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
    – Sam Onela
    Jan 4 at 23:11










  • As long as this parser calls find_first_of, it cannot be correct.
    – Roland Illig
    Jan 5 at 5:12







  • 1




    Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
    – Roland Illig
    Jan 5 at 5:15







  • 3




    You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
    – Snowbody
    Jan 5 at 17:12






  • 1




    Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
    – Dannnno
    Jan 9 at 16:35
















up vote
0
down vote

favorite












I was wondering if someone wouldn't mind reviewing this XML writer code that I've been working on. It should be fully functional, and includes an example of how to use it.



ConsoleApplication1.cpp



// ConsoleApplication1.cpp : Defines the entry point for the console application. 
//

#include "stdafx.h"
#include "XMLReader.h"
#include "XMLWriter.h"
#include "XMLErr.h"
#include <Windows.h>

int main()

XMLWriter doc("test.xml");
doc.CreateDecl("1.0", "utf-8", "true");
doc.CreateRootNode("XML", "id", "test");
XMLNode* childNode = new XMLNode("ACHIEVEMENT");
doc.InsertNode(childNode);
doc.InsertAttribute(childNode, "TestAttr", "test");

XMLNode* newNode = new XMLNode("TEST", "testinnertext");
childNode->AddChildNode(newNode);

XMLAttribute* attr = new XMLAttribute("test", "0");
newNode->AddAttribute(attr);

doc.WriteDoc();
doc.Close();



XMLWriter.cpp



#include "stdafx.h" 
#include "XMLWriter.h"

XMLWriter::XMLWriter(const char* fileName)

file = std::ofstream(fileName);


XMLWriter::~XMLWriter()




void XMLWriter::Close()

if (file.is_open())
file.close();

rootNode->Close();


void XMLWriter::Save()



void XMLWriter::CreateDecl(const char* version, const char* encoding, bool standalone)

file << "<?xml version=" << """ << version << "" ";
if (encoding != nullptr)
file << "encoding=" << """ << encoding << "" ";
if (standalone != false)
file << "standalone=" << """ << "true" << """;

file << "?>" << "n";


void XMLWriter::CreateRootNode(const char* rootName, const char* attrName, const char* attrValue)

rootNode = new XMLNode();

rootNode->SetName(rootName);
if (attrName != nullptr && attrValue != nullptr)

XMLAttribute* attribute = new XMLAttribute(attrName,attrValue);
attribute->SetName(attrName);
attribute->SetValue(attrValue);
rootNode->AddAttribute(attribute);

//file << "<" << rootName << " " << attrName << "=" << """ << attrValue << """ << ">" << "n";


else
//file << "<" << rootName << ">"; //<< "n" << "</" << rootName << ">";



void XMLWriter::InsertNode(XMLNode* child)

rootNode->AddChildNode(child);


void XMLWriter::InsertNode(const char* nodeName, const char* nodeValue)

XMLNode* node = new XMLNode(nodeName, nodeValue);

rootNode->AddChildNode(node);


void XMLWriter::InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue)

target->AddAttribute(attrName, attrValue);


void XMLWriter::WriteDoc()

//TODO: get this gross ass shit functional
file << "<" << rootNode->GetName();
for (auto it : rootNode->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;

file << ">";

WriteNodesRecursively(rootNode);

file << "</" << rootNode->GetName() << ">";


void XMLWriter::WriteNodesRecursively(XMLNode* currNode)

for (auto itor : currNode->GetChildNodes())

file << "n";

file << "t" << "<" << itor->GetName();
for (auto it : itor->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;


if (itor->GetInnerText().length() > 0 && itor->GetChildNodes().size() == 0)

file << ">";
file << itor->GetInnerText();
file << "</" << itor->GetName() << ">" << "n";

else if (itor->GetChildNodes().size() > 0)

file << ">";
WriteNodesRecursively(itor);
file << "t" << "</" << itor->GetName() << ">" << "n";






XMLWriter.h



#pragma once 
#include "XMLNode.h"
#include "XMLAttribute.h"

class XMLWriter

private:
std::ofstream file;
XMLNode* rootNode;
public:
XMLWriter() = default;
XMLWriter(const char* fileName);
~XMLWriter();
void Close();
void Save();
void WriteDoc();
void WriteNodesRecursively(XMLNode* node);
public:

XMLNode * GetRootNode() return rootNode;
void CreateDecl(const char* version, const char* encoding = 0, bool standalone = 0);
void CreateRootNode(const char* nodeName, const char* attrName = 0, const char* attrValue = 0);

///All 3 of these are used only to add a childnode to rootnode, otherwise use XMLNodes AddChildNode function
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void InsertNode(XMLNode* child); //
void InsertNode(const char* nodeName, const char* attrValue = 0); //
void InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue); //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;


XMLNode.cpp



#include "stdafx.h" 
#include "XMLNode.h"

XMLNode::XMLNode(const char* nodeName, const char* nodeInnerText)

name = nodeName;
if (nodeInnerText != nullptr)
innerText = nodeInnerText;


XMLNode::~XMLNode()




///this is called from XMLReader, delete child node and any child nodes that exists within the instance
void XMLNode::Close()

for (auto& attribute : attributes)
delete attribute;

for (auto& childNode : childNodes)
childNode->Close();

attributes.clear();
childNodes.clear();

delete this;


void XMLNode::AddAttribute(XMLAttribute* attribute)

attributes.push_back(attribute);



void XMLNode::AddAttribute(const char* attrName, const char* attrValue)

XMLAttribute* attr = new XMLAttribute(attrName, attrValue);

attributes.push_back(attr);



void XMLNode::AddChildNode(XMLNode* childNode)

childNodes.push_back(childNode);


XMLAttribute* XMLNode::GetAttribute(const std::string attrName)

for (auto attr : attributes)

if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0)
return attr;

return nullptr;


XMLNode* XMLNode::GetChildNode(int index)

if (childNodes.empty())
return nullptr;

return childNodes.at(index);


bool XMLNode::FindChild(XMLNode* node)

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



XMLNode.h



#pragma once 
#include "XMLAttribute.h"

class XMLNode


private:
std::string name;
std::string innerText;
std::vector<XMLAttribute*> attributes;
std::vector<XMLNode*> childNodes;
public:
XMLNode() = default;
XMLNode(const char* nodeName, const char* nodeInnerText = 0);
~XMLNode();

void Close();

void SetName(const std::string value) name = value;
const std::string GetName() const return name;

void SetInnerText(const std::string value) innerText = value;
const std::string GetInnerText() const return innerText;

//give users a choice of either adding a pointer to the function, or creating the pointer in the function, then adding the pointer to the node
void AddAttribute(XMLAttribute* attribute);
void AddAttribute(const char* attrName, const char* attrValue);
void AddChildNode(XMLNode* childNode);
//

XMLAttribute* GetAttribute(const std::string attrName);
XMLNode* GetChildNode(int index);

const std::vector<XMLAttribute*>& GetAttributes() return attributes;

bool FindChild(XMLNode* node);

const std::vector<XMLNode*>& GetChildNodes() const return childNodes;
;


XMLAttribute.cpp



#include "stdafx.h" 
#include "XMLAttribute.h"

XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue)

name = attrName;
value = attrValue;


bool XMLAttribute::CheckValue()

for (auto c : value)

if (!isdigit(c))

throw(std::exception("Error, value is not an integer, or contains a non-numeric value"));


return true;


const int XMLAttribute::AsInt()

if (!CheckValue())
throw std::exception("Error, attributes value contains a non-numerical character");

if (std::stoi(value) > INT_MAX

const unsigned int XMLAttribute::AsUInt()


const float XMLAttribute::AsFloat()
std::stof(value) < FLT_MIN)
throw(std::out_of_range::exception("Error, value is outside the range of a float"));

return std::stof(value);


const double XMLAttribute::AsDouble()



XMLAttribute.h



#pragma once 


class XMLAttribute

private:
std::string value;
std::string name;
public:
XMLAttribute(const char* attrName, const char* attrValue);
void SetValue(const std::string inValue) value = inValue;
void SetName(const std::string inName) name = inName;
const std::string GetValue() const return value;
const std::string GetName() const return name;

///verifies that the string contains only numeric characters
bool CheckValue();

///conversion functions for the attributes value
///////////////////////////////////////////////////////////////////////////////////////
const int AsInt(); //
const unsigned int AsUInt(); //
const float AsFloat(); //
const double AsDouble(); //
///////////////////////////////////////////////////////////////////////////////////////

;






share|improve this question

















  • 1




    When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
    – Sam Onela
    Jan 4 at 23:11










  • As long as this parser calls find_first_of, it cannot be correct.
    – Roland Illig
    Jan 5 at 5:12







  • 1




    Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
    – Roland Illig
    Jan 5 at 5:15







  • 3




    You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
    – Snowbody
    Jan 5 at 17:12






  • 1




    Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
    – Dannnno
    Jan 9 at 16:35












up vote
0
down vote

favorite









up vote
0
down vote

favorite











I was wondering if someone wouldn't mind reviewing this XML writer code that I've been working on. It should be fully functional, and includes an example of how to use it.



ConsoleApplication1.cpp



// ConsoleApplication1.cpp : Defines the entry point for the console application. 
//

#include "stdafx.h"
#include "XMLReader.h"
#include "XMLWriter.h"
#include "XMLErr.h"
#include <Windows.h>

int main()

XMLWriter doc("test.xml");
doc.CreateDecl("1.0", "utf-8", "true");
doc.CreateRootNode("XML", "id", "test");
XMLNode* childNode = new XMLNode("ACHIEVEMENT");
doc.InsertNode(childNode);
doc.InsertAttribute(childNode, "TestAttr", "test");

XMLNode* newNode = new XMLNode("TEST", "testinnertext");
childNode->AddChildNode(newNode);

XMLAttribute* attr = new XMLAttribute("test", "0");
newNode->AddAttribute(attr);

doc.WriteDoc();
doc.Close();



XMLWriter.cpp



#include "stdafx.h" 
#include "XMLWriter.h"

XMLWriter::XMLWriter(const char* fileName)

file = std::ofstream(fileName);


XMLWriter::~XMLWriter()




void XMLWriter::Close()

if (file.is_open())
file.close();

rootNode->Close();


void XMLWriter::Save()



void XMLWriter::CreateDecl(const char* version, const char* encoding, bool standalone)

file << "<?xml version=" << """ << version << "" ";
if (encoding != nullptr)
file << "encoding=" << """ << encoding << "" ";
if (standalone != false)
file << "standalone=" << """ << "true" << """;

file << "?>" << "n";


void XMLWriter::CreateRootNode(const char* rootName, const char* attrName, const char* attrValue)

rootNode = new XMLNode();

rootNode->SetName(rootName);
if (attrName != nullptr && attrValue != nullptr)

XMLAttribute* attribute = new XMLAttribute(attrName,attrValue);
attribute->SetName(attrName);
attribute->SetValue(attrValue);
rootNode->AddAttribute(attribute);

//file << "<" << rootName << " " << attrName << "=" << """ << attrValue << """ << ">" << "n";


else
//file << "<" << rootName << ">"; //<< "n" << "</" << rootName << ">";



void XMLWriter::InsertNode(XMLNode* child)

rootNode->AddChildNode(child);


void XMLWriter::InsertNode(const char* nodeName, const char* nodeValue)

XMLNode* node = new XMLNode(nodeName, nodeValue);

rootNode->AddChildNode(node);


void XMLWriter::InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue)

target->AddAttribute(attrName, attrValue);


void XMLWriter::WriteDoc()

//TODO: get this gross ass shit functional
file << "<" << rootNode->GetName();
for (auto it : rootNode->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;

file << ">";

WriteNodesRecursively(rootNode);

file << "</" << rootNode->GetName() << ">";


void XMLWriter::WriteNodesRecursively(XMLNode* currNode)

for (auto itor : currNode->GetChildNodes())

file << "n";

file << "t" << "<" << itor->GetName();
for (auto it : itor->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;


if (itor->GetInnerText().length() > 0 && itor->GetChildNodes().size() == 0)

file << ">";
file << itor->GetInnerText();
file << "</" << itor->GetName() << ">" << "n";

else if (itor->GetChildNodes().size() > 0)

file << ">";
WriteNodesRecursively(itor);
file << "t" << "</" << itor->GetName() << ">" << "n";






XMLWriter.h



#pragma once 
#include "XMLNode.h"
#include "XMLAttribute.h"

class XMLWriter

private:
std::ofstream file;
XMLNode* rootNode;
public:
XMLWriter() = default;
XMLWriter(const char* fileName);
~XMLWriter();
void Close();
void Save();
void WriteDoc();
void WriteNodesRecursively(XMLNode* node);
public:

XMLNode * GetRootNode() return rootNode;
void CreateDecl(const char* version, const char* encoding = 0, bool standalone = 0);
void CreateRootNode(const char* nodeName, const char* attrName = 0, const char* attrValue = 0);

///All 3 of these are used only to add a childnode to rootnode, otherwise use XMLNodes AddChildNode function
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void InsertNode(XMLNode* child); //
void InsertNode(const char* nodeName, const char* attrValue = 0); //
void InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue); //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;


XMLNode.cpp



#include "stdafx.h" 
#include "XMLNode.h"

XMLNode::XMLNode(const char* nodeName, const char* nodeInnerText)

name = nodeName;
if (nodeInnerText != nullptr)
innerText = nodeInnerText;


XMLNode::~XMLNode()




///this is called from XMLReader, delete child node and any child nodes that exists within the instance
void XMLNode::Close()

for (auto& attribute : attributes)
delete attribute;

for (auto& childNode : childNodes)
childNode->Close();

attributes.clear();
childNodes.clear();

delete this;


void XMLNode::AddAttribute(XMLAttribute* attribute)

attributes.push_back(attribute);



void XMLNode::AddAttribute(const char* attrName, const char* attrValue)

XMLAttribute* attr = new XMLAttribute(attrName, attrValue);

attributes.push_back(attr);



void XMLNode::AddChildNode(XMLNode* childNode)

childNodes.push_back(childNode);


XMLAttribute* XMLNode::GetAttribute(const std::string attrName)

for (auto attr : attributes)

if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0)
return attr;

return nullptr;


XMLNode* XMLNode::GetChildNode(int index)

if (childNodes.empty())
return nullptr;

return childNodes.at(index);


bool XMLNode::FindChild(XMLNode* node)

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



XMLNode.h



#pragma once 
#include "XMLAttribute.h"

class XMLNode


private:
std::string name;
std::string innerText;
std::vector<XMLAttribute*> attributes;
std::vector<XMLNode*> childNodes;
public:
XMLNode() = default;
XMLNode(const char* nodeName, const char* nodeInnerText = 0);
~XMLNode();

void Close();

void SetName(const std::string value) name = value;
const std::string GetName() const return name;

void SetInnerText(const std::string value) innerText = value;
const std::string GetInnerText() const return innerText;

//give users a choice of either adding a pointer to the function, or creating the pointer in the function, then adding the pointer to the node
void AddAttribute(XMLAttribute* attribute);
void AddAttribute(const char* attrName, const char* attrValue);
void AddChildNode(XMLNode* childNode);
//

XMLAttribute* GetAttribute(const std::string attrName);
XMLNode* GetChildNode(int index);

const std::vector<XMLAttribute*>& GetAttributes() return attributes;

bool FindChild(XMLNode* node);

const std::vector<XMLNode*>& GetChildNodes() const return childNodes;
;


XMLAttribute.cpp



#include "stdafx.h" 
#include "XMLAttribute.h"

XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue)

name = attrName;
value = attrValue;


bool XMLAttribute::CheckValue()

for (auto c : value)

if (!isdigit(c))

throw(std::exception("Error, value is not an integer, or contains a non-numeric value"));


return true;


const int XMLAttribute::AsInt()

if (!CheckValue())
throw std::exception("Error, attributes value contains a non-numerical character");

if (std::stoi(value) > INT_MAX

const unsigned int XMLAttribute::AsUInt()


const float XMLAttribute::AsFloat()
std::stof(value) < FLT_MIN)
throw(std::out_of_range::exception("Error, value is outside the range of a float"));

return std::stof(value);


const double XMLAttribute::AsDouble()



XMLAttribute.h



#pragma once 


class XMLAttribute

private:
std::string value;
std::string name;
public:
XMLAttribute(const char* attrName, const char* attrValue);
void SetValue(const std::string inValue) value = inValue;
void SetName(const std::string inName) name = inName;
const std::string GetValue() const return value;
const std::string GetName() const return name;

///verifies that the string contains only numeric characters
bool CheckValue();

///conversion functions for the attributes value
///////////////////////////////////////////////////////////////////////////////////////
const int AsInt(); //
const unsigned int AsUInt(); //
const float AsFloat(); //
const double AsDouble(); //
///////////////////////////////////////////////////////////////////////////////////////

;






share|improve this question













I was wondering if someone wouldn't mind reviewing this XML writer code that I've been working on. It should be fully functional, and includes an example of how to use it.



ConsoleApplication1.cpp



// ConsoleApplication1.cpp : Defines the entry point for the console application. 
//

#include "stdafx.h"
#include "XMLReader.h"
#include "XMLWriter.h"
#include "XMLErr.h"
#include <Windows.h>

int main()

XMLWriter doc("test.xml");
doc.CreateDecl("1.0", "utf-8", "true");
doc.CreateRootNode("XML", "id", "test");
XMLNode* childNode = new XMLNode("ACHIEVEMENT");
doc.InsertNode(childNode);
doc.InsertAttribute(childNode, "TestAttr", "test");

XMLNode* newNode = new XMLNode("TEST", "testinnertext");
childNode->AddChildNode(newNode);

XMLAttribute* attr = new XMLAttribute("test", "0");
newNode->AddAttribute(attr);

doc.WriteDoc();
doc.Close();



XMLWriter.cpp



#include "stdafx.h" 
#include "XMLWriter.h"

XMLWriter::XMLWriter(const char* fileName)

file = std::ofstream(fileName);


XMLWriter::~XMLWriter()




void XMLWriter::Close()

if (file.is_open())
file.close();

rootNode->Close();


void XMLWriter::Save()



void XMLWriter::CreateDecl(const char* version, const char* encoding, bool standalone)

file << "<?xml version=" << """ << version << "" ";
if (encoding != nullptr)
file << "encoding=" << """ << encoding << "" ";
if (standalone != false)
file << "standalone=" << """ << "true" << """;

file << "?>" << "n";


void XMLWriter::CreateRootNode(const char* rootName, const char* attrName, const char* attrValue)

rootNode = new XMLNode();

rootNode->SetName(rootName);
if (attrName != nullptr && attrValue != nullptr)

XMLAttribute* attribute = new XMLAttribute(attrName,attrValue);
attribute->SetName(attrName);
attribute->SetValue(attrValue);
rootNode->AddAttribute(attribute);

//file << "<" << rootName << " " << attrName << "=" << """ << attrValue << """ << ">" << "n";


else
//file << "<" << rootName << ">"; //<< "n" << "</" << rootName << ">";



void XMLWriter::InsertNode(XMLNode* child)

rootNode->AddChildNode(child);


void XMLWriter::InsertNode(const char* nodeName, const char* nodeValue)

XMLNode* node = new XMLNode(nodeName, nodeValue);

rootNode->AddChildNode(node);


void XMLWriter::InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue)

target->AddAttribute(attrName, attrValue);


void XMLWriter::WriteDoc()

//TODO: get this gross ass shit functional
file << "<" << rootNode->GetName();
for (auto it : rootNode->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;

file << ">";

WriteNodesRecursively(rootNode);

file << "</" << rootNode->GetName() << ">";


void XMLWriter::WriteNodesRecursively(XMLNode* currNode)

for (auto itor : currNode->GetChildNodes())

file << "n";

file << "t" << "<" << itor->GetName();
for (auto it : itor->GetAttributes())

file << " " << it->GetName() << "=" << """ << it->GetValue() << """;


if (itor->GetInnerText().length() > 0 && itor->GetChildNodes().size() == 0)

file << ">";
file << itor->GetInnerText();
file << "</" << itor->GetName() << ">" << "n";

else if (itor->GetChildNodes().size() > 0)

file << ">";
WriteNodesRecursively(itor);
file << "t" << "</" << itor->GetName() << ">" << "n";






XMLWriter.h



#pragma once 
#include "XMLNode.h"
#include "XMLAttribute.h"

class XMLWriter

private:
std::ofstream file;
XMLNode* rootNode;
public:
XMLWriter() = default;
XMLWriter(const char* fileName);
~XMLWriter();
void Close();
void Save();
void WriteDoc();
void WriteNodesRecursively(XMLNode* node);
public:

XMLNode * GetRootNode() return rootNode;
void CreateDecl(const char* version, const char* encoding = 0, bool standalone = 0);
void CreateRootNode(const char* nodeName, const char* attrName = 0, const char* attrValue = 0);

///All 3 of these are used only to add a childnode to rootnode, otherwise use XMLNodes AddChildNode function
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void InsertNode(XMLNode* child); //
void InsertNode(const char* nodeName, const char* attrValue = 0); //
void InsertAttribute(XMLNode* target, const char* attrName, const char* attrValue); //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;


XMLNode.cpp



#include "stdafx.h" 
#include "XMLNode.h"

XMLNode::XMLNode(const char* nodeName, const char* nodeInnerText)

name = nodeName;
if (nodeInnerText != nullptr)
innerText = nodeInnerText;


XMLNode::~XMLNode()




///this is called from XMLReader, delete child node and any child nodes that exists within the instance
void XMLNode::Close()

for (auto& attribute : attributes)
delete attribute;

for (auto& childNode : childNodes)
childNode->Close();

attributes.clear();
childNodes.clear();

delete this;


void XMLNode::AddAttribute(XMLAttribute* attribute)

attributes.push_back(attribute);



void XMLNode::AddAttribute(const char* attrName, const char* attrValue)

XMLAttribute* attr = new XMLAttribute(attrName, attrValue);

attributes.push_back(attr);



void XMLNode::AddChildNode(XMLNode* childNode)

childNodes.push_back(childNode);


XMLAttribute* XMLNode::GetAttribute(const std::string attrName)

for (auto attr : attributes)

if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0)
return attr;

return nullptr;


XMLNode* XMLNode::GetChildNode(int index)

if (childNodes.empty())
return nullptr;

return childNodes.at(index);


bool XMLNode::FindChild(XMLNode* node)

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



XMLNode.h



#pragma once 
#include "XMLAttribute.h"

class XMLNode


private:
std::string name;
std::string innerText;
std::vector<XMLAttribute*> attributes;
std::vector<XMLNode*> childNodes;
public:
XMLNode() = default;
XMLNode(const char* nodeName, const char* nodeInnerText = 0);
~XMLNode();

void Close();

void SetName(const std::string value) name = value;
const std::string GetName() const return name;

void SetInnerText(const std::string value) innerText = value;
const std::string GetInnerText() const return innerText;

//give users a choice of either adding a pointer to the function, or creating the pointer in the function, then adding the pointer to the node
void AddAttribute(XMLAttribute* attribute);
void AddAttribute(const char* attrName, const char* attrValue);
void AddChildNode(XMLNode* childNode);
//

XMLAttribute* GetAttribute(const std::string attrName);
XMLNode* GetChildNode(int index);

const std::vector<XMLAttribute*>& GetAttributes() return attributes;

bool FindChild(XMLNode* node);

const std::vector<XMLNode*>& GetChildNodes() const return childNodes;
;


XMLAttribute.cpp



#include "stdafx.h" 
#include "XMLAttribute.h"

XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue)

name = attrName;
value = attrValue;


bool XMLAttribute::CheckValue()

for (auto c : value)

if (!isdigit(c))

throw(std::exception("Error, value is not an integer, or contains a non-numeric value"));


return true;


const int XMLAttribute::AsInt()

if (!CheckValue())
throw std::exception("Error, attributes value contains a non-numerical character");

if (std::stoi(value) > INT_MAX

const unsigned int XMLAttribute::AsUInt()


const float XMLAttribute::AsFloat()
std::stof(value) < FLT_MIN)
throw(std::out_of_range::exception("Error, value is outside the range of a float"));

return std::stof(value);


const double XMLAttribute::AsDouble()



XMLAttribute.h



#pragma once 


class XMLAttribute

private:
std::string value;
std::string name;
public:
XMLAttribute(const char* attrName, const char* attrValue);
void SetValue(const std::string inValue) value = inValue;
void SetName(const std::string inName) name = inName;
const std::string GetValue() const return value;
const std::string GetName() const return name;

///verifies that the string contains only numeric characters
bool CheckValue();

///conversion functions for the attributes value
///////////////////////////////////////////////////////////////////////////////////////
const int AsInt(); //
const unsigned int AsUInt(); //
const float AsFloat(); //
const double AsDouble(); //
///////////////////////////////////////////////////////////////////////////////////////

;








share|improve this question












share|improve this question




share|improve this question








edited Jan 9 at 18:05









Edward

44.4k374202




44.4k374202









asked Jan 4 at 22:54









Jeff

132




132







  • 1




    When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
    – Sam Onela
    Jan 4 at 23:11










  • As long as this parser calls find_first_of, it cannot be correct.
    – Roland Illig
    Jan 5 at 5:12







  • 1




    Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
    – Roland Illig
    Jan 5 at 5:15







  • 3




    You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
    – Snowbody
    Jan 5 at 17:12






  • 1




    Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
    – Dannnno
    Jan 9 at 16:35












  • 1




    When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
    – Sam Onela
    Jan 4 at 23:11










  • As long as this parser calls find_first_of, it cannot be correct.
    – Roland Illig
    Jan 5 at 5:12







  • 1




    Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
    – Roland Illig
    Jan 5 at 5:15







  • 3




    You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
    – Snowbody
    Jan 5 at 17:12






  • 1




    Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
    – Dannnno
    Jan 9 at 16:35







1




1




When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
– Sam Onela
Jan 4 at 23:11




When the attributes function does grab attributes that contain whitespace, please update your post. Until then, this post is off-topic
– Sam Onela
Jan 4 at 23:11












As long as this parser calls find_first_of, it cannot be correct.
– Roland Illig
Jan 5 at 5:12





As long as this parser calls find_first_of, it cannot be correct.
– Roland Illig
Jan 5 at 5:12





1




1




Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
– Roland Illig
Jan 5 at 5:15





Plus, the code doesn't compile since you didn't post the header file. Some test code would also be nice.
– Roland Illig
Jan 5 at 5:15





3




3




You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
– Snowbody
Jan 5 at 17:12




You need to post complete, working code. If you just want a review of part of the code, it still needs to be complete and working for the task you specify.
– Snowbody
Jan 5 at 17:12




1




1




Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
– Dannnno
Jan 9 at 16:35




Why are there so many blank lines? Is that how you format your code or is it a result of the copy-paste?
– Dannnno
Jan 9 at 16:35










1 Answer
1






active

oldest

votes

















up vote
0
down vote



accepted










It's good you have some working code, and good that you've asked for a review. There are number of things that may help you improve your code.



Use the required #includes



The code uses std::string, std::vector, etc. which means that it should have



#include <string>
#include <vector>


and others. Since you have followed the usual separation the implementation (.cpp file) from the interface (the .h file), put only those things in the header file that are required to understand the header. Any implementation details should go into the .cpp file if they don't already show up in the header. As an example, in XMLAttribute.cpp we should have:



#include <stdexcept>
#include <climits>
#include <cfloat>


while XMLAttribute.h should have this:



#include <string>


Eliminate unused headers



Nothing from the <Windows.h> or XMLReader.h or XMLErr.h appear to actually have been used within this code, so it would be best to eliminate those from the ConsoleApplication1 code. Also, I can't help but think that we can come up with a more descriptive file name for this code.



Use standard exceptions



In a few places in the code, there are non-standard exceptions like this:



throw(std::exception("Error, value is not an integer, or contains a non-numeric value")); 


There is no std::exception constructor that takes a std::string or const char * as an argument. Instead, we need to use one of the actual standard types:



throw std::domain_error("Error, value is not an integer, or contains a non-numeric value"); 


Also note that throw is a keyword and not a function call so the enclosing parentheses are not needed or wanted.



General portability



This code could be made portable if, in addition to the changes in the previous points, you omit the Windows-only include files #include "stdafx.h" and #include <Windows.h>



Use exceptions only for exceptional things



There use of exceptions is inconsistent. For instance, we have this:



bool XMLAttribute::CheckValue() const

for (auto c : value)

if (!isdigit(c))

throw std::domain_error("Error, value is not an integer, or contains a non-numeric value");


return true;



This code returns true or throws an exception. That's more than a bit strange, especially because it's only used internally. I'd suggest instead having it return a bool either way and leave it up to the caller to throw if needed, since the calling functions are written that way already anyway.



Use standard library functions



Instead of the code above for CheckValue, first, I'd rename it to something that makes it easy to interpret the result such as containsOnlyDigits. That way it's much easier to understand what it's checking and much more obvious to tell what a true return value means.



Second, use a standard function for this. Most convenient here would be to use a C++11 lambda function:



bool XMLAttribute::containsOnlyDigits() const 
return std::all_of(value.begin(), value.end(),
(char c) return std::isdigit(c); );



Don't specify const for return values



The XMLAttribute member functions AsInt(), AsUInt(), etc. return a value rather than a pointer or reference, but they don't alter the underlying XMLAttribute object. For that reason, instead of this:



const int AsInt(); // returns a const?


write this:



int AsInt() const; // does not alter object


Prefer modern initializers for constructors



The constructors could use the more modern initializer style rather than the old style you're currently using. Instead of this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) 

name = attrName;
value = attrValue;



you could instead write this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) :
valueattrValue,
nameattrName



Note also that the member items are initialized in declaration order rather than the order they're listed in the constructor, so to avoid any misunderstanding, it's best to write the constructor such that it uses the same order as the declaration.



Let the compiler create default destructor



The compiler will create a destructor by default which is essentially identical to what you've got, so you can simply omit both the declaraton and implementation from your code. If you want to make sure the user of the code understands that it's the default-constructed destructor, you can make it very clear by writing this:



~XMLNode() = default;


Use C++ idioms



This isn't Java, so having getters and setters for everything (e.g. GetValue(), SetValue()) is not the norm and is not necessarily a good interface. For instance, the XMLNode::getAttribute() function returns a non-const pointer to its copy of the attribute. That's a broken design for at least two reasons. First, it would allow the caller to arbitrarily alter the internals of the class. Second, if the XMLNode object goes out of scope and is destroyed before the returned pointer is dereferenced, you have undefined behavior. Better would probably be to return a copy.



Reconsider the interface



There are a number of peculiarities that make this interface less than ideal. One thing is that the nodes in this scheme are allowed to have idential attribute names. According to the W3C,




For interoperability, an XML processor MAY at user option issue a warning when more than one attribute-list declaration is provided for a given element type, or more than one attribute definition is provided for a given attribute, but this is not an error.




Also the XMLWriter is tied to a std::ofstream. What if I want to write to a std::stringstream instead? Better would be to have the Writer manage serialization of data to any std::ostream and allow the user to choose at runtime, independently of how/when the instance data is populated.



Avoid raw pointers



The code includes this function:



bool XMLNode::FindChild(XMLNode* node) 

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



Is it really intended to be able to use this code while passing nullptr into the function? If so, I'd suggest a check for nullptr. If not, use a const XMLNode &node argument and overload an XMLNode::operator==() function.



Understand the XML specification



The XMLNode::GetAttribute function inclues these lines



if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0) 
return attr;


In addition to the already mentioned problem about returning a pointer to internal object data, this seems to imply that XML attribute names are case insensitive. In fact, they are not, so this appears to be an error in understanding or applying the XML 1.0 specification. Also _stricmp is non-standard code.






share|improve this answer





















  • Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
    – Jeff
    Jan 10 at 2:08










  • Happy to help. In C++, if any class can get or set member data, just make the members public.
    – Edward
    Jan 10 at 3:44










  • Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
    – Jeff
    Jan 12 at 1:07










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f184318%2fin-progress-xml-parser-xml-writer-class%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
0
down vote



accepted










It's good you have some working code, and good that you've asked for a review. There are number of things that may help you improve your code.



Use the required #includes



The code uses std::string, std::vector, etc. which means that it should have



#include <string>
#include <vector>


and others. Since you have followed the usual separation the implementation (.cpp file) from the interface (the .h file), put only those things in the header file that are required to understand the header. Any implementation details should go into the .cpp file if they don't already show up in the header. As an example, in XMLAttribute.cpp we should have:



#include <stdexcept>
#include <climits>
#include <cfloat>


while XMLAttribute.h should have this:



#include <string>


Eliminate unused headers



Nothing from the <Windows.h> or XMLReader.h or XMLErr.h appear to actually have been used within this code, so it would be best to eliminate those from the ConsoleApplication1 code. Also, I can't help but think that we can come up with a more descriptive file name for this code.



Use standard exceptions



In a few places in the code, there are non-standard exceptions like this:



throw(std::exception("Error, value is not an integer, or contains a non-numeric value")); 


There is no std::exception constructor that takes a std::string or const char * as an argument. Instead, we need to use one of the actual standard types:



throw std::domain_error("Error, value is not an integer, or contains a non-numeric value"); 


Also note that throw is a keyword and not a function call so the enclosing parentheses are not needed or wanted.



General portability



This code could be made portable if, in addition to the changes in the previous points, you omit the Windows-only include files #include "stdafx.h" and #include <Windows.h>



Use exceptions only for exceptional things



There use of exceptions is inconsistent. For instance, we have this:



bool XMLAttribute::CheckValue() const

for (auto c : value)

if (!isdigit(c))

throw std::domain_error("Error, value is not an integer, or contains a non-numeric value");


return true;



This code returns true or throws an exception. That's more than a bit strange, especially because it's only used internally. I'd suggest instead having it return a bool either way and leave it up to the caller to throw if needed, since the calling functions are written that way already anyway.



Use standard library functions



Instead of the code above for CheckValue, first, I'd rename it to something that makes it easy to interpret the result such as containsOnlyDigits. That way it's much easier to understand what it's checking and much more obvious to tell what a true return value means.



Second, use a standard function for this. Most convenient here would be to use a C++11 lambda function:



bool XMLAttribute::containsOnlyDigits() const 
return std::all_of(value.begin(), value.end(),
(char c) return std::isdigit(c); );



Don't specify const for return values



The XMLAttribute member functions AsInt(), AsUInt(), etc. return a value rather than a pointer or reference, but they don't alter the underlying XMLAttribute object. For that reason, instead of this:



const int AsInt(); // returns a const?


write this:



int AsInt() const; // does not alter object


Prefer modern initializers for constructors



The constructors could use the more modern initializer style rather than the old style you're currently using. Instead of this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) 

name = attrName;
value = attrValue;



you could instead write this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) :
valueattrValue,
nameattrName



Note also that the member items are initialized in declaration order rather than the order they're listed in the constructor, so to avoid any misunderstanding, it's best to write the constructor such that it uses the same order as the declaration.



Let the compiler create default destructor



The compiler will create a destructor by default which is essentially identical to what you've got, so you can simply omit both the declaraton and implementation from your code. If you want to make sure the user of the code understands that it's the default-constructed destructor, you can make it very clear by writing this:



~XMLNode() = default;


Use C++ idioms



This isn't Java, so having getters and setters for everything (e.g. GetValue(), SetValue()) is not the norm and is not necessarily a good interface. For instance, the XMLNode::getAttribute() function returns a non-const pointer to its copy of the attribute. That's a broken design for at least two reasons. First, it would allow the caller to arbitrarily alter the internals of the class. Second, if the XMLNode object goes out of scope and is destroyed before the returned pointer is dereferenced, you have undefined behavior. Better would probably be to return a copy.



Reconsider the interface



There are a number of peculiarities that make this interface less than ideal. One thing is that the nodes in this scheme are allowed to have idential attribute names. According to the W3C,




For interoperability, an XML processor MAY at user option issue a warning when more than one attribute-list declaration is provided for a given element type, or more than one attribute definition is provided for a given attribute, but this is not an error.




Also the XMLWriter is tied to a std::ofstream. What if I want to write to a std::stringstream instead? Better would be to have the Writer manage serialization of data to any std::ostream and allow the user to choose at runtime, independently of how/when the instance data is populated.



Avoid raw pointers



The code includes this function:



bool XMLNode::FindChild(XMLNode* node) 

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



Is it really intended to be able to use this code while passing nullptr into the function? If so, I'd suggest a check for nullptr. If not, use a const XMLNode &node argument and overload an XMLNode::operator==() function.



Understand the XML specification



The XMLNode::GetAttribute function inclues these lines



if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0) 
return attr;


In addition to the already mentioned problem about returning a pointer to internal object data, this seems to imply that XML attribute names are case insensitive. In fact, they are not, so this appears to be an error in understanding or applying the XML 1.0 specification. Also _stricmp is non-standard code.






share|improve this answer





















  • Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
    – Jeff
    Jan 10 at 2:08










  • Happy to help. In C++, if any class can get or set member data, just make the members public.
    – Edward
    Jan 10 at 3:44










  • Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
    – Jeff
    Jan 12 at 1:07














up vote
0
down vote



accepted










It's good you have some working code, and good that you've asked for a review. There are number of things that may help you improve your code.



Use the required #includes



The code uses std::string, std::vector, etc. which means that it should have



#include <string>
#include <vector>


and others. Since you have followed the usual separation the implementation (.cpp file) from the interface (the .h file), put only those things in the header file that are required to understand the header. Any implementation details should go into the .cpp file if they don't already show up in the header. As an example, in XMLAttribute.cpp we should have:



#include <stdexcept>
#include <climits>
#include <cfloat>


while XMLAttribute.h should have this:



#include <string>


Eliminate unused headers



Nothing from the <Windows.h> or XMLReader.h or XMLErr.h appear to actually have been used within this code, so it would be best to eliminate those from the ConsoleApplication1 code. Also, I can't help but think that we can come up with a more descriptive file name for this code.



Use standard exceptions



In a few places in the code, there are non-standard exceptions like this:



throw(std::exception("Error, value is not an integer, or contains a non-numeric value")); 


There is no std::exception constructor that takes a std::string or const char * as an argument. Instead, we need to use one of the actual standard types:



throw std::domain_error("Error, value is not an integer, or contains a non-numeric value"); 


Also note that throw is a keyword and not a function call so the enclosing parentheses are not needed or wanted.



General portability



This code could be made portable if, in addition to the changes in the previous points, you omit the Windows-only include files #include "stdafx.h" and #include <Windows.h>



Use exceptions only for exceptional things



There use of exceptions is inconsistent. For instance, we have this:



bool XMLAttribute::CheckValue() const

for (auto c : value)

if (!isdigit(c))

throw std::domain_error("Error, value is not an integer, or contains a non-numeric value");


return true;



This code returns true or throws an exception. That's more than a bit strange, especially because it's only used internally. I'd suggest instead having it return a bool either way and leave it up to the caller to throw if needed, since the calling functions are written that way already anyway.



Use standard library functions



Instead of the code above for CheckValue, first, I'd rename it to something that makes it easy to interpret the result such as containsOnlyDigits. That way it's much easier to understand what it's checking and much more obvious to tell what a true return value means.



Second, use a standard function for this. Most convenient here would be to use a C++11 lambda function:



bool XMLAttribute::containsOnlyDigits() const 
return std::all_of(value.begin(), value.end(),
(char c) return std::isdigit(c); );



Don't specify const for return values



The XMLAttribute member functions AsInt(), AsUInt(), etc. return a value rather than a pointer or reference, but they don't alter the underlying XMLAttribute object. For that reason, instead of this:



const int AsInt(); // returns a const?


write this:



int AsInt() const; // does not alter object


Prefer modern initializers for constructors



The constructors could use the more modern initializer style rather than the old style you're currently using. Instead of this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) 

name = attrName;
value = attrValue;



you could instead write this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) :
valueattrValue,
nameattrName



Note also that the member items are initialized in declaration order rather than the order they're listed in the constructor, so to avoid any misunderstanding, it's best to write the constructor such that it uses the same order as the declaration.



Let the compiler create default destructor



The compiler will create a destructor by default which is essentially identical to what you've got, so you can simply omit both the declaraton and implementation from your code. If you want to make sure the user of the code understands that it's the default-constructed destructor, you can make it very clear by writing this:



~XMLNode() = default;


Use C++ idioms



This isn't Java, so having getters and setters for everything (e.g. GetValue(), SetValue()) is not the norm and is not necessarily a good interface. For instance, the XMLNode::getAttribute() function returns a non-const pointer to its copy of the attribute. That's a broken design for at least two reasons. First, it would allow the caller to arbitrarily alter the internals of the class. Second, if the XMLNode object goes out of scope and is destroyed before the returned pointer is dereferenced, you have undefined behavior. Better would probably be to return a copy.



Reconsider the interface



There are a number of peculiarities that make this interface less than ideal. One thing is that the nodes in this scheme are allowed to have idential attribute names. According to the W3C,




For interoperability, an XML processor MAY at user option issue a warning when more than one attribute-list declaration is provided for a given element type, or more than one attribute definition is provided for a given attribute, but this is not an error.




Also the XMLWriter is tied to a std::ofstream. What if I want to write to a std::stringstream instead? Better would be to have the Writer manage serialization of data to any std::ostream and allow the user to choose at runtime, independently of how/when the instance data is populated.



Avoid raw pointers



The code includes this function:



bool XMLNode::FindChild(XMLNode* node) 

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



Is it really intended to be able to use this code while passing nullptr into the function? If so, I'd suggest a check for nullptr. If not, use a const XMLNode &node argument and overload an XMLNode::operator==() function.



Understand the XML specification



The XMLNode::GetAttribute function inclues these lines



if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0) 
return attr;


In addition to the already mentioned problem about returning a pointer to internal object data, this seems to imply that XML attribute names are case insensitive. In fact, they are not, so this appears to be an error in understanding or applying the XML 1.0 specification. Also _stricmp is non-standard code.






share|improve this answer





















  • Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
    – Jeff
    Jan 10 at 2:08










  • Happy to help. In C++, if any class can get or set member data, just make the members public.
    – Edward
    Jan 10 at 3:44










  • Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
    – Jeff
    Jan 12 at 1:07












up vote
0
down vote



accepted







up vote
0
down vote



accepted






It's good you have some working code, and good that you've asked for a review. There are number of things that may help you improve your code.



Use the required #includes



The code uses std::string, std::vector, etc. which means that it should have



#include <string>
#include <vector>


and others. Since you have followed the usual separation the implementation (.cpp file) from the interface (the .h file), put only those things in the header file that are required to understand the header. Any implementation details should go into the .cpp file if they don't already show up in the header. As an example, in XMLAttribute.cpp we should have:



#include <stdexcept>
#include <climits>
#include <cfloat>


while XMLAttribute.h should have this:



#include <string>


Eliminate unused headers



Nothing from the <Windows.h> or XMLReader.h or XMLErr.h appear to actually have been used within this code, so it would be best to eliminate those from the ConsoleApplication1 code. Also, I can't help but think that we can come up with a more descriptive file name for this code.



Use standard exceptions



In a few places in the code, there are non-standard exceptions like this:



throw(std::exception("Error, value is not an integer, or contains a non-numeric value")); 


There is no std::exception constructor that takes a std::string or const char * as an argument. Instead, we need to use one of the actual standard types:



throw std::domain_error("Error, value is not an integer, or contains a non-numeric value"); 


Also note that throw is a keyword and not a function call so the enclosing parentheses are not needed or wanted.



General portability



This code could be made portable if, in addition to the changes in the previous points, you omit the Windows-only include files #include "stdafx.h" and #include <Windows.h>



Use exceptions only for exceptional things



There use of exceptions is inconsistent. For instance, we have this:



bool XMLAttribute::CheckValue() const

for (auto c : value)

if (!isdigit(c))

throw std::domain_error("Error, value is not an integer, or contains a non-numeric value");


return true;



This code returns true or throws an exception. That's more than a bit strange, especially because it's only used internally. I'd suggest instead having it return a bool either way and leave it up to the caller to throw if needed, since the calling functions are written that way already anyway.



Use standard library functions



Instead of the code above for CheckValue, first, I'd rename it to something that makes it easy to interpret the result such as containsOnlyDigits. That way it's much easier to understand what it's checking and much more obvious to tell what a true return value means.



Second, use a standard function for this. Most convenient here would be to use a C++11 lambda function:



bool XMLAttribute::containsOnlyDigits() const 
return std::all_of(value.begin(), value.end(),
(char c) return std::isdigit(c); );



Don't specify const for return values



The XMLAttribute member functions AsInt(), AsUInt(), etc. return a value rather than a pointer or reference, but they don't alter the underlying XMLAttribute object. For that reason, instead of this:



const int AsInt(); // returns a const?


write this:



int AsInt() const; // does not alter object


Prefer modern initializers for constructors



The constructors could use the more modern initializer style rather than the old style you're currently using. Instead of this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) 

name = attrName;
value = attrValue;



you could instead write this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) :
valueattrValue,
nameattrName



Note also that the member items are initialized in declaration order rather than the order they're listed in the constructor, so to avoid any misunderstanding, it's best to write the constructor such that it uses the same order as the declaration.



Let the compiler create default destructor



The compiler will create a destructor by default which is essentially identical to what you've got, so you can simply omit both the declaraton and implementation from your code. If you want to make sure the user of the code understands that it's the default-constructed destructor, you can make it very clear by writing this:



~XMLNode() = default;


Use C++ idioms



This isn't Java, so having getters and setters for everything (e.g. GetValue(), SetValue()) is not the norm and is not necessarily a good interface. For instance, the XMLNode::getAttribute() function returns a non-const pointer to its copy of the attribute. That's a broken design for at least two reasons. First, it would allow the caller to arbitrarily alter the internals of the class. Second, if the XMLNode object goes out of scope and is destroyed before the returned pointer is dereferenced, you have undefined behavior. Better would probably be to return a copy.



Reconsider the interface



There are a number of peculiarities that make this interface less than ideal. One thing is that the nodes in this scheme are allowed to have idential attribute names. According to the W3C,




For interoperability, an XML processor MAY at user option issue a warning when more than one attribute-list declaration is provided for a given element type, or more than one attribute definition is provided for a given attribute, but this is not an error.




Also the XMLWriter is tied to a std::ofstream. What if I want to write to a std::stringstream instead? Better would be to have the Writer manage serialization of data to any std::ostream and allow the user to choose at runtime, independently of how/when the instance data is populated.



Avoid raw pointers



The code includes this function:



bool XMLNode::FindChild(XMLNode* node) 

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



Is it really intended to be able to use this code while passing nullptr into the function? If so, I'd suggest a check for nullptr. If not, use a const XMLNode &node argument and overload an XMLNode::operator==() function.



Understand the XML specification



The XMLNode::GetAttribute function inclues these lines



if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0) 
return attr;


In addition to the already mentioned problem about returning a pointer to internal object data, this seems to imply that XML attribute names are case insensitive. In fact, they are not, so this appears to be an error in understanding or applying the XML 1.0 specification. Also _stricmp is non-standard code.






share|improve this answer













It's good you have some working code, and good that you've asked for a review. There are number of things that may help you improve your code.



Use the required #includes



The code uses std::string, std::vector, etc. which means that it should have



#include <string>
#include <vector>


and others. Since you have followed the usual separation the implementation (.cpp file) from the interface (the .h file), put only those things in the header file that are required to understand the header. Any implementation details should go into the .cpp file if they don't already show up in the header. As an example, in XMLAttribute.cpp we should have:



#include <stdexcept>
#include <climits>
#include <cfloat>


while XMLAttribute.h should have this:



#include <string>


Eliminate unused headers



Nothing from the <Windows.h> or XMLReader.h or XMLErr.h appear to actually have been used within this code, so it would be best to eliminate those from the ConsoleApplication1 code. Also, I can't help but think that we can come up with a more descriptive file name for this code.



Use standard exceptions



In a few places in the code, there are non-standard exceptions like this:



throw(std::exception("Error, value is not an integer, or contains a non-numeric value")); 


There is no std::exception constructor that takes a std::string or const char * as an argument. Instead, we need to use one of the actual standard types:



throw std::domain_error("Error, value is not an integer, or contains a non-numeric value"); 


Also note that throw is a keyword and not a function call so the enclosing parentheses are not needed or wanted.



General portability



This code could be made portable if, in addition to the changes in the previous points, you omit the Windows-only include files #include "stdafx.h" and #include <Windows.h>



Use exceptions only for exceptional things



There use of exceptions is inconsistent. For instance, we have this:



bool XMLAttribute::CheckValue() const

for (auto c : value)

if (!isdigit(c))

throw std::domain_error("Error, value is not an integer, or contains a non-numeric value");


return true;



This code returns true or throws an exception. That's more than a bit strange, especially because it's only used internally. I'd suggest instead having it return a bool either way and leave it up to the caller to throw if needed, since the calling functions are written that way already anyway.



Use standard library functions



Instead of the code above for CheckValue, first, I'd rename it to something that makes it easy to interpret the result such as containsOnlyDigits. That way it's much easier to understand what it's checking and much more obvious to tell what a true return value means.



Second, use a standard function for this. Most convenient here would be to use a C++11 lambda function:



bool XMLAttribute::containsOnlyDigits() const 
return std::all_of(value.begin(), value.end(),
(char c) return std::isdigit(c); );



Don't specify const for return values



The XMLAttribute member functions AsInt(), AsUInt(), etc. return a value rather than a pointer or reference, but they don't alter the underlying XMLAttribute object. For that reason, instead of this:



const int AsInt(); // returns a const?


write this:



int AsInt() const; // does not alter object


Prefer modern initializers for constructors



The constructors could use the more modern initializer style rather than the old style you're currently using. Instead of this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) 

name = attrName;
value = attrValue;



you could instead write this:



XMLAttribute::XMLAttribute(const char* attrName, const char* attrValue) :
valueattrValue,
nameattrName



Note also that the member items are initialized in declaration order rather than the order they're listed in the constructor, so to avoid any misunderstanding, it's best to write the constructor such that it uses the same order as the declaration.



Let the compiler create default destructor



The compiler will create a destructor by default which is essentially identical to what you've got, so you can simply omit both the declaraton and implementation from your code. If you want to make sure the user of the code understands that it's the default-constructed destructor, you can make it very clear by writing this:



~XMLNode() = default;


Use C++ idioms



This isn't Java, so having getters and setters for everything (e.g. GetValue(), SetValue()) is not the norm and is not necessarily a good interface. For instance, the XMLNode::getAttribute() function returns a non-const pointer to its copy of the attribute. That's a broken design for at least two reasons. First, it would allow the caller to arbitrarily alter the internals of the class. Second, if the XMLNode object goes out of scope and is destroyed before the returned pointer is dereferenced, you have undefined behavior. Better would probably be to return a copy.



Reconsider the interface



There are a number of peculiarities that make this interface less than ideal. One thing is that the nodes in this scheme are allowed to have idential attribute names. According to the W3C,




For interoperability, an XML processor MAY at user option issue a warning when more than one attribute-list declaration is provided for a given element type, or more than one attribute definition is provided for a given attribute, but this is not an error.




Also the XMLWriter is tied to a std::ofstream. What if I want to write to a std::stringstream instead? Better would be to have the Writer manage serialization of data to any std::ostream and allow the user to choose at runtime, independently of how/when the instance data is populated.



Avoid raw pointers



The code includes this function:



bool XMLNode::FindChild(XMLNode* node) 

for (auto itor : childNodes)

if (itor == node)
return true;

return false;



Is it really intended to be able to use this code while passing nullptr into the function? If so, I'd suggest a check for nullptr. If not, use a const XMLNode &node argument and overload an XMLNode::operator==() function.



Understand the XML specification



The XMLNode::GetAttribute function inclues these lines



if (_stricmp(attr->GetName().c_str(), attrName.c_str()) == 0) 
return attr;


In addition to the already mentioned problem about returning a pointer to internal object data, this seems to imply that XML attribute names are case insensitive. In fact, they are not, so this appears to be an error in understanding or applying the XML 1.0 specification. Also _stricmp is non-standard code.







share|improve this answer













share|improve this answer



share|improve this answer











answered Jan 9 at 20:56









Edward

44.4k374202




44.4k374202











  • Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
    – Jeff
    Jan 10 at 2:08










  • Happy to help. In C++, if any class can get or set member data, just make the members public.
    – Edward
    Jan 10 at 3:44










  • Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
    – Jeff
    Jan 12 at 1:07
















  • Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
    – Jeff
    Jan 10 at 2:08










  • Happy to help. In C++, if any class can get or set member data, just make the members public.
    – Edward
    Jan 10 at 3:44










  • Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
    – Jeff
    Jan 12 at 1:07















Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
– Jeff
Jan 10 at 2:08




Thanks so much for the suggestions, implementing several of them tonight, and i'll do the others tomorrow. I was wondering though, if getters/setters aren't a good practice for c++, how would you recommend setting/getting the values? just with the member variable?
– Jeff
Jan 10 at 2:08












Happy to help. In C++, if any class can get or set member data, just make the members public.
– Edward
Jan 10 at 3:44




Happy to help. In C++, if any class can get or set member data, just make the members public.
– Edward
Jan 10 at 3:44












Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
– Jeff
Jan 12 at 1:07




Will do, thanks. One last question if you don't mind. I'm having a bit of trouble figuring out whether or not I should use a shared_ptr or a unique_ptr. I've read about them a couple times and I'm still unsure which to use. based on this code, what would you recommend? A unique_ptr for the rootnode, and shared_ptrs for the childnodes?
– Jeff
Jan 12 at 1:07












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f184318%2fin-progress-xml-parser-xml-writer-class%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation