April 19, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

The Story of a WML Generator, Page 2

  • September 19, 2005
  • By Radu Braniste
  • Send Email »
  • More Articles »

There are a GenericElement and elements sharing its behavior. GenericElement is responsible for the correct generation of XML code and the elements are responsible for the implementation of compositional rules, through the use of static polymorphism ("insert" is overloaded for the type of elements that are allowed to be inserted inside the hosting element; for example, a Deck can aggregate cards and headers only). The final WML representation is built through the overlay of the different elements composing the deck. Please note that GenericElement doesn't implement an interface, as it is at least restrictive to impose a generic interface fitting to more than WML. But once the intent is to support one family of dialects, only an IGenericElement interface is perfectly plausible.

Of course, this is a simple and straightforward approach—no surprise or mystery. But, what happens if someone changes the rules of the game? A WML dialect where a Deck might take Anchors too, for example?

There is no simple answer, mainly due to the fact that the filtering aspect of the elements ("insert") cannot be properly exposed in a factory (there is a variable number of insert methods). The same is true if you refactor away the insert method in Filter classes (a technique you'll use later in the C+ implementation).

One interesting overload of the "using" keyword is that it can be used like a (very restricted) version of typedef—the so-called "using alias" [5]. This means that the implementation to be used can be selected from one place, at compile time:

namespace mlgen
{
   using Card = mlgen.CardImpl;
   using Head = mlgen.HeadImpl;
   using Break = mlgen.BreakImpl;
   using Deck = mlgen.DeckImpl;
   using Paragraph = mlgen.ParagraphImpl;
   public class GenExec
   {
      public static void doReadJads()
      {
         Card c = new Card();
         Paragraph p = new Paragraph();
         Break br = new Break();
         Head hd = new Head();
         hd.insert(br);
         Deck dk = new Deck();
         c.insert(p);
         dk.insert(c);
         dk.insert(hd);
            //p.insert(br);
         Console.WriteLine (dk);
      }
   }
}

The "using" section selects a specific implementation (in the mlgen namespace) and every element can be replaced easily if needed by a different implementation, respecting the Open-Closed principle [6]. Please note that the same idiom can be used for GenericElement—namely defining GenericElement as an alias. This allows different implementations of GenericElement without requiring the use of an interface.

But, "using alias" has restrictions—the alias can be used in the same translation unit only, such as requiring duplications in certain cases and making the idiom impracticable.

The C++ Dose

Moving the C# code to C++ is rewarding, but increases complexity [6]—wml1.h:

struct LateInsertCheck
{
   static void check(bool isClosed)
   {
      assert(!isClosed "inserted after closed");
   }
};
template <class F, class P = LateInsertCheck>
class GenericElement
{
public:
   operator const std::string& () const
   {
      closeElement();
      return root_;
   }
   template <class T>
   void insert(const T& e)
   {
      F::filter(e);
      P::check(used_);
      genericInsert(e);
   }
protected:
   GenericElement(const std::string& element,
                  const std::string& init):
   element_(element), inserted_(false), used_(false)
   {
      root_ += init;
      openElement();
   }
   GenericElement(const std::string& element):
   element_(element), inserted_(false), used_(false)
   {
      openElement();
   }
   ~GenericElement()
   { }
   void genericAddProperty(const std::string& key,
                           const std::string& value)
   {
      P::check(used_);
      P::check(inserted_);
      if (!inserted_)
      {
         root_ += " ";
         root_ += key;
         root_ += "="";
         root_ += value;
         root_ += """;
      }

} template <class T> void genericInsert(const T& e) { whenInserted(); root_ += e; } private: void openElement() { root_ += "<"; root_ += element_; } void whenInserted() { if (!inserted_) { root_ += ">"; inserted_ = true; } root_ += 'n'; } void closeElement() const; { if (used_) return; used_ = !used_; if (inserted_) { root_ += 'n'; root_ += "</"; root_ += element_; root_ += ">"; } else { root_ += "/>"; } } private: mutable std::string root_; const std::string element_; bool inserted_; mutable bool used_; } template <class FG, class T> class BreakImpl : public FG , public T { public: BreakImpl(): FG("br") { } } template <class FG, class T> class HeadImpl : public FG, public T { public: HeadImpl() : FG("head") { } }; template <class FG, class T> class AnchorImpl : public FG, public T { public: AnchorImpl(const std::string& href, const std::string& body) : FG("a") { this->genericAddProperty("href",href); this->genericInsert(body); } }; template <class FG, class T> class ParagraphImpl : public FG, public T { public: ParagraphImpl() : FG("p") { } }; template <class FG,class T> class CardImpl : public FG, public T { public: CardImpl() : FG("card") {} void setID(const std::string& id) { //required in strict mode - to solve ambiguity this->genericAddProperty("id", id); } }; template <class FG,class T> class DeckImpl : public FG, public T { public: DeckImpl() : FG("wml", "<?xml version="1.0"?>n<!DOCTYPE wml PUBLIC "-//PHONE.COM//DTD WML 1.1//EN"n "http://www.phone.com/dtd/wml11.dtd" >n" ) { } };




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel