Module: HappyMapper::ClassMethods
- Defined in:
- lib/happymapper.rb
Instance Method Summary collapse
-
#after_parse {|object| ... } ⇒ Object
Register a new after_parse callback, given as a block.
-
#after_parse_callbacks ⇒ Object
The list of registered after_parse callbacks.
-
#attribute(name, type, options = {}) ⇒ Object
The xml has the following attributes defined.
-
#attributes ⇒ Array<Attribute>
The elements defined through #attribute.
-
#content(name, type = String, options = {}) ⇒ Object
The value stored in the text node of the current element.
-
#element(name, type, options = {}) ⇒ Object
An element defined in the XML that is parsed.
- #elements ⇒ Array<Element>
-
#has_many(name, type, options = {}) ⇒ Object
The object has many of these elements in the XML.
-
#has_one(name, type, options = {}) ⇒ Object
The object has one of these elements in the XML.
-
#has_xml_content ⇒ Object
Sets the object to have xml content, this will assign the XML contents that are parsed to the attribute accessor xml_content.
-
#namespace(namespace = nil) ⇒ Object
Specify a namespace if a node and all its children are all namespaced elements.
-
#nokogiri_config_callback ⇒ Proc
The callback defined through #with_nokogiri_config.
- #parse(xml, options = {}) ⇒ Object
-
#register_namespace(namespace, ns) ⇒ Object
Register a namespace that is used to persist the object namespace back to XML.
- #tag(new_tag_name) ⇒ Object
-
#tag_name ⇒ String
The name of the tag.
-
#with_nokogiri_config(&blk) ⇒ Object
Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse().
-
#wrap(name, &blk) ⇒ Object
There is an XML tag that needs to be known for parsing and should be generated during a to_xml.
Instance Method Details
#after_parse {|object| ... } ⇒ Object
Register a new after_parse callback, given as a block.
200 201 202 |
# File 'lib/happymapper.rb', line 200 def after_parse(&block) after_parse_callbacks.push(block) end |
#after_parse_callbacks ⇒ Object
The list of registered after_parse callbacks.
191 192 193 |
# File 'lib/happymapper.rb', line 191 def after_parse_callbacks @after_parse_callbacks ||= [] end |
#attribute(name, type, options = {}) ⇒ Object
The xml has the following attributes defined.
53 54 55 56 57 |
# File 'lib/happymapper.rb', line 53 def attribute(name, type, = {}) attribute = Attribute.new(name, type, ) @attributes[name] = attribute attr_accessor attribute.method_name.to_sym end |
#attributes ⇒ Array<Attribute>
The elements defined through #attribute.
65 66 67 68 |
# File 'lib/happymapper.rb', line 65 def attributes @attributes ||= {} @attributes.values end |
#content(name, type = String, options = {}) ⇒ Object
The value stored in the text node of the current element.
144 145 146 147 |
# File 'lib/happymapper.rb', line 144 def content(name, type = String, = {}) @content = TextNode.new(name, type, ) attr_accessor @content.method_name.to_sym end |
#element(name, type, options = {}) ⇒ Object
An element defined in the XML that is parsed.
110 111 112 113 114 |
# File 'lib/happymapper.rb', line 110 def element(name, type, = {}) element = Element.new(name, type, ) @elements[name] = element attr_accessor element.method_name.to_sym end |
#elements ⇒ Array<Element>
123 124 125 126 |
# File 'lib/happymapper.rb', line 123 def elements @elements ||= {} @elements.values end |
#has_many(name, type, options = {}) ⇒ Object
The object has many of these elements in the XML.
184 185 186 |
# File 'lib/happymapper.rb', line 184 def has_many(name, type, = {}) element name, type, { single: false }.merge() end |
#has_one(name, type, options = {}) ⇒ Object
The object has one of these elements in the XML. If there are multiple, the last one will be set to this value.
170 171 172 |
# File 'lib/happymapper.rb', line 170 def has_one(name, type, = {}) element name, type, { single: true }.merge() end |
#has_xml_content ⇒ Object
Sets the object to have xml content, this will assign the XML contents that are parsed to the attribute accessor xml_content. The object will respond to the method #xml_content and will return the XML data that it has parsed.
155 156 157 |
# File 'lib/happymapper.rb', line 155 def has_xml_content attr_accessor :xml_content end |
#namespace(namespace = nil) ⇒ Object
Specify a namespace if a node and all its children are all namespaced elements. This is simpler than passing the :namespace option to each defined element.
212 213 214 215 |
# File 'lib/happymapper.rb', line 212 def namespace(namespace = nil) @namespace = namespace if namespace @namespace end |
#nokogiri_config_callback ⇒ Proc
The callback defined through #with_nokogiri_config.
269 270 271 |
# File 'lib/happymapper.rb', line 269 def nokogiri_config_callback @nokogiri_config_callback end |
#parse(xml, options = {}) ⇒ Object
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/happymapper.rb', line 290 def parse(xml, = {}) # create a local copy of the objects namespace value for this parse execution namespace = @namespace # If the XML specified is an Node then we have what we need. if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document) node = xml else # If xml is an XML document select the root node of the document if xml.is_a?(Nokogiri::XML::Document) node = xml.root else # Attempt to parse the xml value with Nokogiri XML as a document # and select the root element xml = Nokogiri::XML( xml, nil, nil, Nokogiri::XML::ParseOptions::STRICT, &nokogiri_config_callback ) node = xml.root end # if the node name is equal to the tag name then the we are parsing the # root element and that is important to record so that we can apply # the correct xpath on the elements of this document. root = node.name == tag_name end # if any namespaces have been provied then we should capture those and then # merge them with any namespaces found on the xml node and merge all that # with any namespaces that have been registered on the object namespaces = [:namespaces] || {} namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces) namespaces = namespaces.merge(@registered_namespaces) # if a namespace has been provided then set the current namespace to it # or set the default namespace to the one defined under 'xmlns' # or set the default namespace to the namespace that matches 'happymapper's if [:namespace] namespace = [:namespace] elsif namespaces.key?('xmlns') namespace ||= DEFAULT_NS namespaces[DEFAULT_NS] = namespaces.delete('xmlns') elsif namespaces.key?(DEFAULT_NS) namespace ||= DEFAULT_NS end # from the options grab any nodes present and if none are present then # perform the following to find the nodes for the given class nodes = .fetch(:nodes) do # when at the root use the xpath '/' otherwise use a more gready './/' # unless an xpath has been specified, which should overwrite default # and finally attach the current namespace if one has been defined # xpath = (root ? '/' : './/') xpath = [:xpath].to_s.sub(/([^\/])$/, '\1/') if [:xpath] xpath += "#{namespace}:" if namespace nodes = [] # when finding nodes, do it in this order: # 1. specified tag if one has been provided # 2. name of element # 3. tag_name (derived from class name by default) # If a tag has been provided we need to search for it. if .key?(:tag) begin nodes = node.xpath(xpath + [:tag].to_s, namespaces) rescue # This exception takes place when the namespace is often not found # and we should continue on with the empty array of nodes. end else # This is the default case when no tag value is provided. # First we use the name of the element `items` in `has_many items` # Second we use the tag name which is the name of the class cleaned up [[:name], tag_name].compact.each do |xpath_ext| begin nodes = node.xpath(xpath + xpath_ext.to_s, namespaces) rescue break # This exception takes place when the namespace is often not found # and we should continue with the empty array of nodes or keep looking end break if nodes && !nodes.empty? end end nodes end # Nothing matching found, we can go ahead and return return (([:single] || root) ? nil : []) if nodes.size == 0 # If the :limit option has been specified then we are going to slice # our node results by that amount to allow us the ability to deal with # a large result set of data. limit = [:in_groups_of] || nodes.size # If the limit of 0 has been specified then the user obviously wants # none of the nodes that we are serving within this batch of nodes. return [] if limit == 0 collection = [] nodes.each_slice(limit) do |slice| part = slice.map do |n| # If an existing HappyMapper object is provided, update it with the # values from the xml being parsed. Otherwise, create a new object obj = [:update] ? [:update] : new attributes.each do |attr| value = attr.from_xml_node(n, namespace, namespaces) value = attr.default if value.nil? obj.send("#{attr.method_name}=", value) end elements.each do |elem| obj.send("#{elem.method_name}=", elem.from_xml_node(n, namespace, namespaces)) end if @content obj.send("#{@content.method_name}=", @content.from_xml_node(n, namespace, namespaces)) end # If the HappyMapper class has the method #xml_value=, # attr_writer :xml_value, or attr_accessor :xml_value then we want to # assign the current xml that we just parsed to the xml_value if obj.respond_to?('xml_value=') n.namespaces.each { |name, path| n[name] = path } obj.xml_value = n.to_xml end # If the HappyMapper class has the method #xml_content=, # attr_write :xml_content, or attr_accessor :xml_content then we want to # assign the child xml that we just parsed to the xml_content if obj.respond_to?('xml_content=') n = n.children if n.respond_to?(:children) obj.xml_content = n.to_xml end # Call any registered after_parse callbacks for the object's class obj.class.after_parse_callbacks.each { |callback| callback.call(obj) } # collect the object that we have created obj end # If a block has been provided and the user has requested that the objects # be handled in groups then we should yield the slice of the objects to them # otherwise continue to lump them together if block_given? && [:in_groups_of] yield part else collection += part end end # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354 nodes = nil # If the :single option has been specified or we are at the root element # then we are going to return the first item in the collection. Otherwise # the return response is going to be an entire array of items. if [:single] || root collection.first else collection end end |
#register_namespace(namespace, ns) ⇒ Object
Register a namespace that is used to persist the object namespace back to XML.
87 88 89 90 |
# File 'lib/happymapper.rb', line 87 def register_namespace(namespace, ns) @registered_namespaces ||= {} @registered_namespaces.merge!(namespace => ns) end |
#tag(new_tag_name) ⇒ Object
220 221 222 |
# File 'lib/happymapper.rb', line 220 def tag(new_tag_name) @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty? end |
#tag_name ⇒ String
The name of the tag
229 230 231 |
# File 'lib/happymapper.rb', line 229 def tag_name @tag_name ||= to_s.split('::')[-1].downcase end |
#with_nokogiri_config(&blk) ⇒ Object
Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse(). See nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
278 279 280 |
# File 'lib/happymapper.rb', line 278 def with_nokogiri_config(&blk) @nokogiri_config_callback = blk end |
#wrap(name, &blk) ⇒ Object
There is an XML tag that needs to be known for parsing and should be generated during a to_xml. But it doesn’t need to be a class and the contained elements should be made available on the parent class
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/happymapper.rb', line 240 def wrap(name, &blk) # Get an anonymous HappyMapper that has 'name' as its tag and defined # in '&blk'. Then save that to a class instance variable for later use wrapper = AnonymousWrapperClassFactory.get(name, &blk) @wrapper_anonymous_classes[wrapper.inspect] = wrapper # Create getter/setter for each element and attribute defined on the anonymous HappyMapper # onto this class. They get/set the value by passing thru to the anonymous class. passthrus = wrapper.attributes + wrapper.elements passthrus.each do |item| class_eval %{ def #{item.method_name} @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new @#{name}.#{item.method_name} end def #{item.method_name}=(value) @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new @#{name}.#{item.method_name} = value end } end has_one name, wrapper end |