rsxml
A Ruby library to translate XML documents into an s-expression representation, and back again, in the style of SXML
Why would you want to do this ? Well, s-expressions can be == compared natively in Ruby, are easy to read and editors indent them nicely when embedded in code. These features make them very suitable for writing readable XML generation code and readable tests for XML generating code
Rsxml uses Nokogiri for parsing XML, and Builder to generate it. Rsxml is not intended to be a feature complete XML processor : It does not attempt to process PIs, CDATA etc, but it does make it very easy to use and generate straightforward XML documents from Ruby
Rsxml represents XML documents as s-expressions thus :
["Foo", {"foofoo"=>"10"}, ["Bar", "barbar"], ["Baz"]]
represents the XML document :
<Foo foofoo="10"><Bar>barbar</Bar><Baz></Baz></Foo>
It is easy to convert XML docuemnts to Rsxml representation and back again :
xml = Rsxml.to_xml(["Foo", {"foofoo"=>"10"}, ["Bar", "barbar"]])
=> '<Foo foofoo="10"><Bar>barbar</Bar></Foo>'
Rsxml.to_rsxml(xml)
=> ["Foo", {"foofoo"=>"10"}, ["Bar", "barbar"]]
Namespaces
XML namespaces are dealt with straightforwardly. When an XML document is converted to Rsxml, namespaces are preserved, and you can specify namespaces in an Rsxml structure in two ways, which can be freely mixed
-
using QName prefixes and declarative attributes, exactly as with XML
-
using exploded QNames consisting of
[local_part, prefix, uri]
triples and[local_part, prefix]
pairs
Converting to Rsxml
When you convert an XML document to Rsxml you can choose either :xml
or :exploded
style
:xml
style
In :xml
style namespaces are declared using attributes, and namespaces are referenced using prefixed QNames, just as in XML
Rsxml.to_rsxml('<foo:foofoo xmlns:foo="http://foo.com/foo" foo:bar="barbar"/>', :style=>:xml)
=> ["foo:foofoo", {"foo:bar"=>"barbar", "xmlns:foo"=>"http://foo.com/foo"}]
:exploded
style
In :exploded
style namespaces are not declared using attributes, and QNames are specified using [local_part, prefix, uri]
triples
Rsxml.to_rsxml('<foo:foofoo xmlns:foo="http://foo.com/foo" foo:bar="barbar"/>', :style=>:exploded)
=> [["foofoo", "foo", "http://foo.com/foo"], {["bar", "foo", "http://foo.com/foo"]=>"barbar"}]
Converting to XML
Rsxml styles can be mixed, and unnecessary namespace references can be skipped for readability
Rsxml.to_xml([["foofoo", "foo", "http://foo.com/foo"], {"foo:bar"=>"1", ["baz", "foo"]=>"2"}])
=> '<foo:foofoo foo:baz="2" foo:bar="1" xmlns:foo="http://foo.com/foo"></foo:foofoo>'
Fragments
XML Fragments, without proper namespace declarations, can be parsed by passing a Hash of namespace prefix bindings
Rsxml.to_rsxml('<foo:foofoo foo:bar="barbar"/>', :ns=>{"foo"=>"http://foo.com/foo"}, :style=>:xml)
=> ["foo:foofoo", {"foo:bar"=>"barbar"}]
Fragments can be generated similarly :
Rsxml.to_xml(["foo:foofoo", {"foo:bar"=>"barbar"}], :ns=>{"foo"=>"http://foo.com/foo"})
=> '<foo:foofoo foo:bar="barbar"></foo:foofoo>'
Visitors
Rsxml.to_rsxml
and Rsxml.to_xml
are implemented with a simple Visitor pattern over the XML document structure. Rsxml::Sexp.traverse()
traverses a Rsxml s-expression representation of an XML document and Rsxml::Xml.traverse()
traverses a Nokogiri Node tree. The Visitor receives a
text(context, text)
invocation for each text node, and an
element(context, name, attrs, ns_decls)
method invocation for each element. If namespaces are used, element and attribute names in the element
method invocations are exploded [local_part, prefix, uri]
triples. The attributes are presented as a {name=>value}
Hash
which contains no namespace-related attributes. Any namespace declarations for the element are provided as the {prefix=>uri}
ns_decls
Hash
. Namespace prefixes, URIs and declaration attributes are cleanly separated in this API, so it is easy for Visitor implementations to correctly process XML documents with namespaces
Install
gem install rsxml
Contributing to rsxml
-
Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet
-
Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it
-
Fork the project
-
Start a feature/bugfix branch
-
Commit and push until you are happy with your contribution
-
Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright
Copyright © 2011 Trampoline Systems Ltd. See LICENSE.txt for further details.