7
\$\begingroup\$

A while back, I posted an XML Writer. Today I have finished what I hope to be a solid XML parser that, while not professional, will get the job done.

To start, I do use a stack on one part of the parser, so you can find the code for that in the XML writer page I linked above.

Xml.h

#ifndef Xml_H
#define Xml_H

#include <fstream>
#include <iostream>
#include "LinkStack.h"
#include <string>
#include <vector>
#include <windows> //Error reporting with message boxes. 

namespace Xml {


    enum class ErrorCode {

        None,
        StreamError,
        FileExistError,
        ElementCloseError,
        ElementTagsNotEmpty

    };

    class Error {
    public:
        /**
           Constructor
        */
        Error();

        /**
            Throws Error Message and displays the message in messagebox
        */
        void ThrowError(ErrorCode errorCode);

    private:
        ErrorCode err;
        const char* errMsg;
    };

    Error::Error() :
        err(ErrorCode::None),
        errMsg(nullptr)
    {

    }

    void Error::ThrowError(ErrorCode errorCode) {
        err = errorCode;
        if (errorCode != ErrorCode::None) {

            switch (errorCode) {
            case ErrorCode::StreamError:
                errMsg = "Stream error has occured";
                break;
            case ErrorCode::FileExistError:
                errMsg = "File does not exist";
                break;
            case ErrorCode::ElementCloseError:
                errMsg = "No elements to close";
                break;
            case ErrorCode::ElementTagsNotEmpty:
                errMsg = "Not all element tags are closed. Closing remaining tags";
                break;
            }
            MessageBox(NULL, errMsg, "Error Has Occured", MB_ICONEXCLAMATION | MB_OK);
        }
        errMsg = nullptr;
    }

    class XmlReader : private Error {

    public:
        /**
            Child Node
        */
        struct cNode {
            std::vector<cNode> childNode;
            std::string strInnerText;
            std::vector<std::string> strAttributeName;
            std::vector<std::string> strAttributeValue;
            bool hasChildNode();
        };

        /**
            Parent Node
        */
        struct pNode {
            std::vector<cNode> childNode;
            std::string strInnerText;
            std::vector<std::string> strAttributeName;
            std::vector<std::string> strAttributeValue;
            bool hasChildNode();
        };

        /**
            Structure of parent and child nodes
        */
        struct node {
            std::vector<pNode> parentNode;
            std::string strInnerText;
            std::vector<std::string> strAttributeName;
            std::vector<std::string> strAttributeValue;
        };

        /**
            Constructor
        */
        XmlReader();

        /**
            Loads the file
        */
        bool load(std::string strFileName);

        /**
            Closed the file
        */
        void close();

        /*
            Gets Node by Tag Name
        */
        XmlReader::node getElementByTagName(std::string strTagName);

    private:
        /**
            Checks if the file exists
        */
        bool exists(std::string strFileName);

        /**
            Checks if the file is open
        */
        bool isOpen();

        /**
            Gets child nodes
        */
        XmlReader::cNode getChildNodes();

        /**
            Gets Attributes
        */
        void getAttributes(std::string& strOpenTag, std::string& strAttName, std::string& strAttValue);

        /*
            Get Inner Text
        */
        void getInnerText(std::string& strInLine, std::string& strInnerText);

        std::ifstream& goToLine(std::ifstream& file, int lineNUm);

        std::ifstream   m_ifsInFile;
        std::string     m_strParentNode;
        std::size_t     m_nLineNum;
        bool            m_bDocOpen;
        LinkStack<std::string> m_lsstrChildNodeName;
    };

    XmlReader::XmlReader() {
        m_bDocOpen = false;
        m_strParentNode = "";
        m_nLineNum = 0;
    }

    bool XmlReader::load(std::string strFileName) {
        if (!exists(strFileName)) {
            Error::ThrowError(ErrorCode::FileExistError);
            return false;
        }
        else {
            m_ifsInFile.open(strFileName, std::ios::in);
        }
        m_bDocOpen = isOpen();
        if (!m_bDocOpen) {
            Error::ThrowError(ErrorCode::StreamError);
            return false;
        }
        return true;
    }

