DynamicBuilder
Suspiciously pleasant XML construction API for C# 4, inspired by Ruby's Builder
Install / Use
/learn @mmonteleone/DynamicBuilderREADME
DynamicBuilder
[http://github.com/mmonteleone/DynamicBuilder][1]
Suspiciously pleasant XML construction API for [C# 4][5], inspired by Ruby's [Builder][4]
What?
Inspired heavily by the Ruby [Builder][4] library, DynamicBuilder exploits new features of [C# 4][5] like dynamic method invocation and optional parameters along with anonymous objects and delegates to yield an API which accomplishes the unthinkable: pleasant XML construction with .NET. The API can be learned in five minutes and integrated into existing code in even less time as it's simply a single small class.
Examples!
Nodes via dynamic invocation
dynamic xml = new DynamicBuilder.Xml();
// non-existent "hello" method resolves to a "hello" node at runtime
xml.hello("world");
// yields <hello>world</hello>
Attributes via anonymous objects
dynamic xml = new DynamicBuilder.Xml();
// passing an anonymous object resolves to xml attributes
xml.user("John Doe", new { username="jdoe" = 2, usertype = "admin" });
// yields <user username="jdoe" usertype="admin">John Doe</user>
Nesting via anonymous delegates
dynamic xml = new DynamicBuilder.Xml();
// passing an anonymous delegate creates a nested context
xml.user(Xml.Fragment(u => {
u.firstname("John");
u.lastname("Doe");
u.email("jdoe@example.org");
}));
// yields...
<user>
<firstname>John</firstname>
<lastname>Doe</lastname>
<email>jdoe@example.org</email>
</user>
Putting it all together: building an Atom syndication feed:
// First let's get some posts from a hypothetical `postRepository`
IList<BlogPost> posts = postRepository.GetLatest(50);
// now let's build an atom feed dynamically
dynamic xml = new DynamicBuilder.Xml();
// set an xml declaration tag
xml.Declaration();
// create the feed and metadata
xml.feed(new { xmlns = "http://www.w3.org/2005/Atom" }, Xml.Fragment(feed =>
{
feed.title("My Blog!");
feed.subtitle("Others have blogs too, but this one is mine");
feed.link(new { href = "http://example.org" });
feed.link(new { href = "http://example.org/feed.xml", rel = "self" });
feed.author(Xml.Fragment(author =>
{
author.name("John Doe");
author.email("johndoe@example.org");
}));
// iterate through the posts, adding them to the feed
// note this part could not have been done simply via nested
// object initializers with System.Xml.Linq types
foreach (var post in posts)
{
feed.entry(Xml.Fragment(entry =>
{
entry.title(post.Title);
entry.link(new { href = post.PermaLink });
entry.updated(post.PublishDate);
entry.summary(post.Content);
}));
}
}));
Comparison to other XML-generation methods:
The System.Xml Ghetto
The mean streets. Power and control meets, well, nothing. The original System.Xml types, with us since .NET 1.0, can be quite tedious to manipulate directly and have grown anachronistically low-level.
// Direct node creation with System.Xml types
XmlDocument doc = new XmlDocument();
XmlElement userElement = doc.CreateElement("user");
doc.AppendChild(userElement);
XmlElement firstNameElement = doc.CreateElement("firstname");
firstNameElement.InnerText = "John";
userElement.AppendChild(firstNameElement);
XmlElement lastNameElement = doc.CreateElement("lastname");
lastNameElement.InnerText = "Doe";
userElement.AppendChild(lastNameElement);
XmlElement emailElement = doc.CreateElement("email");
emailElement.InnerText = "jdoe@example.org";
userElement.AppendChild(emailElement);
doc.Save(Console.Out);
// Xml creation with an XmlTextWriter - maybe better?
XmlTextWriter writer = new XmlTextWriter(Console.Out);
writer.WriteStartElement("user");
writer.WriteElementString("firstname", "John");
writer.WriteElementString("firstname", "Doe");
writer.WriteElementString("email", "jdoe@example.org");
writer.WriteEndElement();
writer.Close();
While these types still are behind the scenes of all subsequent .NET XML APIs (including DynamicBuilder), their verbose syntaxes mean they are no longer the best option for direct XML creation.
The System.Xml.Serialization Suburbs
Medicated and mostly harmless. This is an attractive choice when your serializable types map exactly to the XML you wish to generate. Otherwise, hope you like creating boilerplate serializable classes and adapters just for serialization, or that you actually enjoy XSLT.
[Serializable]
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
User user = new User();
XmlSerializer x = new XmlSerializer(typeof(User));
x.Serialize(Console.Out, user);
DynamicBuilder allows code that is just as terse as a Serializable class while still retaining the flexibility of manually generating specific XML content.
The System.Xml.Linq New Urbanism
Attractive but superficial. C# 3.0 brought LINQ to XML, and with it, System.Xml.Linq. This revolutionized both the programmatic querying of XML as well as the declarative construction of it via object initialization.
XElement user = new XElement("user",
new XElement("firstname", "John"),
new XElement("lastname", "Doe"),
new XElement("email", "jdoe@exampe.org")
);
While a terrific improvement, it's still troublesome to use when the document must be generated programmatically. The simple case of iterating over a list of blog posts in the above Atom feed example is not possible with the XElement object initialization syntax without building the document in multiple separate looped chunks and then adding the parts together. DynamicBuilder's use of anonymous delegates instead of just object initialization allows for all manner of logic within a single, unified, XML declaration. DynamicBuilder's anonymous object-to-attributes are also terser than instantiating multiple XAttributes.
LINQ to XML remains the simplest XML querying/consumption mechanism, even against DynamicBuilder.Xml. DynamicBuilder.Xml actually uses System.Xml.Linq types internally to model its XML, and can easily expose it via xmlInstance.ToXElement().
Installation
Requirements
DynamicBuilder requires the .NET 4 framework to compile and/or run its xUnit test suite.
Installation
Since this is such a small piece of code (just a small single class), it is recommended to simply copy the source, Xml.cs, directly into your project. It does not really warrant the overhead of being a referenced, compiled, assembly.
- Download the source.
cdinto the project's directory> build release- Copy build\Release\Xml.cs into your own project.
- Alternatively, the assembly
DynamicBuilder.dllcan be added to your project as well.
- Alternatively, the assembly
- Either modify
Xml.csto share your project's namespace, or stateusing DynamicBuilderwithin your code
To run DynamicBuilder's [xUnit][2]-based tests, use > build test
Usage
Declaring XML
Instantiation
Create a new dynamic xml instance
dynamic xml = new DynamicBuilder.Xml();
Adding nodes
Unresolvable methods on the xml instance will dynamically resolve to new XML nodes
xml.foo(); // yields <foo />
xml.foo("bar"); // yields <foo>bar</foo>
xml.count(2); // yields <count>2</count>
Attributes can be specified via anonymous objects
xml.foo("bar", new { spam = "eggs" }); // yields <foo spam="eggs">bar</foo>
Nested nodes can be specified with anonymous delegates (although due to a limitation of C# 4, they need to be either explicitly cast as Action or simply passed through the Xml.Fragment() helper method which does this for us in a more fluent manner.
// yields <foo><bar>baz</bar></foo>
xml.foo(Xml.Fragment(() => {
xml.bar("baz");
}));
// this yields the exact same, but allows aliasing of the `xml`
// instance within nested contexts for greater readability
xml.foo(Xml.Fragment(foo => {
foo.bar("baz");
}));
Adding Special Content
An XML declaration node can be added via the .Declaration() method
// yields <?xml version="1.0" encoding="utf-8"?>
xml.Declaration();
// yields <?xml version="1.0" encoding="utf-8"?>
xml.Declaration(encoding: "utf-8");
// yields <?xml version="1.0" encoding="utf-16" standalone="yes"?>
xml.Declaration(encoding: "utf-16", standalone: "yes");
A Document Type can be added via the .DocumentType() method
// yields <!DOCTYPE html>
xml.DocumentType("html");
// yields
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
// http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
xml.DocumentType("html",
publicId: "-//W3C//DTD XHTML 1.0 Strict//EN",
systemId: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
Comments can be added via the Comment() method
xml.Comment("Foo"); // yields <!--foo-->
CData can be added via the CData() method
xml.CData("data content"); // yields <![CDATA[data content]]>
Text can be added via the Text() method
xml.Text("raw text"); // yields "raw text"
Escaping
In the event that you need to name a tag the same as any of the special content methods, you can escape the name with an underscore.
xml._Comment("foo"); // yields <Comment>foo</Comment>
Namespaces
Control of namespacing is still an area op
Related Skills
node-connect
343.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
92.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
