Class: XmlStruct
- Inherits:
-
Object
- Object
- XmlStruct
- Defined in:
- lib/xml_struct.rb
Overview
Purpose
XMLStruct is a specialised version of OpenStruct that allows you to create XML layout automagically using Ruby assignment. The class supports XML tag attributes and element content.
The class has been designed so that you can rapidly change a flat dataset into an XML layout without all that tedious messing about with Xpaths, tree traversal, nested blocks and the like.
Usage
Create a blank document using the usual #new method.
xml = XmlStruct.new
Add items as required and output using the #to_s command
xml.GovTalkMessage = {:xmlns => "http://www.govtalk.gov.uk/CM/envelope"}
md = xml.GovTalkMessage.Header.MessageDetails
md.Class="IR-PAYE-EOY"
md.Transformation="XML"
xml.GovTalkMessage.GovTalkDetails
xml.GovTalkMessage.Body
xml.to_s
gives
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<Header>
<MessageDetails>
<Class>IR-PAYE-EOY</Class>
<Transformation>XML</Transformation>
</MessageDetails>
</Header>
<GovTalkDetails>
</GovTalkDetails>
<Body>
</Body>
</GovTalkMessage>
Content
Xml content is added using standard Ruby assign. Any wrapper tags are created automatically.
xml.GovTalkMessage.Header.MessageDetails.Class="IR-PAYE-EOY"
XML is output using the #to_s method. The above would output as:
<GovTalkMessage>
<Header>
<MessageDetails>
<Class>IR-PAYE-EOY</Class>
</MessageDetails>
</Header>
</GovTalkMessage>
Blank elements with no content are created by simply referencing the element.
xml.GovTalkMessage.Body
Content is stored in an array like manner within the structure so that you can have multiple elements with the same tag, e.g.
xml.Address.Line[0] = "1 Some Street"
xml.Address.Line[1] = "Somewhere"
xml.Address.Line[2] = "Sometown"
xml.to_s
gives
<Address>
<Line>1 Some Street</Line>
<Line>Somewhere</Line>
<Line>Sometown</Line>
</Address>
Any element can be referenced to a variable as a shortcut. That address example again.
ln = xml.Address.Line
ln[0] = "1 Some Street"
ln[1] = "Somewhere"
ln[2] = "Sometown"
To make things really simple assigning an array of values to an element creates multiple elements with the same tag.
xml.Address.Line = ["1 Some Street", "Somewhere", "Sometown"]
Attributes
You set attributes for elements by assigning a hash of attributes to the element, e.g
xml.GovTalkMessage = {:xmlns => "http://www.govtalk.gov.uk/CM/envelope"}
xml.to_s
gives
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
</GovTalkMessage>
Each element can have its own set of attributes - even those with the same element name, eg.
xml.Keys.Key[0]={:Type => "VendorNumber"}
xml.Keys.Key[0]=275687
xml.Keys.Key[1]={:Type => "MainCPH"}
xml.Keys.Key[1]="14/02/0327"
xml.to_s
gives
<Keys>
<Key Type="VendorNumber">275687</Key>
<Key Type="MainCPH">14/02/0327</Key>
</Keys>
Multiple attributes are simply a matter of enumerating all the attributes in the hash, eg.
xml.Keys.Key = {:Type => "Unique Tax Reference", :Length => "10"}
gives
<Keys>
<Key Type="Unique Tax Reference" Length="10">
</Key>
</Keys
Caveats
XMLStruct undefines all the instance methods that may clash with XML tags except for inspect
. So if you have a file with an <inspect>
XML tag then the class won’t work without modification.
If you need to add methods to this class or any subclass, then use the ! and ? suffixes. Otherwise the method will clash with the XML tag of the same name. (Fortunately ! and ? are not valid XML tag characters).
There is no ‘delete’ or ‘reorder’ methods within this class. The class is designed to take flat data and impose an XML structure on it statically. So the original purpose doesn’t require anything other than assign and output facilities. Still I hope you find it useful.
Instance Method Summary collapse
-
#[](index) ⇒ Object
Allows you to access the content values for a particular element.
-
#[]=(index, rval) ⇒ Object
Assigning to [0] is the same as the default assign.
-
#initialize(tag = "") ⇒ XmlStruct
constructor
Creates a new XmlStruct document.
-
#method_missing(method_id, *args) ⇒ Object
The
method_missing
method implements the Proxy pattern for this class. -
#to_s ⇒ Object
Converts the XMLStruct into XML format.
Constructor Details
#initialize(tag = "") ⇒ XmlStruct
188 189 190 191 192 193 194 |
# File 'lib/xml_struct.rb', line 188 def initialize (tag = "") @my_tag = tag @content = [] @attributes = [] @children = {} @create_order = [] end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_id, *args) ⇒ Object
The method_missing
method implements the Proxy pattern for this class. It automatically creates any attribute if it is missing and handles the default assignment by passing the assignment onto the [0] element.
Methods that cannot be XML tags (essentially those ending in ? and !) are raised as errors.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/xml_struct.rb', line 244 def method_missing(method_id, *args) # :nodoc: method_name = method_id.id2name len = args.length if method_name[-1] == ?= unless len == 1 raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) end if frozen? raise TypeError, "can't modify frozen object", caller(1) end method_name.chop! unless method_name =~ /^[a-z:_][\w:\.-]*$/i raise NoMethodError, "undefined method '#{method_name}'", caller(1) end child = obtain_accessor!(method_name) child[0] = args[0] elsif method_name =~ /^[a-z:_][\w:\.-]*$/i if len == 0 obtain_accessor!(method_name) else raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) end else raise NoMethodError, "undefined method '#{method_name}'", caller(1) end end |
Instance Method Details
#[](index) ⇒ Object
Allows you to access the content values for a particular element. The default assign places content in [0], and you use that index to read the content back, e.g.
xml.Address.PostCode = "EC1A 5SW"
xml.Address.PostCode[0]
=> "EC1A 5SW"
206 207 208 |
# File 'lib/xml_struct.rb', line 206 def [](index) @content[index] end |
#[]=(index, rval) ⇒ Object
Assigning to [0] is the same as the default assign. The following are equivalent
xml.Address.PostCode = "EC1A 5SW"
xml.Address.PostCode[0] = "EC1A 5SW"
Assigning to indexes greater than zero creates an additional element with the same tag but different content
xml.Address.Line[0] = "1 Some Street"
xml.Address.Line[1] = "Somewhere"
However the default assign with an array is easier to use.
xml.Address.Line = [ "1 Some Street", "Somewhere" ]
226 227 228 229 230 231 232 233 234 235 |
# File 'lib/xml_struct.rb', line 226 def []=(index, rval) case when rval.kind_of?(Hash) @attributes[index] = rval when rval.kind_of?(Array) rval.each_index { |i| @content[i] = rval[i] } else @content[index] = rval end end |
#to_s ⇒ Object
Converts the XMLStruct into XML format. Returns an empty string if the XMLStruct is empty.
275 276 277 278 279 280 |
# File 'lib/xml_struct.rb', line 275 def to_s %{#{}#{ @create_order.collect do |tag_id| @children[tag_id].to_s end.join}#{}} end |