#ifndef __XmlSupport_h_ #define __XmlSupport_h_ #include #include #include #include "../shared/MiscSupport.h" /* This library is our custom tool for generating XML. It's based on similar * ideas we've used in PHP with great success. This version is slightly nicer * because we're using an object rather than a dictionary. In php you might * accidentally misspell a word and it will get ignored. In this C++ code if * you mi-spell "properties" or one of our other keywords you'll get a compile * time error. * * The basic idea is simple. Each XML tag, like or ... is * represented by an XmlNode object. In there are two * tags and two XmlNode objects. The outer object owns the inner one. We * often pass XmlNode objects by reference to make it easier to navigate these * structures. * * Note than an XmlNode has no idea who its parent or siblings are. Those * are common properties in some libraries. I mostly find them useless and * they would have made the library more complicated. If a function needs to * know the parent of an XmlNode, send the parent of an XmlNode, then send * the parent as an input. In fact, a lot of functions take XmlNode &parent * as an input. That's useful when you want to add N children to a parent. * * These objects can be read. Any property you can set, you can also read. * Mostly that's just to help us build an XML document from various pieces of * code that don't know much about each other. It's never our intent to use * an XmlNode in C++ to read information. * * If you want to read an XML file from disk, look at rapidxml.hpp. That's a * 3rd party library that's good at reading XML. It's surprisingly easy to * use. Actually, look at XmlReader.h which is my wrapper around rapidxml.hpp. * * You can also use rapidxml.hpp to create XML documents, but I find this * file much easier to use. * * Notice that XmlNode uses (and exposes) a lot of STL containers. In fact, * you should generally treat XmlNode like an STL container. The copy * constructor works perfectly, but you probably want to pass these objects * by reference, instead. */ // Note, XmlElement would have been a more accurate name. I didn't understand // the standard XML naming conventions when I called this a "node". class XmlNode { private: void addToString(std::string &output, const std::string &recommendedName) const; public: // The name of the tag. To get ..., // set myNode.name = "WINNERS"; std::string name; // These should have been called attributes. That's the standard XML // terminology. To get , // set myNode.attributes["METHOD"] = "root mean square"; PropertyList properties; // There are two ways to store the children. Sometimes you want to create // a lot of nodes and you don't care about the previous nodes. So you add // each one to the end of the orderedChildren list. // // Sometimes three different parts of the code want to make updates to the // same child. // Then you would say something like // config.namedChildren["COLORS"].namedChildren["FOREGROUND"].text = "red"; // config.namedChildren["COLORS"].namedChildren["BACKGROUND"].text = "black"; // config.namedChildren["COLORS"].namedChildren["HILIGHT"].text = "blue"; // The first of these will automatically create the COLORS child. No one has // to know who is first. If later you add // config.namedChildren["COLORS"].namedChildren["BACKGROUND"].text = "green"; // that will change the text for that node, rather than creating a new node. // So if you want, one routine can fill in defaults, and a second routine // can make changes. // // A third option is that the client is explicitly looking for something in // the 3rd child. The ordered children are written out in exactly the same // order as they appear in the vector. namedChildren are written after the // orderedChildren. So the first item in orderedChildren will be the first // child written out. The last item in orderedChildren might NOT be the last // item written out. std::map< std::string, class XmlNode > namedChildren; std::vector< class XmlNode > orderedChildren; // The body of the tag. myNode.text = "hi" --> hi // // Generally we do not use text when TI is talking with TI. Attributes / // properties work better for me most of the time. The internet lists strong // opinions on both sides, and for external communications we might not get // a choice. std::string text; // If the text is an XML or HTML document, you probably want to set this to // true. It's just an optimization. It says how we quote things. For the // most part the reader will just unquote the body either way, so the result // will be the same. I've seen notes on the internet that some XML parsers // will treat this slightly differently, but I haven't seen that first hand. bool useCdata; // Write the entire thing out as a document. We always return a string. // You can send this to the log or write it to a normal file. Most of the // time we send the entire result (and nothing else) as a message to the // client. // // recommendedName gets used if the name property is "". For the top // level of the document, we usually say API and it usually doesn't matter. // For namedChildren the default is grabbed from the key. For // orderedChildren we use "NODE" for the default. std::string asString(const std::string &recommendedName = "API") const; // x.clear() is similar to x = XmlNode(). // // Currently we're not clearing the useCdata flag. I'm not sure if that was // intentional or an oversight. void clear(); // Checks the same fields that clear() changes. bool empty() const; // Just a shortcut for a very common operation. Notice the trivial // implementation. If someone adds a node to orderedChildren, this function // will ignore it, even if the node had the same name. This function will // create the child if it does not already exist. XmlNode &operator[](std::string const &name) { return namedChildren[name]; } // 0 will find and return the first child. -1 will create a new child at the // end and return that. If i is not negative, and the requested child does // not exist, this will fail badly. XmlNode &operator[](int i) { if (i < 0) { // Note the nextItem() function in MiscSupport.h That does the same // thing as this slightly complicated line below. return *orderedChildren.insert(orderedChildren.end(), XmlNode()); } else { return orderedChildren[i]; } } // Standard STL swap() function. This quickly swaps the contents of two // XmlNode objects. Before C++ had move semantics, this is how we got the // same results. This could be used for other purposes, but as far as I know // it's only used to simulate move semantics. void swap(XmlNode &other) { name.swap(other.name); properties.swap(other.properties); namedChildren.swap(other.namedChildren); orderedChildren.swap(other.orderedChildren); text.swap(other.text); bool temp = other.useCdata; other.useCdata = useCdata; useCdata = temp; } XmlNode() : useCdata(false) { } // This means that if we send a string as .text or a .property[], the result // will be byte for byte the same as when we started. It might be quoted, // but that's okay as long as the client can unquote it and get what we // started with. This is talking about a UTF-8 client. This ignores the // magic we sometimes need to get bytes to the delphi client. // // Be sure to call setlocale(LC_ALL, ""); in the main program! Otherwise // this will not work right for utf-8 characters. static bool binarySafe(std::string const &); }; #endif