    void XmlReader::close() {
        m_ifsInFile.close();
    }

    bool XmlReader::exists(std::string strFileName) {
        std::ifstream ifsCheckFile(strFileName);
        return !!ifsCheckFile;
    }

    bool XmlReader::isOpen() {
        if (!m_ifsInFile.is_open()) {
            return false;
        }
        return true;
    }

    XmlReader::node XmlReader::getElementByTagName(std::string strTagName) {
        XmlReader::node node;
        XmlReader::pNode parentNode;
        XmlReader::cNode childNode;
        std::string strInnerText;
        std::string strAttributeName;
        std::string strAttributeValue;
        std::size_t sz = strTagName.size();
        std::string strInLine;
        std::string strTmp;
        std::string strOpenTag;
        int posLt = 0;
        int posGt = 0;
        int posFAtr = 0;
        int posLAtr = 0;
        int posLastAtr = 0;
        m_strParentNode = strTagName;
        while (std::getline(m_ifsInFile, strInLine, '\n')) {
            parentNode.strAttributeName._Pop_back_n(parentNode.strAttributeName.size());
            parentNode.strAttributeValue._Pop_back_n(parentNode.strAttributeValue.size());
            m_nLineNum = m_ifsInFile.tellg();
            if (strInLine != "") {

                if (strInLine[0] == '\t') {
                    std::vector<int> tabPos;
                    for (int i = 0; i < strInLine.size(); i++) {
                        if (strInLine[i] == '\t') {
                            tabPos.push_back(i);
                        }
                    }
                    for (int i = tabPos.size(); i > 0; i--) {
                        strInLine.erase(tabPos[i - 1], 1);
                        tabPos.pop_back();
                    }
                }

                if (strInLine.substr(1, sz) == strTagName) {

                    posLt = strInLine.find_last_of("<");
                    posGt = strInLine.find_last_of(">");

                    strTmp = strInLine.substr(posLt + 1, posGt - posLt - 1);

                    if (strTmp == "/" + strTagName) {

                        posLt = strInLine.find_first_of("<");
                        posGt = strInLine.find_first_of(">");

                        strOpenTag = strInLine.substr(posLt + 1, posGt - posLt - 1);

                        if (strOpenTag.size() > sz) {

                            while (strOpenTag.size() > sz) {

                                getAttributes(strOpenTag, strAttributeName, strAttributeValue);
                                parentNode.strAttributeName.push_back(strAttributeName);
                                parentNode.strAttributeValue.push_back(strAttributeValue);

                            }
                        }
                        getInnerText(strInLine, strInnerText);
                        parentNode.strInnerText = strInnerText;

                    }
                    else {
                        childNode = getChildNodes();
                        parentNode.childNode.push_back(childNode);
                    }

                    node.parentNode.push_back(parentNode);

                }

            }

        }
        return node;
    }


