MyXML 1.1.0
Wrapper to REXML for easyofuseness of XML files :-)

AUTHOR
jan molic (MiG)
http://myxml.1984.cz
jan.molic {at sign} 1984 {dot} cz
please report errors and enhancements, thanks

DISTRIBUTED UNDER GPL LICENCE, USE AT ONE'S OWN RISK - THERE'S NO WARRANTY

download: 1.1.0, 1.0.5, 1.0.4, 1.0.3, 1.0.2, 1.0.1, 1.0


IMPORTANT CHANGES

>1.0.5 Element::add and Element::force have "auto_real" now. If you add/force element with attributes set, it defaultly becomes real. But not if auto_real=false.
>1.0.4 Added methods Element::has_text?, Element::has_elements? and Element::empty? which combinate both.
>1.0.3 Added method Element::empty! which delete all child elements and text of the element.
>1.0.2 Added method Element::exists? which can be used to test if XML element exists already.
>1.0.1 Document can be saved to another file (Document::save method).
>1.0 You can add/force multiple path (element named "accounts/account" will be created before)
   root.add("accounts/account")                      # create two elements; accounts and it's child account
root.add("accounts/account", {"name"=>"linux"} ) # the same but set attribute "name" of account
root.force("accounts/account") # create only if not already exists
>1.0-r4 Added method "hashlist" and example 10 for describing it.
>1.0-r3 If add method is specified with attributes, element will be added "real".
root.add("db") # not_real
root.add("db", {"name"=>"aaa"} ) # real

DESCRIPTION


EXAMPLES

(syntax highlighted using code2html)

This is a test.xml:
<root>
	<db name='firstdb'>
		<user name='carl'>
			<password>foo</password>
		</user>
		<user name='mary'>
			<password>bar</password>
			<description>
				<name>Mary</name>
				<age>23</age>
				<private>
					<niceness>4</niceness>
				</private>
			</description>
		</user>
	</db>
	<defaults>
		<height>77</height>
		<width>99</width>
		<weight>1111</weight>
	</defaults>
</root>



example 1
# Load, save and print test.xml 

require 'myxml'
xml = MyXML::Document.new "test.xml"
xml.done
xml.save
xml.write


example 2
# Find "db" element named "firstdb" and print all users

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
firstdb.list('user').each { |e|
    puts e.attributes['name']
}  
# or without argument (default is '*')
firstdb.list.each { |e|
    puts e.attributes['name']
}
xml.done


example 3
# Find "db" element named "firstdb",
# find user "mary" and print all her description
# (texts of first-child elements)

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
mary = firstdb.find('user', "name"=>"mary")
mary.list("description/*").each { |e|
    puts "#{e.name}: #{e}"
    # or puts "#{e.name}: #{e.text}" but String behavior is easier
}  
xml.done


example 4
# Set description/name of Carl

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
carl = firstdb.find('user', {"name"=>"carl"})
carl["description/name"] = "Carl Cox"
xml.done
xml.write


example 5
# Add user Monica and set her password

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
monica = firstdb.add('user', {"name"=>"monica"})
# until here monica not really exists !!!
monica["password"] = "123456"
xml.done
xml.write


example 6
# Add user carl if not already and
# set description/private/niceness and attribute "tip"

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
carl = firstdb.force('user', {"name"=>"carl"})
# carl may not really exits until here
carl["description/private/niceness"] = "4"
carl.attributes["tip"] = "must see!"
xml.done   
xml.write  


example 7
# The same but we can use monica's password like String

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
monica = firstdb.add('user', {"name"=>"monica"})
password = monica["password"]
# monica not exists really, so she has no password
puts "Monica's old password: " + password
password.text = "abcdef"
# !!! 'password = "abcdef"' is wrong because "abcdef" is another object (String)
# now monica is created
puts "Monica's new password: " + password 
xml.done


example 8
# We can debug which elements are not really existent
# - just "print password" and see that not really exists

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
monica = firstdb.add('user', {"name"=>"monica"})
puts monica["password"]
puts "BEFORE DONE: "
xml.write  # here we can see all nonexistent elements (have attribute NOT-EXIST='true')
xml.done   # elements with attribute NOT-EXIST are deleted
puts "AFTER DONE: "
xml.write  # now xml is without nonexistent elements


