📜 ⬆️ ⬇️

DSL for XML in C ++

What we have


To begin with, I will tell you a little about the project in which I work and how everything is written there. Maybe not some of us so ...

The project is a CRM system developed specifically for customers of one business segment. The project is 6 years old and the development team consists of 10 people. Language: C ++ and PL / SQL.

Our system uses Plain Old XML, so it is a tradition. And the XML used is not schematic, for the most part. What can I say, if unit tests take root here only for the second year and the manager still reproaches for the time spent writing them. Come on…
')
Along the way, all the improvements appear when the current state of affairs is getting bored and unbearable. The same happened now.

Like many, I think we do a lot of things not optimally or in the best way. The main thing we do. The XML example is no exception.


Standard practice


Here is an example of the code for building an XML document in a function:

::std::string request = clearCreateNode("book", createNode("author", "", "name='Freddy' surname='Smith'") + createNode("author", "", "name='Bill' surname='Joe'") + clearCreateNode("quote", "This is the best unknown book I've ever quoted!" + createNode("author", "", "name='Mr. Bob'")), "isbn='123456789' name='Some book' year='2011'"); 


Standard Practice. Why not stream ? - because there is an nesting of elements. Although stream is the first thing that comes to mind and for the imperative approach, it is hardly possible to get something different from “feeding the right object to the left”.

Okay, used to. You can suffer a comma, but tolerate for each tag the repetition of the name of the function clearNode or clearCreateNode - I could not. And no matter how short the name can be, even if just "T" is insanity. It is akin to repeating “I speak” in front of each remark in a dialogue with someone, and “offer:” before each sentence, “word:” before the word, and so on. Do not insanity?

What is the problem


Someone will say that XML itself is redundant - it’s good that in the code you don’t have to close the tag ...

Yes, it is, but it is not a reason to behave as well. I like communicating with a person more than an idiot. When we both understand the context and semantics of dialogue. When we can use slang and understand each other. Or, when you can only once explain the design rules and continue to communicate taking them into account.

For example, in Object Pascal there is such a construction:

 with (Object) begin a := 1; b := "foobar"; end; 


It's nice, however, when you can say: “Now we are working with an Object, therefore we set its 'a' property to 1, and the 'b' property to 'foobar'”.

"Not a drop of fat!"


Returning to XML, this is how an XML document looks like on DSL, for example in Lisp:

 ((book :isbn "123456789" :name "Some book" :year "2011") ((author :name "Freddy" :surname "Smith")) ((author :name "Bill" :surname "Joe")) (quote "This is the best unknown book I've ever quoted!" (author :name "Mr. Bob"))) 


If you do not pay attention to the brackets, then there is no “fat drop!” If you do not pay attention to the symbols <,>, /, = in XML and omit the redundancy of the closing tag, you get the same messages.

 <book isbn='123456789' name='Some book' year='2011'><author name='Freddy' surname='Smith'/><author name='Bill' surname='Joe'/><quote>This is the best unknown book I&apos;ve ever quoted!<author name='Mr. Bob'/></quote></book> 


Or with pretty print:

 <book isbn='123456789' name='Some book' year='2011'> <author name='Freddy' surname='Smith'/> <author name='Bill' surname='Joe'/> <quote>This is the best unknown book I&apos;ve ever quoted! <author name='Mr. Bob'/> </quote> </book> 


Is it possible to get something similar in meaning in C ++?

Result


In general, here's what happened:

  const xml::Tag book ("book"); const xml::Tag author ("author"); const xml::Tag quote ("quote"); xml::Element request = xml::Element() (book ("isbn", "123456789") ("name", "Some book") ("year", "2011"), xml::Element() (author ("name", "Freddy") ("surname", "Smith")) (author ("name", "Bill") ("surname", "Joe")) (quote, xml::Element() ("This is the best unknown book I've ever quoted!") (author ("name", "Mr. Bob")))); 