    XmlReader::cNode XmlReader::getChildNodes() {
        XmlReader::cNode tmpNode;
        XmlReader::cNode childNode;
        std::string strInLine;
        std::string strInnerText;
        std::string strAttributeName;
        std::string strAttributeValue;
        std::string strTagName;
        std::string strEndTag;
        std::string strOpenTag;
        std::size_t sz;
        int posF = 0;
        int posL = 0;
        int posS = 0;
        int posLt = 0;
        int posGt = 0;
        bool endTag = false;

        if (m_nLineNum != m_ifsInFile.tellg()) {
            goToLine(m_ifsInFile, m_nLineNum);
        }

        while (!endTag) {

            std::getline(m_ifsInFile, strInLine, '\n');
            m_nLineNum = m_ifsInFile.tellg();

            if (strInLine != "") {
                posF = strInLine.find_first_of("<");
                posL = strInLine.find_first_of(">");
                posS = strInLine.find_first_of(" ");

                if (posS < posL && posS > posF) {
                    strTagName = strInLine.substr(posF + 1, posS - posF - 1);
                }
                else {
                    strTagName = strInLine.substr(posF + 1, posL - posF - 1);
                }

                if (strTagName.substr(0, 1) == "/") {
                    strTagName.erase(0, 1);
                }

                m_lsstrChildNodeName.push(strTagName);
                posF = strInLine.find_last_of("<");
                posL = strInLine.find_last_of(">");
                if (strInLine.substr(posF + 1, posL - posF - 1) != "/" + m_lsstrChildNodeName.top()) {

                    childNode.strAttributeName._Pop_back_n(childNode.strAttributeName.size());
                    childNode.strAttributeValue._Pop_back_n(childNode.strAttributeValue.size());

                    sz = strTagName.size();

                    if (strInLine.substr(1, sz) == strTagName) {

                        posLt = strInLine.find_first_of("<");
                        posGt = strInLine.find_first_of(">");

                        strOpenTag = strInLine.substr(posLt + 1, posGt - posLt - 1);

                        if (strOpenTag.size() > sz) {

                            while (strOpenTag.size() > sz) {

                                getAttributes(strOpenTag, strAttributeName, strAttributeValue);
                                childNode.strAttributeName.push_back(strAttributeName);
                                childNode.strAttributeValue.push_back(strAttributeValue);

                            }

                        }

                        posLt = strInLine.find_last_of("<");
                        posGt = strInLine.find_last_of(">");

                        strEndTag = strInLine.substr(posLt + 1, posGt - posLt - 1);

                        if (strEndTag == "/" + strTagName) {

                            getInnerText(strInLine, strInnerText);
                            childNode.strInnerText = strInnerText;

                        } else {

                            tmpNode = getChildNodes();
                            childNode.childNode.push_back(tmpNode);

                        }

                    } else {
                        m_lsstrChildNodeName.pop();
                        endTag = true;
                    }
                } else {
                    getInnerText(strInLine, strInnerText);
                    childNode.strInnerText = strInnerText;
                    m_lsstrChildNodeName.pop();
                    endTag = true;
                }
            }
        }

        return childNode;

    }

    void XmlReader::getAttributes(std::string& strOpenTag, std::string& strAttName, std::string& strAttValue) {
        int posF = 0;
        int posL = 0;

        posF = strOpenTag.find_first_of(" ");
        posL = strOpenTag.find_first_of("=");

        strAttName = strOpenTag.substr(posF + 1, posL - posF - 1);
        strOpenTag.erase(posF, posL - posF + 1);

        posF = strOpenTag.find_first_of("\"");
        posL = posF;
        posF = strOpenTag.find("\"", posF + 1);
        strAttValue = strOpenTag.substr(posL + 1, posF - posL - 1);
        strOpenTag.erase(posL, posF - posL + 1);
    }


    void XmlReader::getInnerText(std::string& strInLine, std::string& strInnerText) {
        int posLt = 0;
        int posGt = 0;

        posLt = strInLine.find_last_of("<");
        posGt = strInLine.find_first_of(">");

        strInnerText = strInLine.substr(posGt + 1, posLt - posGt - 1);
    }


    std::ifstream& XmlReader::goToLine(std::ifstream& file, int lineNum) {
        file.seekg(std::ios::beg);
        file.seekg(lineNum);

        return file;
    }


    bool XmlReader::pNode::hasChildNode() {
        if (XmlReader::pNode::childNode.size() != 0) {
            return true;
        }
        return false;
    }


    bool XmlReader::cNode::hasChildNode() {
        if (XmlReader::cNode::childNode.size() != 0) {
            return true;
        }
        return false;
    }

}

#endif

main.cpp

#include "Xml.h"

int main() {

    Xml::XmlReader xmlR;

    if (xmlR.load("path\\to\\xml\\doc")) {

        Xml::XmlReader::node xmlNode;

        xmlNode = xmlR.getElementByTagName("TagName");

        std::cout << xmlNode.parentNode[0].//enter rest of statement to get child nodes,
                                           //attributes, or what ever that is collected.

    }

}

I hope this is legible. I'm here to learn, so lay it on me.

\$\endgroup\$

3 Answers 3

6
\$\begingroup\$

