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.



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

def self._import_xml_dom(xmlel)
  if xmlel.is_a? Nokogiri::XML::Document
    xmlel = xmlel.root
  end

  elclass = get_element_class(xmlel.name)
  if not elclass
    if class_variable_get(:@@strict_parsing)
		  raise ElementClassNotFound, "No class defined for element type: '#{e.name}'"  
    else
      return nil
    end
  end

  if elclass.allvalue?
    elements = [xmlel.children.map{|c| c.to_xml(:save_with=>0)}.join]
  else
    elements = []
    xmlel.children.each do |e|
      if e.text?
        elements << e.text
      else
        element = _import_xml_dom(e)
        elements << element if element
      end
    end
  end
  
  attributes = {}
  xmlel.attributes.each do |name, attr|
    attributes[name] = attr.value
  end
  
  elclass.new_with_content(attributes, elements)
end

._import_xml_text(text) ⇒ Object

Import the XML directly from the text.



449
450
451
452
453
# File 'lib/element.rb', line 449

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

.allvalue?Boolean

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

Returns:

  • (Boolean)


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

def self.allvalue?; false end

.get_element_class(name) ⇒ Object

Gets the class for a certain element name.



352
353
354
355
356
357
358
# File 'lib/element.rb', line 352

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.



361
362
363
# File 'lib/element.rb', line 361

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

.has_subelements?Boolean

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

Returns:

  • (Boolean)


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

def self.has_subelements?; false end

.hasvalue?Boolean

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

Returns:

  • (Boolean)


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

def self.hasvalue?; false end

.import_xml(obj) ⇒ Object

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



398
399
400
401
402
403
404
405
406
407
# File 'lib/element.rb', line 398

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



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/element.rb', line 456

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



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

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



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

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.



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

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

#add_texts(texts) ⇒ Object

add the text elements into the element



492
493
494
495
496
# File 'lib/element.rb', line 492

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

#allvalue?Boolean

Returns:

  • (Boolean)


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

def allvalue?; self.class.allvalue?; end

#already_partial_export_ended?Boolean

Have we already ended the partial export of this element?

Returns:

  • (Boolean)


539
540
541
# File 'lib/element.rb', line 539

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)


534
535
536
# File 'lib/element.rb', line 534

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.



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

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 self.has_subelements?
    create_xml_subelements(xmlel)
  end
  
  xmlel
end

#delete_element(element) ⇒ Object

Remove the given subelement from the element



325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/element.rb', line 325

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.



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/element.rb', line 575

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

Returns:

  • (Boolean)


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

def has_subelements?; self.class.has_subelements? end

#hasvalue?Boolean

Returns:

  • (Boolean)


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

def hasvalue?; self.class.hasvalue? 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.



546
547
548
549
550
551
# File 'lib/element.rb', line 546

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.



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

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



519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/element.rb', line 519

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