Class: XMLCodec::XMLElement

Inherits:
Object
  • Object
show all
Defined in:
lib/element.rb

Overview

This class should be inherited from to create classes that are able to import and export XML elements and their children. It provides three main functions: xmlattr, xmlsubel and xmlsubel_mult.

To create an importer/exporter for a XML format all that’s needed is to create a class for each of the elements and then declare their atributes and subelements.

Two other functions have an important role. elname declares the name of the XML element the class represents. elwithvalue declares that the element has no subelements and includes only text content.

After the class is defined import_xml can be used to import the content from a Nokogiri Element or Document and create_xml can be used to create the XML DOM of the element as a child to a Nokogiri Element or Document. For big documents these are usually too slow and memory hungry, using xml_text to export to XML and import_xml_text to import XML are probably better ideas. import_xml_text is just a utility function around XMLStreamObjectParser, that allow more flexible stream parsing of XML files while still using the same XMLElement objects.

Constant Summary collapse

INDENT_STR =
'  '
CACHE =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#__parentObject

Returns the value of attribute __parent.



33
34
35
# File 'lib/element.rb', line 33

def __parent
  @__parent
end

#__xml_textObject

Returns the value of attribute __xml_text.



32
33
34
# File 'lib/element.rb', line 32

def __xml_text
  @__xml_text
end

#element_idObject

Returns the value of attribute element_id.



32
33
34
# File 'lib/element.rb', line 32

def element_id
  @element_id
end

#parent_idObject

Returns the value of attribute parent_id.



32
33
34
# File 'lib/element.rb', line 32

def parent_id
  @parent_id
end

Class Method Details

._import_xml_dom(xmlel) ⇒ Object

Import the XML into an object from a Nokogiri XML Node or Document. This call is recursive and imports any subelements found into the corresponding objects.



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
# File 'lib/element.rb', line 395

def self._import_xml_dom(xmlel)
  if xmlel.is_a? Nokogiri::XML::Document
    xmlel = xmlel.root
  end
  
  elements = []
  xmlel.children.each do |e|
    if e.text?
      elements << e.text
    else
      elclass = get_element_class(e.name)
      if not elclass
        if class_variable_get('@@strict_parsing')
        raise ElementClassNotFound, "No class defined for element type: '#{e.name}'"  
        end
    else
        elements << elclass._import_xml_dom(e)
      end
    end
  end
  
  attributes = {}
  xmlel.attributes.each do |name, attr|
    attributes[name] = attr.value
  end
  
  elclass = get_element_class(xmlel.name)
  return nil if not elclass
  elclass.new_with_content(attributes, elements)
end

._import_xml_text(text) ⇒ Object

Import the XML directly from the text.



427
428
429
430
431
# File 'lib/element.rb', line 427

def self._import_xml_text(text)
  parser = XMLStreamObjectParser.new(self)
  parser.parse(text)
  parser.top_element
end

.get_element_class(name) ⇒ Object

Gets the class for a certain element name.



338
339
340
341
342
343
344
# File 'lib/element.rb', line 338

def self.get_element_class(name)
  cl = elclasses[name.to_sym]
 if not cl and class_variable_get('@@strict_parsing')
  raise ElementClassNotFound, "No class defined for element type: '" + name.to_s + "'"
end
cl
end

.get_element_names(name) ⇒ Object

Gets the possible element names for a certain element.



347
348
349
# File 'lib/element.rb', line 347

def self.get_element_names(name)
  get_element_class(name).get_elnames
end

.import_xml(obj) ⇒ Object

Import the XML into an object from a Nokogiri XML Node or Document or from a string.



381
382
383
384
385
386
387
388
389
390
# File 'lib/element.rb', line 381

def self.import_xml(obj)
  if obj.instance_of? String
    _import_xml_text(obj)
  elsif obj.instance_of? Nokogiri::XML::Node or 
        obj.instance_of? Nokogiri::XML::Document
    _import_xml_dom(obj)
  else
    nil
  end
end

.new_with_content(attrs, children) ⇒ Object

Create a new element passing it all the atributes, children and texts



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/element.rb', line 434

def self.new_with_content(attrs, children)
  text_children = []
  element_children = []
  
  children.each do |c|
    if c.is_a? String
      text_children << c
    else
      element_children << c
    end
  end

  obj = self.allocate
  obj.add_attr(attrs)
  obj.add_subel(element_children)
  obj.add_texts(text_children)
  if obj.has_subelements?
    obj.add_subelements(children)
  end
  obj
end

Instance Method Details

#add_attr(attrs) ⇒ Object

add the attributes passed as a hash to the element



457
458
459
460
461
462
463
464
465
466
467
# File 'lib/element.rb', line 457

