# XML::Mixup: A mixin for XML markup ```ruby require 'xml-mixup' class Anything include XML::Mixup end something = Anything.new # generate a structure node = something.markup spec: [ { '#pi' => 'xml-stylesheet', type: 'text/xsl', href: '/transform' }, { '#dtd' => :html }, { '#html' => [ { '#head' => [ { '#title' => 'look ma, title' }, { '#elem' => :base, href: 'http://the.base/url' }, ] }, { '#body' => [ { '#h1' => 'Illustrious Heading' }, { '#p' => :lolwut }, ] }, ], xmlns: 'http://www.w3.org/1999/xhtml' } ] # `node` will correspond to the last thing generated. In this # case, it will be a text node containing 'lolwut'. doc = node.document puts doc.to_xml ``` ## Yet another XML markup generator? Some time ago, [I](https://doriantaylor.com/) wrote a Perl module called [Role::Markup::XML](https://metacpan.org/pod/Role::Markup::XML). I did this because I had a lot of XML to generate, and was dissatisfied with what was currently on offer. Now I have a lot of XML to generate using Ruby, and found a lot of the same things: ### Structure is generated by procedure calls Granted it's a lot nicer to do this sort of thing in Ruby, but at the end of the day, the thing generating the XML is a nested list of method calls — _not_ a declarative data structure. ### Document has to be generated all in one shot It's not super-easy to generate a piece of the target document and then go back and generate some more (although `Nokogiri::XML::Builder.with` is a nice start). This plus the last point leads to all sorts of cockamamy constructs which are almost as life-sucking as writing raw DOM routines. ### Hard to do surgery on existing documents This comes up a lot: you have an existing document and you want to add even just a single node to it — say, in between two nodes just for fun. Good luck with that. ### Enter `XML::Mixup` * __The input consists of ordinary Ruby data objects__ so you can build them up ahead of time, in bulk, transform them, etc., * __Sprinkle pre-built XML subtrees anywhere into the spec__ so you can memoize repeating elements, or otherwise compile a document incrementally, * __Attach new generated content anywhere:__ underneath a parent node, or before, after, or _instead of_ a node at the sibling level. ## The spec format At the heart of this module is a single method called `markup`, which, among other things, takes a `:spec`. The spec can be any composite ### Hashes The principal construct in `XML::Mixup` is the `Hash`. You can generate pretty much any node with it: #### Elements ```ruby { '#tag' => 'foo' } # => # or { '#elem' => 'foo' } # => # or, with the element name as a symbol { '#element' => :foo } # => # or, with nil as a key { nil => :foo } # => # or, with attributes { nil => :foo, bar: :hi } # => # or, with namespaces { nil => :foo, xmlns: 'urn:x-bar' } # => # or, with more namespaces { nil => :foo, xmlns: 'urn:x-bar', 'xmlns:hurr' => 'urn:x-durr' } # => # or, with content { nil => [:foo, :hi] } # => hi # or, shove your child nodes into an otherwise content-less key { [:hi] => :foo, bar: :hurr } # => hi ``` Attributes are sorted lexically. Composite attribute values get flattened like this: ```ruby { nil => :foo, array: [:a, :b], hash: { e: :f, c: :d } } # => ``` #### Processing instructions ```ruby { '#pi' => 'xml-stylesheet', type: 'text/xsl', href: '/transform' } # => # or, if you like typing { '#processing-instruction' => :hurr } # => ``` #### `DOCTYPE` declarations ```ruby { '#dtd' => :html } # => # or (note either :public or :system can be nil) { '#dtd' => [:html, :public, :system] } # => # or, same thing { '#doctype' => :html, public: :public, system: :system } ``` #### Comments and `CDATA` sections Comments and `CDATA` are flattened into string literals: ```ruby { '#comment' => :whatever } # => { '#cdata' => '' } # => ]]> ``` Pretty straight forward? ### Arrays Parts of a spec that are arrays (or really anything that can be turned into one) are attached at the same level of the document in the sequence given, as you might expect. ### `Nokogiri::XML::Node` objects These are automatically cloned, but otherwise passed in as-is. ### `Proc`s, lambdas etc. These are executed with any supplied `:args`, and then `markup` is run again over the result. (Take care not to supply a `Proc` that produces another `Proc`.) ### Everything else Turned into a text node. ## Installation Come on, you know how to do this: $ gem install xml-mixup ## Contributing Bug reports and pull requests are welcome at https://github.com/doriantaylor/rb-xml-mixup. ## License This software is provided under the [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).