Relying on specific type of error message deep inside your library makes it very tied to a specific platform.

void Error::ThrowError(ErrorCode errorCode) {

       // STUFF

        MessageBox(NULL, errMsg, "Error Has Occured", MB_ICONEXCLAMATION | MB_OK);

       // STUFF
}

Your library should not be handling user interactions. This is the job of the application. The library should just inform the application that an error has occurred and then let the application decide what to do (this code could be used in some background server app; you defiantly do not want a dialog box to show up on a headless server).

Two big options.

  • Throw an exception.
  • Allow the user to specify a callback that is called on error.
  • Or both. The default action of the callback (if not provide by the user) is to throw.

Do you really need a cNode, pNode and Node? They all look identical.

Comments like this:

    /**
        Checks if the file exists
    */
    bool exists(std::string strFileName);

    /**
        Checks if the file is open
    */
    bool isOpen();

are a waste of space. They are not part of the public interface and the function name clearly identifies what they do. Useless comments are worse than no comments as they need to be maintained.

The check for file existence seems overly hyperbolic:

    if (!exists(strFileName)) {
        Error::ThrowError(ErrorCode::FileExistError);
        return false;
    }
    else {
        m_ifsInFile.open(strFileName, std::ios::in);
    }

The call to exist merely tries to create a file stream object and returns its state. Why not just try and open the file then check its state.

    m_ifsInFile.open(strFileName, std::ios::in);

    if (!m_ifsInFile) {
        Error::ThrowError(ErrorCode::FileExistError);
        return false;
    }

Double negative check.

   return !!ifsCheckFile;

That's old school not seen that in a while. But its also not even needed. A stream object used in a boolean context (like returning a bool from a function) is converted to a boolean value (or pre C++11 a value that can be used in a boolean context).

So easier to write:

   return ifsCheckFile;  // And much easier to understand for those that have not
                         // seen the double negative check trick.

Don't use if to check the state of a boolen

    if (!m_ifsInFile.is_open()) {
        return false;
    }
    return true;

    // much easier to write as:

    return m_ifsInFile.is_open();
\$\endgroup\$
3
\$\begingroup\$

I would like to add to @LokiAstari's excellent answer

It is a good rule to write out all possible tags in a switch statement, you have one case ErrorCode::None which you do not handle also add a default as well to catch any invalid values that may be added in the future.

Your getChildNodes / getElementsByTagName functions I find are a bit too long, IMHO it would be better to split up the function in a couple of separate functions to make it more readable. (Also here you suddenly stopped having any comments). You have quite a few variables in your functions so separating the functionality into several small functions will help with that too.

When you write comments have in mind that the reader often wants to know why something is done, not what is being done since that is the purpose of the code.

When you initialize member variables do it in the form of

XmlReader::XmlReader() 
: m_strParentNode(""), m_nLineNum(0) , m_bDocOpen(false)  
{
}

Note also that the variables should be initialized in the order they are declared.

A tip is also to write Doxygen styled comments (doxygen.org), that way you can get a nicely formatted html document which is generated from your code.

A see a direct error also, you have not declared a virtual destructor in Error but you inherit from it, add a virtual dtor to avoid getting base pointer delete issues.

\$\endgroup\$
1
  • \$\begingroup\$ I think Error is used in a sort of "mixin" inheritance style only, so it should instead have a protected and non-virtual destructor. Good review anyway, +1. \$\endgroup\$
    – glampert
    Dec 23, 2014 at 0:12
0
\$\begingroup\$

Your function to get attribute is problematic.

Let's suppose the text to parse is that:

"<S/>= Hello! ="

The parser consider the "=" outside the tag "S" a attribute assigner, then it gets outside the tag.

Also, for example, when there's a quote, you're just taking the first.

"<S attr=/>= this is \" quote"

Then it gets outside de tag, considering the final of the text as the attribute value, e.g:

"<S attr=\" quote\">"

Solution: (assigner) search the value assigner through extraspaces or tab indent, so when a different character is identified just check if it's equal to the value assigner, else then you finish the attribute with value true (boolean). Do the same for the quotes search.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.