Or with a list of tags to be reused in the project:

 namespace library { namespace books { const xml::Tag book ("book"); const xml::Tag author ("author"); const xml::Tag quote ("quote"); } } 


And then:

  namespace bks = ::library::books; xml::Element request = xml::Element() (bks::book ("isbn", "123456789") ("name", "Some book") ("year", "2011"), xml::Element() (bks::author ("name", "Freddy") ("surname", "Smith")) (bks::author ("name", "Bill") ("surname", "Joe")) (bks::quote, xml::Element() ("This is the best unknown book I've ever quoted!") (bks::author ("name", "Mr. Bob")))); 


It was not possible to completely get rid of redundancy (after all, it is necessary to specify xml::Element() at the attachment point). But there is progress: it is no longer necessary to repeat for each tag that it is a TAG, and you can collect analytics for the entire document under construction and make on its basis, for example, pretty print or something else, since we are already dealing with an object, more precisely with a tree common root.

findings


You can select the interface of the DSL, consisting of:

  template <class T> void addContent(const T & content); void addEmptyTag(const Tag & tag); template <class T> void addTag(const Tag & tag, const T & content); void addTagElement(const Tag & tag, const Element & element); 


  template <class T> inline Element & operator () (const T & content) { addContent(content); return *this; } inline Element & operator () (const Tag & tag) { addEmptyTag(tag); return *this; } template <class T> inline Element & operator () (const Tag & tag, const T & content) { addTag(tag, content); return *this; } template <> inline Element & operator () (const Tag & tag, const Element & element) { addTagElement(tag, element); return *this; } 



And now it remains only to invent various implementations for compiling code written in DSL in XML. Here's an example of a laboratory version:

  template <class T> void addContent(const T & content) { ::std::ostringstream oss; oss << content; buf += http_encode(oss.str()); } void addEmptyTag(const Tag & tag) { buf += createNode(tag.name, "", tag.args); } template <class T> void addTag(const Tag & tag, const T & content) { ::std::ostringstream oss; oss << content; buf += createNode(tag.name, oss.str(), tag.args); } void addTagElement(const Tag & tag, const Element & element) { buf += clearCreateNode(tag.name, element.str(), tag.args); } 


For completeness, here’s the Tag class code:

 class Tag { public: // DSL : template <class K> Tag & operator () (const K & attr_name) { ::std::ostringstream oss; oss << " " << attr_name; args += oss.str(); return *this; } template <class K, class T> Tag & operator () (const K & attr_name, const T & value) { ::std::ostringstream oss; oss << " " << attr_name << "='" << value << "'"; args += oss.str(); return *this; } //   DSL : template <class K> Tag operator () (const K & attr_name) const { Tag copy(*this); return copy (attr_name); } template <class K, class T> Tag operator () (const K & attr_name, const T & value) const { Tag copy(*this); return copy (attr_name, value); } //   : template <class T> Tag(const T & name) : name(name) {} Tag(const Tag & ref) : name(ref.name), args(ref.args) {} ::std::string name; ::std::string args; }; 


... and an example XSLT for translating a simple XML document into code on this DSL:

 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions"> <xsl:output method="text" version="1.0" encoding="windows-1251" indent="no"/> <xsl:template match="/"> <xsl:apply-templates/> ; </xsl:template> <xsl:template match="text()">"<xsl:value-of select="."/>"</xsl:template> <xsl:template match="attribute::*"> ("<xsl:value-of select="name()"/>", "<xsl:value-of select="."/>")</xsl:template> <xsl:template match="*"><xsl:if test="child::node()">xml::Element()</xsl:if> (<xsl:value-of select="name()"/><xsl:apply-templates select="attribute::*"/><xsl:if test="child::node()">, <xsl:apply-templates/></xsl:if>)</xsl:template> </xsl:stylesheet> 


As a result, we can say that it turned out to write a little blood DSL for C ++. Now the next item will be the inclusion of SQL code in C ++ ...

Thanks for your time, I hope that you liked the topic.

Source: https://habr.com/ru/post/128933/


All Articles