--- a
+++ b/cfgui/picoxml.h
@@ -0,0 +1,292 @@
+/* Copyright (C) 2016 J.F.Dockes
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _PICOXML_H_INCLUDED_
+#define _PICOXML_H_INCLUDED_
+
+/**
+ * PicoXMLParser: a single include file parser for an XML-like, but
+ * restricted language, adequate for config files, not for arbitrary
+ * externally generated data.
+ *
+ * - The code depends on nothing but the "classical" C++ standard
+ * library (c++11 not necessary).
+ * - The input to the parser is a single c++ string. Does not deal with
+ * input in several pieces or files.
+ * - SAX mode only. You have access to the tag stack. I've always
+ * found DOM mode less usable.
+ * - Checks for proper tag nesting and not much else.
+ * - ! No CDATA
+ * - ! Attributes should really really not contain XML special chars.
+ * - Entity decoding is left as an exercise to the user.
+ *
+ * A typical input would be like the following (you can add XML
+ * declarations, whitespace and newlines to taste).
+ *
+ * <top>top chrs1<sub attr="attrval">sub chrs</sub>top chrs2 <emptyelt /></top>
+ *
+ * Usage: subclass PicoXMLParser, overriding the methods in the
+ * "protected:" section (look there for more details), call the
+ * constructor with your input, then call parse().
+ */
+
+#include <string>
+#include <vector>
+#include <map>
+#include <sstream>
+#include <iostream>
+
+class PicoXMLParser {
+public:
+ PicoXMLParser(const std::string& input)
+ : m_in(input), m_pos(0) {
+ }
+ virtual ~PicoXMLParser() { }
+
+ virtual bool parse() {
+ // skip initial whitespace and XML decl. On success, returns with
+ // current pos on first tag '<'
+ if (!skipDecl()) {
+ return false;
+ }
+ if (nomore()) {
+ // empty file
+ return true;
+ }
+
+ for (;;) {
+ // Current char is '<' and the next char is not '?'
+ //std::cerr<< "m_pos "<< m_pos<<" char "<< m_in[m_pos] << std::endl;
+ m_pos++;
+ if (nomore()) {
+ m_reason << "EOF within tag";
+ return false;
+ }
+ std::string::size_type spos = m_pos;
+ int isendtag = m_in[m_pos] == '/' ? 1 : 0;
+
+ skipStr(">");
+ if (m_pos == std::string::npos || m_pos <= spos + 1) {
+ m_reason << "Empty tag or EOF inside tag. pos " << spos;
+ return false;
+ }
+
+ int emptyel = m_in[m_pos-2] == '/' ? 1 : 0;
+ if (emptyel && isendtag) {
+ m_reason << "Bad tag </xx/> at cpos " << spos;
+ return false;
+ }
+
+ std::string tag =
+ m_in.substr(spos + isendtag,
+ m_pos - (spos + 1 + isendtag + emptyel));
+ //std::cerr << "TAG NAME [" << tag << "]\n";
+ trimtag(tag);
+ std::map<std::string, std::string> attrs;
+ if (!parseattrs(tag, attrs)) {
+ return false;
+ }
+ if (isendtag) {
+ if (m_tagstack.empty() || tag.compare(m_tagstack.back())) {
+ m_reason << "Closing not open tag " << tag <<
+ " at cpos " << m_pos;
+ return false;
+ }
+ m_tagstack.pop_back();
+ endElement(tag);
+ } else {
+ startElement(tag, attrs);
+ m_tagstack.push_back(tag);
+ if (emptyel) {
+ m_tagstack.pop_back();
+ endElement(tag);
+ }
+ }
+ spos = m_pos;
+ m_pos = m_in.find("<", m_pos);
+ if (nomore()) {
+ if (!m_tagstack.empty()) {
+ m_reason << "EOF hit inside open element";
+ return false;
+ }
+ return true;
+ }
+ if (m_pos != spos) {
+ characterData(m_in.substr(spos, m_pos - spos));
+ }
+ }
+ return false;
+ }
+
+ virtual std::string getReason() {
+ return m_reason.str();
+ }
+
+protected:
+
+ /* Methods to be overriden */
+
+ /**
+ * Called when seeing an opening tag.
+ * @param tagname the tag name
+ * @param attrs a map of attribute name/value pairs
+ */
+ virtual void startElement(const std::string& /*tagname*/,
+ const std::map<std::string, std::string>&
+ /* attrs */) {
+ }
+
+ /**
+ * Called when closing a tag. You should probably have been
+ * accumulating text and stuff since the tag opening.
+ * @param tagname the tag name.
+ */
+ virtual void endElement(const std::string& /*tagname*/) {}
+
+ /*
+ * Called when we see non-tag data.
+ * @param data the data.
+ */
+ virtual void characterData(const std::string& /*data*/) {}
+
+ /*
+ * Gives access to the current path in the tree. Attributes are
+ * not kept in there though, you'll have to do this yourself.
+ * @return a const ref to a vector of tag names.
+ */
+ virtual const std::vector<std::string>& tagStack() {
+ return m_tagstack;
+ }
+
+
+private:
+
+ const std::string& m_in;
+ std::string::size_type m_pos;
+ std::stringstream m_reason;
+ std::vector<std::string> m_tagstack;
+
+ bool nomore() const {
+ return m_pos == std::string::npos || m_pos == m_in.size();
+ }
+ bool skipWS(const std::string& in, std::string::size_type& pos) {
+ if (pos == std::string::npos)
+ return false;
+ pos = in.find_first_not_of(" \t\n\r", pos);
+ return pos != std::string::npos;
+ }
+ bool skipStr(const std::string& str) {
+ if (m_pos == std::string::npos)
+ return false;
+ m_pos = m_in.find(str, m_pos);
+ if (m_pos != std::string::npos)
+ m_pos += str.size();
+ return m_pos != std::string::npos;
+ }
+ int peek() const {
+ if (nomore())
+ return -1;
+ return m_in[m_pos + 1];
+ }
+ void trimtag(std::string& tagname) {
+ std::string::size_type trimpos = tagname.find_last_not_of(" \t\n\r");
+ if (trimpos != std::string::npos) {
+ tagname = tagname.substr(0, trimpos+1);
+ }
+ }
+
+ bool skipDecl() {
+ for (;;) {
+ if (!skipWS(m_in, m_pos)) {
+ m_reason << "EOF during initial ws skip";
+ return true;
+ }
+ if (m_in[m_pos] != '<') {
+ m_reason << "EOF file does not begin with decl/tag";
+ return false;
+ }
+ if (peek() == '?') {
+ if (!skipStr("?>")) {
+ m_reason << "EOF while looking for end of xml decl";
+ return false;
+ }
+ } else {
+ break;
+ }
+ }
+ return true;
+ }
+
+ bool parseattrs(std::string& tag,
+ std::map<std::string, std::string>& attrs) {
+ //std::cerr << "parseattrs: [" << tag << "]\n";
+ attrs.clear();
+ std::string::size_type spos = tag.find_first_of(" \t\n\r");
+ if (spos == std::string::npos)
+ return true;
+ std::string tagname = tag.substr(0, spos);
+ //std::cerr << "tag name [" << tagname << "] pos " << spos << "\n";
+ skipWS(tag, spos);
+
+ for (;;) {
+ //std::cerr << "top of loop [" << tag.substr(spos) << "]\n";
+ std::string::size_type epos = tag.find_first_of(" \t\n\r=", spos);
+ if (epos == std::string::npos) {
+ m_reason << "Bad attributes syntax at cpos " << m_pos + epos;
+ return false;
+ }
+ std::string attrnm = tag.substr(spos, epos - spos);
+ if (attrnm.empty()) {
+ m_reason << "Empty attribute name ?? at cpos " << m_pos + epos;
+ return false;
+ }
+ //std::cerr << "attr name [" << attrnm << "]\n";
+ skipWS(tag, epos);
+ if (epos == std::string::npos || epos == tag.size() - 1 ||
+ tag[epos] != '=') {
+ m_reason <<"Missing equal sign or value at cpos " << m_pos+epos;
+ return false;
+ }
+ epos++;
+ skipWS(tag, epos);
+ if (tag[epos] != '"' || epos == tag.size() - 1) {
+ m_reason << "Missing dquote or value at cpos " << m_pos+epos;
+ return false;
+ }
+ spos = epos + 1;
+ epos = tag.find_first_of("\"", spos);
+ if (epos == std::string::npos) {
+ m_reason << "Missing closing dquote at cpos " << m_pos+spos;
+ return false;
+ }
+ attrs[attrnm] = tag.substr(spos, epos - spos);
+ //std::cerr << "attr value [" << attrs[attrnm] << "]\n";
+ if (epos == tag.size() - 1) {
+ break;
+ }
+ epos++;
+ skipWS(tag, epos);
+ if (epos == tag.size() - 1) {
+ break;
+ }
+ spos = epos;
+ }
+ tag = tagname;
+ return true;
+ }
+};
+#endif /* _PICOXML_H_INCLUDED_ */