// configuration.hpp // // Copyright (C) 2004-2020 MicroNeil Research Corporation. // // This software is released under the MIT license. See LICENSE.TXT. // What about this ============================================================= // The configuration module provides a platform for reading configuration files // (or string data) containing well-formed xml and mapping that data to program // variables. // // The idea is to provide the ability for an object or application to provide // a modular "configuration" object that models a hierarchical collection of // "settings" that can be represented easily in code and in xml. // // The following is an example model of a configuration in code and that same // configuration fully populated in xml. // // The code might look like this... // // int IntValue, DefaultInt = 3; // double DblValue, DefaultDbl = 3.14159; // bool BooleanValue, DefaultBool = false; // string StringValue, DefaultString = "NoStringHere"; // // SpecialConfigurator : public ConfigurationHandler { // Create a special handler to build a list // ... // public: // // ConfigurationHandler& Startup(ConfigurationElement& E) { // This function returns a handy handler to // return MyStartupConfigurationHandler; // (re) initialize this handler ;-) // } // // void Operator()() { // Each time the configurator is called // ... // } // // int Attribute1; // these items are interpreted and added // double Attribute2; // to the list. A ConfigurationHandler COULD // string Attribute3; // do something entirely different though ;-) // string Contents; // ... // } Special; // // ConfigurationElement SampleConfig("SampleConfiguration"); // Define a sample config (doc element) // SampleConfig // Populate the SampleConfig // .atStartCall(Special.Startup()) // .Element("Integer", IntValue, DefaultInt).End() // Link an element to an int w/ default. // .Element("Double", DblValue, DefaultDbl).End("Double") // Link an element to a dbl w/ default. // .Element("String", StringValue, DefaultString).End("String") // Link an element to a string w/ default. // .Element("ComplexElements") // Create a sub element. // .Element("Complex1") // Sub element Complex1 has attributes. // .Attribute("IntAtt", IntValue, DefaultInt) // Complex1 has an integer attribute. // .Attribute("DblAtt", DblValue, DefaultDbl) // Complex1 has a dbl attribute. // .Element("IntAtt", IntValue).End() // IntAtt can also be set by a sub element. // .Element("DblAtt", DblValue).End() // DblAtt can also be set by a sub element. // .End() // That's it for Complex1. // .Element("Complex2") // Create the Complex2 sub element. // .Attribute("C2I", IntValue, DefaultInt) // C2I attribute. // .Attribute("C2D", DblValue) // C2D attribute - no default. // .Attribute("C2S", StringValue, DefultString) // C2S attribute - string w/ default. // .End("Complex2") // End of element throws if doesn't match. // .Element("Complex3", Special.Contents) // Element 3 using a special configurator. // .Attribute("A1", Special.Attribute1) // Set A1 and A2 and A3 and when the // .Attribute("A2", Special.Attribute2) // element has been completed, Special() // .Attribute("A3", Special.Attribute3) // will be called to record the entries. // .atEndCall(Special) // Here's where we register the handler. // .End() // Closing Complex3 to be ice. // .End() // Closing ComplexElements to be nice. // .End(); // Closing SampleConfiguration to be nice. // // The XML might look like this... // // // 10 // 2.4 // This is a sample string // // // 24 // // // stuff in here // Another instance // Each one gets passed to Special() on activation // This way, Special() can build a list or some other // interesting thing with all of these. // // // // Include This Header Once Only =============================================== #pragma once #include #include #include #include #include #include #include namespace codedweller { class ConfigurationElement; // Elements exist class ConfigurationAttribute; // Attributes exist class ConfigurationData; // Data exists class ConfigurationTranslator; // Translators exist class ConfigurationMnemonic; // Mnemonics exist class Configurator; // Configurators exist //// Configuration Element ///////////////////////////////////////////////////// // // Elements make up the core of a configuration. That is, a configuration is a // tree of elements. Elements translate directly to well formed xml elements in // a configuration file or string. class ConfigurationElement { private: std::string myName; // Elements have a name. // External important things I remember but don't touch... ConfigurationElement* myParent; // They may have a parrent. std::list myStartConfigurators; // Call these when we start Interpret() std::list myEndConfigurators; // Call these when we finish Interpret() // Internal / subordinate things I own and kill... std::list myAttributes; // They may have a list of attributes. std::list myElements; // They may have a list of sub-elements. std::list myMnemonics; // They may have a list of mnemonics. std::list myTranslators; // They may have a list of translators. // During Interpret() operations we keep track of where we are seen... int myLine; // Last line number I was seen on. int myIndex; // Last char position I was seen on. int myLength; // Last segment length. bool myCleanFlag; // Keep track of initialization. bool myInitOnInterpretFlag; // Initialize() at each Interpret()? void runStartConfigurators(ConfigurationData& D); // Does what it says ;-) void runEndConfigurators(ConfigurationData& D); // Does what it says ;-) public: ConfigurationElement(const char* Name); // Must be constructed with a name ConfigurationElement(const std::string Name); // either c string or c++ string. ConfigurationElement(const char* Name, ConfigurationElement& Parent); // Sub-elements are constructed with a ConfigurationElement(const std::string Name, ConfigurationElement& Parent); // parrent. // Upon desctruction an element will delete all subordinate objects: // * All sub element objects. // * All attribute objects. // * All mnemonic objects. // * All translator objects. // It is important to use new when passing one of these objects to an // element or attribute to prevent problems with the delete operation. // NORMALLY these things would be created using factory methods on the // element and attribute objects themselves - so be careful. // It will not delete Configurators - they must // be deleted elsewhere because they may have been // re-used and this element wouldn't know about it ;-) ~ConfigurationElement(); // The descrutor clears and deletes all! // Elements can be probed for some simple, useful things. std::string Name(); // Get the name of this element. ConfigurationElement& Parent(); // Get the parent of this element. ConfigurationElement& Parent(ConfigurationElement& newParent); // Set the parent of this element. // Note - if there is no parent (an element is the root) then it will // return a reference to itself when Parent() is called. int Line(); // Get the last line number. int Index(); // Get the last data position. int Length(); // Get the last length. // Elements can contain either data or sub-elements. ConfigurationElement& Element(const char* Name); // Add a new sub element by c string name. ConfigurationElement& Element(const std::string Name); // Add a new sub element by c++ string name. //// Mapping element factory methods for convenience. //// Root-Node elements are _usually_ empty and without attributes in xml //// so we don't make any of that type of convenience constructor here. // char* versions ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // string versions ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // End methods for heading back up the tree at the end of an element. class EndNameDoesNotMatch {}; // Throw when End(name) doesn't match. ConfigurationElement& End(); // Return this element's parent. ConfigurationElement& End(const char* Name); // Check the name and return the parent ConfigurationElement& End(const std::string Name); // if the name is correct - or throw! // Elements can have attributes. ConfigurationAttribute& Attribute(const char* Name); // Add an attribute using a cstring. ConfigurationAttribute& Attribute(const std::string Name); // Add an attribute using a c++ string. //// Mapping Attribute factory methods for convenience. // char* versions ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // string versions ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // Elements can Initialize() at each Interpret() call. ConfigurationElement& setInitOnInterpret(); // Set the init on interpret flag. // Elements can call external functions to aid in special operations // such as building lists. ConfigurationElement& atStartCall(Configurator& Functor); // Add an atStart call-back to this element. ConfigurationElement& atEndCall(Configurator& Functor); // Add an atEnd call-back to this element. // Extracting data from the element's contents is done with // translators. A good set of primatives are built in, but the user // can also make their own. If an Element is mapped to more than // one then they are all called once the element's contents are // collected. A translator takes the data provided by the element, // converts it into the expected type, and sets one or more variables // to the converted value. Usually - just one variable. ConfigurationElement& mapTo(ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationElement& mapTo(std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationElement& mapTo(int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationElement& mapTo(double& x, double init = 0.0); // Map to a double. ConfigurationElement& mapTo(bool& x, bool init = false); // Map to a boolean. // An Element's contents may use some special mnemonics to make a // configuration easier to understand and less error prone. When the // contents match a mnemnoic then the translation of the mnemonic is // passed to the Translators instead of the raw contents. ConfigurationElement& Mnemonic(const char* name, const char* value); // Add a mnemonic using c strings. ConfigurationElement& Mnemonic(const char* name, const std::string value); // Add a mnemonic using c & c++ strings. ConfigurationElement& Mnemonic(const std::string name, const char* value); // Add a mnemonic using c++ & c strings. ConfigurationElement& Mnemonic(const std::string name, const std::string value); // Add a mnemonic using c++ strings. // The way data gets into an element tree is that it is Interpret()ed // recursively. The data is loaded into a ConfigurationData object which // is passed to the top Element. That element interpretes the data, moves // the interpretation pointers, and passes the data on to it's subordinate // elements in turn. They do the same recursively. When the last sub - // element has had it's way with the data, the interpretation process is // complete. The ConfigurationData object will contain the original data // and a log of anything that happened during the interpretation process. // // Each time an element is asked to Interpret() data, it calls any atStart // configurators, translates any attributes, then either translates it's // contents or passes the data to it's children, then calls any atEnd // configurators. // // To ensure that the correct default values are used the Initialize() is // always called on all internal attributes and elements before any data is // interpreted. To prevent this from being inefficient, a boolean flag is // kept in each element to keep track of whether it is clean and if it is // then the call to Initialize will simply return (skipping subordinate // elements along the way). // // Interpret returns true if this object found itself at the current // Data.Index and false if not. This helps keep the recursive parsing // code simpler ;-) void initialize(); // Reset all translators to defaults. void notifyDirty(); // Set dirty (if translators change). bool interpret(ConfigurationData& Data); // (re) Interpret this data. }; //// Configuration Attribute /////////////////////////////////////////////////// // // Attributes translate directly to well formed xml attributes (within the // start tag of an element). class ConfigurationAttribute { private: std::string myName; // Elements have a name. ConfigurationElement& myParent; // They may have a parrent. std::list myMnemonics; // They may have a list of mnemonics. std::list myTranslators; // They may have a list of translators. int myLine; // Last line number I was seen on. int myIndex; // Last char position I was seen on. int myLength; // Last segment length. public: ConfigurationAttribute(const char* Name, ConfigurationElement& Parent); // Sub-elements are constructed with a ConfigurationAttribute(const std::string Name, ConfigurationElement& Parent); // parrent. // Attributes delete their Mnemonics and Translators when they go. // See Elements for similar warnings about objects provided to // this object... you must use new to be safe, or better yet - stick to // the built in factory methods ;-) ~ConfigurationAttribute(); // Crush, Kill, Destroy! // Attributes can be probed for some simple, useful things. std::string Name(); // Get the name of this attribute. ConfigurationElement& Parent(); // Get the parent of this attribute. int Line(); // Get the last line number. int Index(); // Get the last data position. int Length(); // Get the last length. void notifyDirty(); // Attributes use this when they change. // For convenience in building configurations, an Attribute offers // some call-through methods to it's parrent Element. This allows for // clear, concise .method() coding that mimics an outline of the // configuration structure. //// For switching back to the parent element and adding new sub-elements. ConfigurationElement& Element(const char* Name); // Add a new sub element by c string name. ConfigurationElement& Element(const std::string Name); // Add a new sub element by c++ string name. //// Mapping element factory methods for convenience. //// Root-Node elements are _usually_ empty and without attributes in xml //// so we don't make any of that type of convenience constructor here. // char* versions ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationElement& Element( // Mapping factory for convenience, const char* Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // string versions ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationElement& Element( // Mapping factory for convenience, const std::string Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // End methods for heading back up the tree at the end of an element. ConfigurationElement& End(); // Return this element's parent. ConfigurationElement& End(const char* Name); // Check the name and return the parent ConfigurationElement& End(const std::string Name); // if the name is correct - or throw! //// For adding new attributes to the parent element. ConfigurationAttribute& Attribute(const char* Name); // Add an attribute using a cstring. ConfigurationAttribute& Attribute(const std::string Name); // Add an attribute using a c++ string. //// Mapping Attribute factory methods for convenience. // char* versions ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const char* Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. // string versions ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, ConfigurationTranslator& newTranslator); // Add a Translator to this element. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, int& x, int init = 0, int radix = 0); // Map to an int. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, double& x, double init = 0.0); // Map to a double. ConfigurationAttribute& Attribute( // Mapping factory for convenience, const std::string Name, // requires a name, of course, bool& x, bool init = false); // Map to a boolean. //// Set Init On Interprete for the parent element. ConfigurationElement& setInitOnInterpret(); // Set the init on interpret flag. //// For adding configurators to the parent element. ConfigurationElement& atStartCall(Configurator& Functor); // Add an atStart call-back to this element. ConfigurationElement& atEndCall(Configurator& Functor); // Add an atEnd call-back to this element. // Of course, the most useful thing about attributes is that they can // be mapped to variables using translators. The same as those that // apply to the parent element's contents. Here they are for use on this // attribute. ConfigurationAttribute& mapTo(ConfigurationTranslator& newTranslator); // Add a Translator to this attribute. ConfigurationAttribute& mapTo(std::string& x, std::string init = std::string("")); // Map to a string. ConfigurationAttribute& mapTo(int& x, int init, int radix = 0); // Map to an int. ConfigurationAttribute& mapTo(double& x, double init = 0.0); // Map to a double. ConfigurationAttribute& mapTo(bool& x, bool init = false); // Map to a boolean. // Attributes can have mnemonics just like elements. ConfigurationAttribute& Mnemonic(const char* name, const char* value); // Add a mnemonic using a c string. ConfigurationAttribute& Mnemonic(const char* name, const std::string value); // Add a mnemonic using c & c++ strings. ConfigurationAttribute& Mnemonic(const std::string name, const char* value); // Add a mnemonic using c++ & c strings. ConfigurationAttribute& Mnemonic(const std::string name, const std::string value); // Add a mnemonic using a c++ string. // Attributes participate in the Interprete() task just like elements. void initialize(); // Reset all translators to defaults. bool interpret(ConfigurationData& Data); // (re) Interpret this data. }; //// Configuration Data //////////////////////////////////////////////////////// // // A ConfigurationData object holds on to the configuration source data and // provideds a place to log any information about how the configuration was // interpreted. It also creates and destroys a handy char[] to contain the // data. To make this beastie easier to handle, we use the Named Constructor // Idiom and hide the true constructor in the private section. class ConfigurationData { // Configuration Data Source private: char* myDataBuffer; // The actual data buffer. int myBufferSize; // Size of the current buffer. int myIndex; // The current interpretation index. int myLine; // Current line number. public: ConfigurationData(const char* FileName); // Constructor from c string file name. ConfigurationData(const std::string FileName); // Constructor from c++ string file name. ConfigurationData(const char* Data, int Length); // Raw constructor from text buffer. ~ConfigurationData(); // Destroys the internal buffer etc. char Data(int Index); // Returns char from Data[Index] int Index(); // Reads the current Index. int Index(int i); // Changes the current Index. int Line(); // Reads the current Line number. int addNewLines(int Count); // Increments the Line number. std::stringstream Log; // Convenient Interpret log. }; //// Configuration Translator ////////////////////////////////////////////////// // // A Translator converts the contents provided to it in string form into some // other data type. The object here is a prototype for that, followed by a // collection of the basic translators used for built-in mapTo()s. class ConfigurationTranslator { // Translators exist public: virtual ~ConfigurationTranslator(){}; // Stop No Virt Dtor warnings. virtual void translate(const char* Value) = 0; // Pure virtual translator. virtual void initialize() = 0; // Pure virtual initializer. }; class StringTranslator : public ConfigurationTranslator { private: std::string& myVariable; // Variable to map. std::string myInitializer; // Initial/Default value. public: StringTranslator( // Construct this with std::string& Variable, // the variable to map, std::string Inititializer); // and the default value. void translate(const char* Value); // Provide a translation method. void initialize(); // Provide an initialization method. }; class IntegerTranslator : public ConfigurationTranslator { private: int& myVariable; // Variable to map. int myInitializer; // Initial/Default value. int myRadix; // Radix for strtol() public: IntegerTranslator( // Construct this with int& Variable, // the variable to map, int Inititializer, // and the default value. int Radix); // For this one we also need a Radix. void translate(const char* Value); // Provide a translation method. void initialize(); // Provide an initialization method. }; class DoubleTranslator : public ConfigurationTranslator { private: double& myVariable; // Variable to map. double myInitializer; // Initial/Default value. public: DoubleTranslator( // Construct this with double& Variable, // the variable to map, double Inititializer); // and the default value. void translate(const char* Value); // Provide a translation method. void initialize(); // Provide an initialization method. }; class BoolTranslator : public ConfigurationTranslator { private: bool& myVariable; // Variable to map. bool myInitializer; // Initial/Default value. public: BoolTranslator( // Construct this with bool& Variable, // the variable to map, bool Inititializer); // and the default value. void translate(const char* Value); // Provide a translation method. void initialize(); // Provide an initialization method. }; //// Configuration Mnemonic //////////////////////////////////////////////////// // // A Mnemonic allows the actual contents of an element or attribute to be // exchanged for a different "named" value to help eliminate "magic numbers" // and "secret codes" from configurations. One way this might be used is to // map an enumeration to the appropriate integer values, or things like YES and // NO to boolean true and false (respectively) when turning on/off program // options. class ConfigurationMnemonic { // Mnemonics private: std::string myName; // What is the Mnemonic? std::string myValue; // What is the translation? public: ConfigurationMnemonic(std::string Name, std::string Value); // To make one, provide both parts. bool test(std::string Name); // Test to see if this Mnemonic matches. std::string Value(); // If it does then we will need it's value. }; //// Configurator ////////////////////////////////////////////////////////////// // // A configurator is a "functor" or "closure" or "callback" that can be used to // support sophisticated interpretation options. The most basic and necessary // of these is support for list building. Consider an object created to contain // a list of records where each record might be represented as a collection of // attributes and elements. The object would have data elements mapped to the // attributes and elements in the configuration and then control elements which // are functors for initializing the list and storing new entries as they are // completed. The object here is a pure virtual prototype. class Configurator { // Configurators exist public: virtual void operator()(ConfigurationElement& E, ConfigurationData& D) = 0; // Pure virtual configurator. virtual ~Configurator() {} // Virtual dtor keeps warnings away. }; //// Utilities ///////////////////////////////////////////////////////////////// // SetTrueOnComplete Configurator ////////////////////////////////////////////// class ConfiguratorSetTrueOnComplete : public Configurator { // Configurator set's a boolean true. private: bool* myBoolean; // The boolean to set. public: ConfiguratorSetTrueOnComplete(); // Must init to NULL for safety. void setup(bool& Target); // Link to the target boolean. void operator()(ConfigurationElement& E, ConfigurationData& D); // Handle the operation. }; } // End namespace codedweller