example 9
# Delete user Carl

require 'myxml'
xml = MyXML::Document.new "test.xml"
firstdb = xml.root.find('db', {"name"=>"firstdb"})
carl = firstdb.find('user', {"name"=>"carl"})
carl["description/name"] = "Carl Cox"
carl["description/age"] = "33"
carl.delete
# !!! carl may be reused after deletion, but all children will be erased !!!
carl["password"] = "aaa"
xml.done  
xml.write 


example 10
# Print defaults - using hashlist
# ("hashlist" return hash {name=>element,name=>element,...}
# "list" return only [element,element,...])

require 'myxml'
xml = MyXML::Document.new "test.xml"
xml.root.hashlist('defaults/*').each {|name,e|
    puts "default #{name} = #{e}"
}
xml.done  

TIPS

You can use both - it depends on you
MyXML::Element.elements.find
MyXML::Element.find

You can use REXML::Element directly
MyXML::Element.rexml_element
for example
puts root["mailbox"].rexml_element.name

HOW DOES IT ALL WORK?

It uses the same rexml tree. If you add an element (via add/force/...), this element is defaultly "not real". It means that has an attribute NOT-REAL. When you set it's text or attribute, this NOT-REAL attribute is deleted up to the root element, so it "becomes real".
After done method, all elements with NOT-REAL attribute are deleted.
If you delete an element with delete method, rexml elements are not deleted, just NOT-REAL attribute is set and texts & attributes of all children erased. Why? You are then able to reuse the element.


IMPORTANT CLASSES AND METHODS

module MyXML

   class Attributes
      
      ##
      # just the same as rexml_element.attributes[]
      #
      def []

      ##
      # if you set attribute, rexml_element becomes real
      #
      def []=
      
   end


   class Elements

      ##
      # find element
      # 
      def find path,attributes=nil,real_only=true
     
      ##
      # create parent elements if not exists (force)
      # if both auto_real and attributes set, element becomes real
      # add element with specified attributes
      # 
      def add path,attributes=nil,auto_real=true

      ##
      # like add, but element is added only if not already 
      # if both auto_real and attributes set, element becomes real
      # already = must have specified attributes
      #
      def force path,attributes=nil,auto_real=true
     
      ##
      # return array of child elements [element,element,...]
      #
      def list path='*',real_only=true

      ##
      # return hash of child elements {element.name => element,element.name => element,...}
      #
      def hashlist path='*',real_only=true


   end


   class Element < String

      attr_accessor :rexml_element

      ##
      # see Attributes wrapper above
      # 
      def attributes

      ##
      # return name of element
      # 
      def name

      ##
      # return text of element
      # 
      def text
      
      ##
      # sets self value (String behavior)
      # rexml_element becomes real
      # value is converted to String
      # 
      def text= value
   
      ##
      # synonym for Elements.force_all
      #
      def [] path
      
      ##
      # create all parential elements and
      # then call text= method
      # 
      def []= path,value
        
      ##
      # rexml_element becomes not_real (is not destroyed,
      # because must be reusable - be able to become real again)
      def delete

      ##
      # has element a text?
      #
      def has_text?

      ##
      # has element child element(s)?
      #
      def has_elements?

      ##
      # is element empty? (neither text nor child elemets exist)
      #
      def empty?

      ##
      # empty this element (delete all child elements and text)
      #
      def empty!

      ##
      # is this MyXML Element real? (really exists in XML?)
      #
      def exists?

      ##
      # delete from rexml tree all not_real elements 
      # (have NOT-REAL='true' attribute)
      # 
      def done
      
      ##
      # AND ALIASES FOR METHODS IN Elements CLASS JUST FOR EASYOFUSENESS
      #
           
   end
     
   
   class Document 

      ##
      # create new document from file
      #
      def initialize path

      ##
      # return document root
      #
      def root

      ##
      # write actual dom tree to dest
      # before done "actual" means you will see all nonexistent elements!
      #
      def write dest=$stdout

      ##
      # do "root.done" - delete all not_real elements
      #
      def done
      
      ##
      # save actual dom tree to filename (> filename.tmp, synced, renamed to filename)
      # or other file
      #
      def save other_path=nil
      
   end

end