public
Description: Xml read/write with a nice ruby api
Clone URL: git://github.com/tobi/xml_node.git
xml_node / lib / xml_node.rb
100644 222 lines (185 sloc) 4.245 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
require 'rexml/document'
 
class Object
  def to_xml_value
    to_s
  end
end
 
class NilClass
  def to_xml_value
    nil
  end
end
 
class TrueClass
  def to_xml_value
    to_s
  end
end
 
class FalseClass
  def to_xml_value
    to_s
  end
end
 
class Time
  def to_xml_value
    self.xmlschema
  end
end
 
class DateTime
  def to_xml_value
    self.xmlschema
  end
end
 
class Date
  def to_xml_value
    self.to_time.xmlschema
  end
end
 
class REXML::Element
  def to_xml_element
    self
  end
end
 
class XmlNode
  attr_accessor :child_nodes
  attr_reader :element
  
  class List
    include Enumerable
    
    def initialize(parent)
      @parent = parent
      @children = {}
    end
 
    def [](value)
      node_for @parent.element.elements[value]
    end
    
    def []=(value, key)
      @parent.element.elements[value.to_s] = key.to_xml_element
    end
    
    def each(&block)
      @parent.element.each_element { |e| yield node_for(e) }
    end
    
    private
    
    def node_for(element)
      @parent.child_nodes[element] ||= XmlNode.new(element)
    end
  end
  
  # Allows for very pretty xml generation akin to xml builder.
  #
  # Example:
  #
  # # Create an atom like document
  # doc = Document.new
  # doc.root = XmlNode.new 'feed' do |feed|
  #
  # feed << XmlNode.new('id', 'tag:atom.com,2007:1')
  # feed << XmlNode.new('title', 'Atom test feed')
  # feed << XmlNode.new('author') do |author|
  # author << XmlNode.new("name", "tobi")
  # author << XmlNode.new("email", "tobi@gmail.com")
  # end
  #
  # feed << XmlNode.new('entry') do |entry|
  # entry << XmlNode.new('title', 'First post')
  # entry << XmlNode.new('summary', 'Lorem ipsum', :type => 'xhtml')
  # entry << XmlNode.new('created_at', Time.now)
  # end
  #
  # feed << XmlNode.new('dc:published', Time.now)
  # end
  #
  def initialize(node, *args)
    @element = if node.is_a?(REXML::Element)
      node
    else
      REXML::Element.new(node)
    end
    
    @child_nodes = {}
    
    if attributes = args.last.is_a?(Hash) ? args.pop : nil
      attributes.each { |k,v| @element.add_attribute(k.to_s, v.to_xml_value) }
    end
    
    if !args[0].nil?
      @element.text = args[0].to_xml_value
    end
 
    if block_given?
      yield self
    end
  end
 
  def self.parse(xml)
    self.new(REXML::Document.new(xml).root)
  end
  
  def children
    XmlNode::List.new(self)
  end
  
  def []=(key, value)
    @element.attributes[key.to_s] = value.to_xml_value
  end
  
  def [](key)
    @element.attributes[key]
  end
  
  # Add a namespace to the node
  # Example
  #
  # node.namespace 'http://www.w3.org/2005/Atom'
  # node.namespace :opensearch, 'http://a9.com/-/spec/opensearch/1.1/'
  #
  def namespace(*args)
    args[0] = args[0].to_s if args[0].is_a?(Symbol)
    @element.add_namespace(*args)
  end
  
  def cdata=(value)
    new_cdata = REXML::CData.new( value )
    @element.children.each do |c|
      if c.is_a?(REXML::CData)
        return @element.replace_child(c,new_cdata)
      end
    end
    @element << new_cdata
  rescue RuntimeError => e
    @element << REXML::Text.new(e.message)
  end
  
  def cdata
    @element.cdatas.first.to_s
  end
  
  def name
    @element.name
  end
  
  def text=(value)
    @element.text = REXML::Text.new( value )
  end
  
  def text
    @element.text
  end
  
  def find(scope, xpath)
    case scope
    when :first
      elem = @element.elements[xpath]
      elem.nil? ? nil : child_nodes[elem] ||= XmlNode.new(elem)
    when :all
      @element.elements.to_a(xpath).collect { |e| child_nodes[e] ||= XmlNode.new(e) }
    end
  end
  
  def <<(elem)
    case elem
    when nil then return
    when Array
      elem.each { |e| @element << e.to_xml_element }
    else
      @element << elem.to_xml_element
    end
  end
    
  def to_xml_element
    @element
  end
  
  def to_s
    @element.to_s
  end
  
  # Use to get pretty formatted xml including DECL
  # instructions
  def to_xml
    xml = []
    document = REXML::Document.new
    document << REXML::XMLDecl.new('1.0')
    document << @element
    document.write( xml, 0)
    xml.join
  end
  
end