zen::Xml
Simple C++ XML Processing
|
zen::Xml is an XML library serializing structured user data in a convenient way. Using compile-time information gathered by techniques of template metaprogramming it minimizes the manual overhead required and frees the user from implementing fundamental type conversions by himself. Basic data types such as
are handled automatically. Thereby a large number of recurring problems is solved by the library:
The design follows the philosophy of the Loki library:
http://loki-lib.sourceforge.net/index.php?n=Main.Philosophy
1. Download zen::Xml: http://sourceforge.net/projects/zenxml
2. Setup one of the following preprocessor macros for your project to identify the platform (this is only required if you use C-stream-based file IO)
3. For optimal performance define this global macro in release build: (following convention of the assert
macro)
4. Include the main header:
5. Start serializing user data:
The following XML file will be created:
<?xml version="1.0" encoding="UTF-8"?> <Root> <elem1>10</elem1> <elem2>2.000000</elem2> <elem3>-1</elem3> </Root>
Load an XML file and map its content to user data:
zen::Xml is written in a platform independent manner and runs on any C++14-compliant compiler, e.g. Microsoft Visual C++, MinGW (Windows) and GCC, Clang (Linux and macOS).
Note: In order to enable C++14 features in GCC it is required to specify either of the following compiler options:
-std=c++14 -std=gnu++14
Depending on what granularity of control is required in a particular application, zen::Xml allows the user to choose between full control or simplicity.
The library is structured into the following parts, each of which can be used in isolation:
<File>
|
| io.h
|
<Byte Stream>
|
| parser.h
|
<Document Object Model>
|
| bind.h
|
<C++ user data>
The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <elemento1> <элемент2> <要素3> <στοιχείο4> <elem5> <元素6> <元> <z>-1234</z> </元> </元素6> </elem5> </στοιχείο4> </要素3> </элемент2> </elemento1> </Root>
The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <elem attr1="-1" attr2="2.1" attr3="true"/> </Root>
All built-in arithmetic types and bool
are detected at compile time and a proper conversion is applied. Common conversions for integer-like types such as int
, long
, long long
, ect. as well as floating point types are optimized for maximum performance.
The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <int>-1234</int> <double>1.23</double> <float>4.56</float> <ulong>1234</ulong> <bool>false</bool> </Root>
The document object model of zen::Xml internally stores all names and values as a std::string. Consequently everything that is not a std::string but is "string-like" is UTF-converted into a std::string representation. By default zen::Xml accepts all character arrays like char[]
, wchar_t[]
, char*
, wchar_t*
, single characters like char
, wchar_t
, standard string classes like std::string
, std::wstring
and user-defined string classes. If the input string is based on char
, it will simply be copied and thereby preserves any local encodings. If the input string is based on wchar_t
it will be converted to an UTF-8 encoded std::string
. The correct wchar_t
encoding of the system will be detected at compile time, for example UTF-16 on Windows, UTF-32 on most Linux distributions.
Note: User-defined string classes are automatically supported if they fulfill the following string concept by defining:
value_type
for the underlying character type: must be char
or wchar_t
c_str()
returning something that can be converted into a const value_type*
length()
returning the number of characters returned by c_str()
The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <string>elemento1</string> <wstring>элемент2</wstring> <wxString>要素3</wxString> <MyString>στοιχείο4</MyString> <char[6]>elem5</char[6]> <wchar_t[4]>元素6</wchar_t[4]> <wchar_t>元</wchar_t> <char>z</char> </Root>
value_type
for the underlying element type of the containeriterator
for a non-const iterator into the containerconst_iterator
for a const iterator into the container begin()
returning an iterator pointing to the first element in the containerend()
returning an iterator pointing just after the last element in the containerinsert()
with the signature iterator insert(iterator position, const value_type& x)
clear()
removing all elements from the containerstd::vector<MyType>
or std::vector<std::list<MyType>>
it is sufficient to only integrate MyType
into zen::Xml. The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <deque> <Item>1.234</Item> <Item>5.678</Item> </deque> <list> <Item>1</Item> <Item>2</Item> </list> <map> <Item> <one>1.1</one> <two>a</two> </Item> <Item> <one>2.2</one> <two>b</two> </Item> </map> <multimap> <Item> <one>3</one> <two>99</two> </Item> <Item> <one>3</one> <two>100</two> </Item> <Item> <one>4</one> <two>101</two> </Item> </multimap> <set> <Item>1</Item> <Item>2</Item> </set> <multiset> <Item>1</Item> <Item>1</Item> <Item>2</Item> </multiset> <vector> <Item>Ä</Item> <Item>Ö</Item> </vector> <vect_list> <Item> <Item>ä</Item> <Item>ö</Item> <Item>ü</Item> </Item> <Item> <Item>ä</Item> <Item>ö</Item> <Item>ü</Item> </Item> </vect_list> <pair> <one>a</one> <two>â</two> </pair> </Root>
User types can be integrated into zen::Xml by providing specializations of zen::readText() and zen::writeText() or zen::readStruc() and zen::writeStruc(). The first pair should be used for all non-structured types that can be represented as a simple text string. This specialization is then used to convert the type to XML elements and XML attributes. The second pair should be specialized for structured types that require an XML representation as a hierarchy of elements. This specialization is used when converting the type to XML elements only.
See section Type Safety for a discussion of type categories.
Example: Specialization for an enum type
Example: Brute-force specialization for an enum type
Example: Specialization for a structured user type
The resulting XML:
<?xml version="1.0" encoding="UTF-8"?> <Root> <config> <Item> <number>2</number> <address>Abc 3</address> </Item> </config> </Root>
Although it is possible to enable conversion of structured user types by specializing zen::readStruc() and zen::writeStruc() (see Support for user-defined types), this approach has one drawback: If a mapping error occurs when converting an XML element to structured user data, for example a child-element is missing, the input proxy class zen::XmlIn is only able to detect that the whole conversion failed. It cannot say which child-elements in particular failed to convert.
Therefore it may be appropriate to convert structured types by calling subroutines in order to enable fine-granular logging:
zen::Xml heavily uses methods of compile-time introspection in order to free the user from managing basic type conversions by himself. Thereby it is important to find the right balance between automatic conversions and type safety so that program correctness is not compromised. In the context of XML processing three fundamental type categories can be recognized:
std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
bool
std::pair
, structured user typesThese categories can be seen as a sequence of inclusive sets:
----------------------------- | structured | Used as: XML element value | ------------------------- | Conversion via: readStruc(), writeStruc() - may be specialized for user-defined types! | | to-string-convertible | | Used as: XML element/attribute value | | --------------- | | Conversion via: readText(), writeText() - may be specialized for user-defined types! | | | string-like | | | Used as: XML element/attribute value or element name | | --------------- | | Conversion via: utfCvrtTo<>() | ------------------------- | -----------------------------
A practical implication of this design is that conversions that do not make sense in a particular context simply lead to compile-time errors: