Previously, we've build a house and an arbitrary XML structure using loquacious API. The next loquacious interface usage I'll share will be more complicated and probably closer to the real-life needs. It'll be an API to build any valid XHTML markup in the code, based on XSD (XML Schema Definition). You can see the result on GitHub, feel free to use and fork it, if you find it useful!
When building the interfaces seen in Action<T> parameters I've strictly followed the rules and names given in XSD. That makes a guarantee that the code produced using my API will always be a valid HTML (XHTML 1.0 Strict in this case) in terms of elements nesting rules. If an element is not available at given level, it means that XSD doesn't allow it there.
I'm going to go over the codebase quickly to show how easily XSD-based loquacious interfaces can be built.
Architecture overview
A root idea in loquacious interfaces is that when going down the structure of our constructed object graph, we need Action<T> lambda typed with an interface exposing all the options available at given level. For XHTML (and XML in general), these levels are elements and available options are its allowed child elements, attributes and inner textual content. So for each XHTML element (for each <xs:element> element in schema) we need an interface - let's call it by prefixing element's name with I and let's leave it empty by now.
public interface IHtml {}
public interface IHead {}
public interface IBody {}
// etc...
That's almost 80 interfaces, quite a lot, but that's what will give us the validity guarantee later. We need to have all these interfaces implemented, too, and that's more scary. Some interfaces will be very similiar as a lot of HTML elements have the same attributes and child elements. If we decide to have separate implementation for each interface, we'll have a massive code duplication.
I've decided to do something different - implement all elements' interfaces in single class - ElementImpl. At the end, it will have a method for each element and each attribute in whole schema, what will make that class pretty big - about 250 members. But it is my implementation detail, marked as internal, never exposed, so I feel it's not such a bad thing, especially that it would take three or four times more code when implemented separately.
Of course, that similarity in child elements and attributes is specific for HTML and there are different XML schemas that do not have such characteristics. In those cases, it'll probably be cleaner to implement each interface separately.
OK, by now we have 80 empty interfaces and one empty class implementing all of them. We need a way to create instances of given interface. In case of separate implementations, we'll probably go with newing it where needed. But here we can do it in generic and concise way, as we have all supported elements implemented in the single class - we just need a type cast to an interface. I have a static utility class for that - ElementFactory. To keep things simple, I'm using the element interface name to get element name added to XML tree by skipping the prefix and lowercasing.
The last infrastructure thing to note is already known NodeBuilder class, which is a wrapper for standard XML API, extended this time with few tweaks. Creating a node and running its Action<T> is now hidden inside AddNode method with element's interface in generic argument. This way I just need to call provided Action<T> on the instance fetched from ElementFactory.
XSD translation
Time to fill in the elements' interfaces and the ElementImpl implementation. I've decided to follow the XSD literally. I've translated each <xs:attributeGroup> into an interface. See the example:
<xs:attributeGroup name="coreattrs">
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="class" type="xs:NMTOKENS"/>
<xs:attribute name="style" type="StyleSheet"/>
<xs:attribute name="title" type="Text"/>
</xs:attributeGroup>
public interface IHaveCoreAttrs
{
void Id(string id);
void Class(string @class);
void Style(string style);
void Title(string title);
}
The same with each <xs:group>, groupping the elements. Each element available corresponds to an Action<T>-typed method in loquacious interface:
<xs:group name="fontstyle">
<xs:choice>
<xs:element ref="tt"/>
<xs:element ref="i"/>
<xs:element ref="b"/>
<xs:element ref="big"/>
<xs:element ref="small"/>
</xs:choice>
</xs:group>
public interface IHaveFontStyleElements
{
void Tt(Action<ITt> action);
void I(Action<II> action);
void B(Action<IB> action);
void Big(Action<IBig> action);
void Small(Action<ISmall> action);
}
Again, the same with each <xs:complexType>. Note that as XSD elements reference and extend each other, we are following it with our interfaces:
<xs:complexType name="Inline" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:group ref="inline"/>
<xs:group ref="misc.inline"/>
</xs:choice>
</xs:complexType>
public interface IInlineComplexType : IHaveInnerContent, IHaveInlineElements, IHaveMiscInlineElements {}
I've also translated each enumerated type (defined as <xs:restriction>) into C# enumeration.
And finally, we get to <xs:element>s. We're doing the same here. If the element extends already defined XSD complex type, we mimic it with inheriting from corresponding interface we've created previously; if the element contains a group of attributes, we inherit from corresponding interface again; when there are other attributes or elements referenced inside, we add it directly to our interface. See the example:
<xs:element name="body">
<xs:complexType>
<xs:complexContent>
<xs:extension base="Block">
<xs:attributeGroup ref="attrs"/>
<xs:attribute name="onload" type="Script"/>
<xs:attribute name="onunload" type="Script"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
public interface IBody : IBlockComplexType, IHaveCommonAttrs
{
void OnLoad(string onLoad);
void OnUnload(string onUnload);
}
Each time we add methods to any of our interface, our ElementImpl class needs to grow. Every new method corresponds to either an attribute or a child element - in both cases the implementation is very simple:
public void Body(Action<IBody> action)
{
_nb.AddNode(action);
}
public void Id(string id)
{
_nb.SetAttribute("id", id);
}
It just calls an appropriate NodeBuilder's method. In case of nodes, we rely on the type of Action<T> parameter - that's all we need. The starting point method - Html.For<T>(Action<T> action) - looks pretty much the same - we can start from any point in XHTML tree by specifying the element's interface which is needed.
Usage example
Let's take the first example from XHTML article in Wikipedia and build it using NOtherHtml.
var html = Html.For(x =>
{
x.Lang("en");
x.Head(head =>
{
head.Meta(meta =>
{
meta.HttpEquiv("Content-Type");
meta.Content("text/html; charset=UTF-8");
});
head.Title(t => t.Content("XHTML 1.0 Strict Example"));
head.Script(script =>
{
script.Type("text/javascript");
script.CData(@"function loadpdf() {
document.getElementById(""pdf-object"").src=""http://www.w3.org/TR/xhtml1/xhtml1.pdf"";
}");
});
});
x.Body(body =>
{
body.OnLoad("loadpdf()");
body.P(p =>
{
p.Content("This is an example of an");
p.Abbr(abbr =>
{
abbr.Title("Extensible HyperText Markup Language");
abbr.Content("XHTML");
});
p.Content("1.0 Strict document.");
p.Br();
p.Img(img =>
{
img.Id("validation-icon");
img.Src("http://www.w3.org/Icons/valid-xhtml10");
img.Alt("Valid XHTML 1.0 Strict");
});
p.Br();
p.Object(obj =>
{
obj.Id("pdf-object");
obj.Name("pdf-object");
obj.Type("application/pdf");
obj.Data("http://www.w3.org/TR/xhtml1/xhtml1.pdf");
obj.Width("100%");
obj.Height("500");
});
});
});
});
Weeknesses (or rather strengths)
My basic implementation was to show the pattern for loquacious interface based on XSD only, and it was not intended to follow all the XSD constraints, i.e. it doesn't enforce that required attributes are set, like alt for <img>. But that's relatively easy to achieve - we can always change Img(Action<IImg> action) method and add required parameter there, i.e. Img(string alt, Action<IImg> action).
Similarly, if one finds Em(s => s.Content("emphasized text")) to be cumbersome, it's easy to change the implementation to allow calling Em("emphasised text") - it can even be implemented as an extension method:
public static class HtmlExtensions
{
public static void Em(this IHavePhraseElements parent, string content)
{
parent.Em(x => x.Content(content));
}
}
Hope you can see the power beneath all that simplicity. Loquacious interface patterns just allow us to build our APIs that perfectly suits our needs.