def add_attr(attrs)
  attrs.each do |name, value|
    if not self.class.attr_names.include?(name.to_sym)
      if self.class.class_variable_get('@@strict_parsing')
        raise ElementAttributeNotFound, "No attribute '#{name}' defined for class '#{self.class}'" 
      end
    else
      self.send("#{name}=", value)
    end
  end
end

#add_subel(children) ⇒ Object

add the subelements into the element



477
478
479
480
481
482
483
484
485
486
487
# File 'lib/element.rb', line 477

def add_subel(children)
  children.each do |c|
    if subel_name = get_subel(c.class)
      if self.class.subel_mult? subel_name
        self.send(subel_name) <<  c
      else
        self.send(subel_name.to_s+'=', c)
      end 
    end
  end
end

#add_subelements(all_children) ⇒ Object

If the class is one with many subelements import all of them into the object.



491
492
493
# File 'lib/element.rb', line 491

def add_subelements(all_children)
  all_children.each {|c| self.subelements << c}
end

#add_texts(texts) ⇒ Object

add the text elements into the element



470
471
472
473
474
# File 'lib/element.rb', line 470

def add_texts(texts)
  if hasvalue?
    @value = texts.join
  end
end

#already_partial_export_ended?Boolean

Have we already ended the partial export of this element?

Returns:

  • (Boolean)


517
518
519
# File 'lib/element.rb', line 517

def already_partial_export_ended?
  (@already_partial_export_ended ||= false)
end

#already_partial_exported?Boolean

Have we already started the partial export of this element?

Returns:

  • (Boolean)


512
513
514
# File 'lib/element.rb', line 512

def already_partial_exported?
  (@already_partial_exported ||= false)
end

#create_xml(parent) ⇒ Object

Creates the xml for the element inside the parent element. The parent passed should be a Nokogiri XML Node or Document. This call is recursive creating the XML for any subelements.



364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/element.rb', line 364

def create_xml(parent)
  xmlel = parent.add_child Nokogiri::XML::Element.new(self.elname.to_s, parent)
  if self.hasvalue?
    xmlel.add_child self.value
  end
  create_xml_attr(xmlel)
  create_xml_subel(xmlel)
  
  if has_subelements?
    create_xml_subelements(xmlel)
  end
  
  xmlel
end

#delete_element(element) ⇒ Object

Remove the given subelement from the element



311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/element.rb', line 311

def delete_element(element)
  self.class.each_subel do |a|  
    value = self.send(a)
    if self.class.subel_mult? a
      value.delete_element(element)
    else
      self.send(a.to_s+'=', nil) if value == element
    end
  end
  
  if has_subelements?
    @subelements.delete_element(element)
  end 
end

#end_partial_export(file) ⇒ Object

Ends the partial exporting of the element.



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# File 'lib/element.rb', line 553

def end_partial_export(file)
  if not already_partial_export_ended?
    @already_partial_export_ended = true
    
    if not already_partial_exported?
      raise "<#{self} Trying to end the export of an element that hasn't"+
            " been started yet"
    end
    
    each_subelement do |e|
      e.end_partial_export(file)
    end
    
    file << create_close_tag

    if self.__parent
      self.__parent.delete_element(self)
    end
  end
end

#has_subelements?Boolean

Method that checks if a given class has subelements. This is usually only used when exporting stuff.

Returns:

  • (Boolean)


353
# File 'lib/element.rb', line 353

def has_subelements?; false; end

#hasvalue?Boolean

tests if the element is a value element as defined by ‘elwithvalue’

Returns:

  • (Boolean)


356
357
358
# File 'lib/element.rb', line 356

def hasvalue?
  false
end

#partial_export(file) ⇒ Object

Export this element into a file. Will also start to export the parents of the element. It’s equivalent to calling start_partial_export followed by end_partial_export.



524
525
526
527
528
529
# File 'lib/element.rb', line 524

def partial_export(file)
  if not already_partial_exported?
    start_partial_export(file)
    end_partial_export(file)
  end
end

#start_partial_export(file) ⇒ Object

Starts to export the element to a file. all the existing elements will be exported. After calling this you should only add stuff that you will export explicitly by calling partial_export or start_partial_export.



534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/element.rb', line 534

def start_partial_export(file)
  if not already_partial_exported?
    @already_partial_exported = true
    if self.__parent
      self.__parent.start_partial_export(file)
    end
    
    file << create_open_tag
    if self.hasvalue?
      file << XMLCodec::XMLUtils::escape_xml(self.value)
    end
    
    each_subelement do |e|
      e.partial_export(file)
    end
  end
end

#xml_textObject

create the XML text of the element



497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/element.rb', line 497

def xml_text
  str = create_open_tag
  if self.hasvalue?
    str << XMLCodec::XMLUtils::escape_xml(self.value)
  end
  
  each_subelement do |e|
    str << e.xml_text
  end
  
  str << create_close_tag
  str
end