Representable: XML
Last updated 05 May 2017 representable v3.0Representable XML
If you’re enjoying the pleasure of working with XML, Representable can help you. It does render and parse XML, too, with an almost identical declarative API.
require "representable/xml"
class SongRepresenter < Representable::Decorator
include Representable::XML
property :title
collection :composers
end
Note that you have to include the Representable::XML
module.
The public API then gives you to_xml
and from_xml
.
Song = Struct.new(:title, :composers)
song = Song.new("Fallout", ["Stewart Copeland", "Sting"])
SongRepresenter.new(song).to_xml
<song>
<title>Fallout</title>
<composers>Stewart Copeland</composers>
<composers>Sting</composers>
</song>
Tag Attributes
You can also map properties to tag attributes in Representable. This works only for the top-level node, though (seen from the representer’s perspective).
class SongRepresenter < Representable::Decorator
include Representable::XML
property :title, attribute: true
collection :composers
end
SongRepresenter.new(song).to_xml
<song title="Fallout">
<composers>Stewart Copeland</composers>
<composers>Sting</composers>
</song>
Naturally, this works both ways.
Mapping Content
The same concept can also be applied to content. If you need to map a property to the top-level node’s content, use the :content
option. Again, top-level refers to the document fragment that maps to the representer.
class SongRepresenter < Representable::Decorator
include Representable::XML
property :title, content: true
end
SongRepresenter.new(song).to_xml
<song>Fallout</song>
Wrapping Collections
It is sometimes unavoidable to wrap tag lists in a container tag.
class AlbumRepresenter < Representable::Decorator
include Representable::XML
collection :songs, as: :song, wrap: :songs
end
Album = Struct.new(:songs) album = Album.new([“Laundry Basket”, “Two Kevins”, “Wright and Rong”])
album_representer = AlbumRepresenter.new(album) album_representer.to_xml
Note that :wrap
defines the container tag name.
<album>
<songs>
<song>Laundry Basket</song>
<song>Two Kevins</song>
<song>Wright and Rong</song>
</songs>
</album>
Namespace
Namespaces in XML allow the use of different vocabularies, or set of names, in one document. Read this great article to share our fascination about them.
Where’s the EXAMPLE CODE?
The Namespace
module is available in Representable >= 3.0.4. It doesn’t work with JRuby due to Nokogiri’s extremely complex implementation. Please wait for Representable 4.0 where we replace Nokogiri.
For future-compat: Namespace
only works in decorator classes, not modules.
Namespace: Default
You can define one namespace per representer using ::namespace
to set the section’s default namespace.
class Library < Representable::Decorator
feature Representable::XML
feature Representable::XML::Namespace
namespace "http://eric.van-der-vlist.com/ns/library"
property :book do
namespace "http://eric.van-der-vlist.com/ns/library"
property :id, attribute: true
property :isbn
end
end
Nested representers can be inline or classes (referenced via :decorator
). Each class can maintain its own namespace.
Without any mappings, the namespace will be used as the default one.
%{<library xmlns="http://eric.van-der-vlist.com/ns/library">
<book id="1">
<isbn>666</isbn>
</book>
</library>}
Namespace: Prefix
After defining the namespace URIs in the representers, you can map them to a document-wide prefix in the top representer via ::namespace_def
.
class Library < Representable::Decorator
feature Representable::XML
feature Representable::XML::Namespace
namespace "http://eric.van-der-vlist.com/ns/library"
namespace_def lib: "http://eric.van-der-vlist.com/ns/library"
namespace_def hr: "http://eric.van-der-vlist.com/ns/person"
property :book, class: Model::Book do
namespace "http://eric.van-der-vlist.com/ns/library"
property :id, attribute: true
property :isbn
property :author, class: Model::Character do
namespace "http://eric.van-der-vlist.com/ns/person"
property :name
property :born
end
collection :character, class: Model::Character do
namespace "http://eric.van-der-vlist.com/ns/library"
property :qualification
property :name, namespace: "http://eric.van-der-vlist.com/ns/person"
property :born, namespace: "http://eric.van-der-vlist.com/ns/person"
end
end
end
Note how you can also use :namespace
to reference a certain differing prefix per property.
When rendering or parsing, the local property will be extended, e.g. /library/book/isbn
will become /lib:library/lib:book/lib:isbn
.
%{<lib:library xmlns:lib=\"http://eric.van-der-vlist.com/ns/library\" xmlns:hr=\"http://eric.van-der-vlist.com/ns/person\">
<lib:book id=\"1\">
<lib:isbn>666</lib:isbn>
<hr:author>
<hr:name>Fowler</hr:name>
</hr:author>
<lib:character>
<lib:qualification>typed</lib:qualification>
<hr:name>Frau Java</hr:name>
<hr:born>1991</hr:born>
</lib:character>
</lib:book>
</lib:library>}
The top representer will include all namespace definitions as xmlns
attributes.
Namespace: Parse
Namespaces also apply when parsing an XML document to an object structure. When defined, only the known, prefixed tags will be considered.
Library.new(lib).from_xml(%{<lib:library
lns:lib="http://eric.van-der-vlist.com/ns/library"
lns:hr="http://eric.van-der-vlist.com/ns/person">
ib:book id="1">
<lib:isbn>666</lib:isbn>
<hr:author>
<hr:name>Fowler</hr:name>
</hr:author>
<lib:character>
<lib:qualification>typed</lib:qualification>
<hr:name>Frau Java</hr:name>
<bogus:name>Mr. Ruby</hr:name>
<name>Dr. Elixir</hr:name>
<hr:born>1991</hr:born>
</lib:character>
lib:book>
b:library>}
In this example, only the /lib:library/lib:book/lib:character/hr:name
was parsed.
lib.book.character[0].name #=> "Frau Java"
If your incoming document has namespaces, please do use and specify them properly.
Namespace: Remove
If an incoming document contains namespaces, but you don’t want to define them in your representers, you can automatically remove them.
class AlbumRepresenter < Representable::Decorator
include Representable::XML
remove_namespaces!
This will ditch the namespace prefix and parse all properties as if they never had any prefix in the document, e.g. lib:author
becomes author
.
Removing namespaces is a Nokogiri hack. It’s absolutely not recommended as it defeats the purpose of XML namespaces and might result in wrong values being parsed and interpreted.