#!/usr/local/bin/ruby

require 'xmlparser'
require "xmltree"
require 'xmltreebuilder'

class Module
  # element_accessor creates attribute accesssors but also 
  # maintains a list of attribute names in a class 
  # variable @@element_accessor.
  #
  # Usage just like attr_accessor.
  #
  #   class Image
  #     element_accessor :title, :url, :link, :width, :height
  #   end
  
  def element_accessor(*symbols)
    # initialize the class variable if not initialized already
    self.instance_eval(%Q!
      unless class_variables.include? "@@element_accessor"
        @@element_accessor = []
      end
    !)
    
    # add attribute accessor, and add symbol to the maintained list
    symbols.each do |symbol|
      self.instance_eval %Q[attr_accessor :#{symbol.id2name}]
	self.instance_eval %Q[@@element_accessor << "#{symbol.id2name}"]
    end
  end
  private :element_accessor
end

class Object
  # map_elements is a method which tries to find a xml element
  # for each element attribute defined with element_accessor.
  # Then it calls the write accessor to assign the value of the
  # xml node to the attribute. 
  #
  # Thus it maps xml elements directly into class attributes.
  def map_elements(xmlNode)
    cvar_element_accessor = self.class.instance_eval %Q[@@element_accessor]
    cvar_element_accessor.each do |e|
      value = ""
      elements = xmlNode.getElementsByTagName(e)
      if elements.size > 0
	value = elements[0].firstChild.data
      end
      element_attribute_writer = method(e+"=")
      element_attribute_writer.call(value)
    end
  end
end
	


module RSS
	include XML::SimpleTree

	def do_items(xmltree)
	  xmltree.getElementsByTagName("item").each do | s |
	    i = Item.new
	    i.map_elements(s)
	    yield i
	  end
	end
	private :do_items

	def do_channel(xmltree)
	  xmltree.getElementsByTagName("channel").each do | s |
	    c = Channel.new
	    c.map_elements(s)
	    return c # there can be only one
	  end
	end
	private :do_channel

	def do_textinput(xmltree)
	  xmltree.getElementsByTagName("textinput").each do | s |
	    ti = TextInput.new
	    ti.map_elements(s)
	    return ti 
	  end
	end
	private :do_textinput

	def do_image(xmltree)
	  xmltree.getElementsByTagName("image").each do | s |
	    im = Image.new
	    im.map_elements(s)
	    return im
	  end
	end
	private :do_image

	def do_textinput(xmltree)
	  if(xmltree.getElementsByTagName("textinput") == nil) then return nil end
	  xmltree.getElementsByTagName("textinput").each do | s |
	    ti = TextInput.new
	    ti.title = get_data(s.getElementsByTagName("title"))
	    ti.description = get_data(s.getElementsByTagName("description"))
	    ti.name = get_data(s.getElementsByTagName("name"))
	    ti.link = get_data(s.getElementsByTagName("link"))
	    return ti
	  end
	end
	private :do_textinput
 	
 	def get_data(elems)
	  if elems[0] == nil then return nil end
	  return elems[0].firstChild.data
 	end
 	private :get_data


	class Channel
	  element_accessor :title, :link, :description, :url, :language, :rating, :copyright, :pubdata, :lastbuilddate, :docs, :managingEditor, :webmaster
	  attr_accessor    :image, :textinput

	  def initialize
	    @items = Array.new
	  end
	  
	  def each_item
	    @items.each do | item |
	      yield item
	    end
	  end
	  
	  def add_item(item)
            if(@items.length >= 15) then
               raise "Limit of 15 items per channel"
             end 
	    @items.push item
	  end
	  
	  def to_xml
	    tree = Document.new(ProcessingInstruction.new("xml",
							  "version='1.0' encoding='ISO-8859-1'"),
				DocumentType.new("rss", "PUBLIC '-//Netscape Communications//DTD RSS 0.91//EN' 'http://my.netscape.com/publish/formats/rss-0.91.dtd'"),
				Element.new("rss",[
					      Attr.new('version', "0.91")],
					    Element.new("channel", nil, 
							Element.new("title", nil, @title),
							Element.new("link", nil, @link),
							Element.new("description", nil, @description),
							Element.new("language", nil, @language),
							Element.new("image", nil, Element.new("title", nil, @image.title),
							Element.new("link", nil, @image.link),
							Element.new("url", nil, @image.url)
							)
							)
					    )
				
				)
		rss = tree.getElementsByTagName("rss")[0]
		chan = tree.getElementsByTagName("channel")[0]
	    @items.each do | item |
	      chan.appendChild(Element.new("item", nil, Element.new("title", nil, item.title),
					   Element.new("link", nil, item.link))	
			       )
	    end
	    if(@textinput != nil) then
	      title = Element.new("title", nil, @textinput.title)
	      description = Element.new("description", nil, @textinput.description)
	      link = Element.new("link", nil, @textinput.link)
	      textinput = Element.new("name", nil, @textinput.name)
	      chan.appendChild(Element.new("textinput", nil, 
					  title, description, textinput, link))
	    end
	    tree.to_s
	  end
	end

	class Item
	  element_accessor :title, :link		
	end

	class Image
	  element_accessor :title, :url, :link, :width, :height

	  def initialize
	    @width  = "88"
	    @height = "31"
	  end
	end

	class TextInput
	  element_accessor :title, :description, :name, :link	
 	end

	def from_xml(xml)
		builder = XML::DOM::Builder.new(0)
		builder.setBase("./")
		begin
		  xmltree = builder.parse(xml, true)
		rescue XMLParserError
		  line = builder.line
		  print "#{$0}: #{$!} (in line #{line})\n"
		  exit 1
		end
		
		## unify sequential Text nodes
		xmltree.documentElement.normalize
		xmltree.trim 

		chan = do_channel xmltree
		chan.image = do_image xmltree
		chan.textinput = do_textinput xmltree
		inp = do_textinput xmltree
		if(inp != nil) then chan.textinput = inp end
		do_items(xmltree) do | item |
			chan.add_item item
		end
		return chan
	end

end

