#!/usr/bin/ruby
## Time-stamp: <2004-04-15 02:21:59 vk>

require "parsearg.rb" ## to parse command line arguments
require 'nqxml/streamingparser'

## -------------------------------------------------------------------
## 2do:  (volunteers please apply ;-)
##   replace FIXXMEs in code with working code
##   or enhance functionality
## additional:
##   * z2pal.rb --append
##   * ? save last invocation time and append only new/modified
##     entries to the outfile (?)
## -------------------------------------------------------------------

def usage
  puts <<-EOD

 PURPOSE:  converts PIM-informations from Zaurus/OPIE to palcal

 AUTHOR:   Karl Voit (shellscript@Karl-Voit.at)
          
 LICENSE:  GPL
          
 VERSION:  0.1 2004-04-15  initial version

 SYNOPSIS: z2pal.rb [options]

           (OR if /usr/bin/ruby is not found:
           ruby z2pal.rb [options])

 OPTIONS:
           --categories </path/to/Category.xml>
           --datebook </path/to/datebook.xml>
           --todolist </path/to/todolist.xml>
           --addressbook </path/to/addressbook.xml>
                set path to files

           --output /path/to/output.pal       (default: z2pal-output.pal)
                output file

           --from <days>                        (default: from beginning)
                export only things that matter <days> days ago

           --to <days>                             (default: to eternity)
                export only things that matter until <days> days from now

           --high <priority>                                 (default: 1)
                export only todo-tasks up to this priority

           --low <priority>                                  (default: 5)
                export only todo-tasks down to this priority

           --notodolist
           --noaddressbook
           --nodatebook
                ignore some files

           --append (FIXXME: not implemented yet!)
                do not overwrite output-file, append to it

           --categorywidth <width>
                set (minimal) category width of output

           --help
           --version
                show this help screen

           Options can be saved in ~/.z2pal.xml
           Example:
             <?xml version="1.0"?>
             <z2palconfiguration>
               <datebook ignore="true" file="afile1.xml" />
               <todolist high="1" low="3" ignore="false" 
                         file="afile2.xml" />
               <addressbook ignore="true" file="afile3.xml" />
               <categories ignore="false" file="afile4.xml" />
               <output categorywidth="8" append="true" 
                       from="30" to="10" file="afile5.pal" />
             </z2palconfiguration>

EOD
end
$USAGE = 'usage'
## -------------------------------------------------------------------



## -------------------------------------------------------------------
## purpose: defines entries of the addressbookArray
## FIXXME: this is INCOMPLETE. Here, it's only birthday information!
## -------------------------------------------------------------------
class AddressbookArrayEntry

  attr_accessor :fileas
  attr_accessor :birthday
  attr_accessor :lastname
  attr_accessor :firstname
  attr_accessor :categories

  def initialize(fileas, birthday, lastname, firstname, categories)
    @fileas=fileas
    @birthday=birthday
    @lastname=lastname
    @firstname=firstname
    @categories=categories
  end

end



## -------------------------------------------------------------------
## purpose: parses addressbook.xml
## -------------------------------------------------------------------
class ParseAddressbook


  def initialize()
    @linenumber=0
    @numofentries=0
  end


  def incNumofentries()
    @numofentries+=1
    printf "  \b\b\b\b\b\b%4d",@numofentries
  end


  def parseBirthdayOnly(inputfilename, addressbookArray)

    printf "\nbegin parsing of \"#{inputfilename}\"...\nNumber of Entries found:  %4d",0

    aFile = File.new(inputfilename)

    begin
      parser = NQXML::StreamingParser.new(aFile)
      parser.each { | entity |

        if entity.instance_of?(NQXML::Tag) && entity.name == 'Contact' && entity.isTagEnd == false

          incNumofentries()

          raise "Parse error: found \"Contact\" without any option in it." if entity.attrs.keys == nil

          addressbookArray.push( AddressbookArrayEntry.new(\
                                                     entity.attrs['FileAs'],\
                                                     entity.attrs['Birthday'],\
                                                     entity.attrs['FirstName'],\
                                                     entity.attrs['LastName'],\
                                                     entity.attrs['Categories']))
        end#if

      }
    rescue NQXML::ParserError => ex
      puts "Error in line \"#{ex.line}\" at character #{ex.column}: #{$!}"
    end

    printf "\n"

  end# parse

end# class ParseAddressbook






## -------------------------------------------------------------------
## purpose: defines entries of the todolistArray
## -------------------------------------------------------------------
class TodolistArrayEntry

  attr_accessor :summary
  attr_accessor :categories
  attr_accessor :completed
  attr_accessor :dateday
  attr_accessor :datemonth
  attr_accessor :dateyear
  attr_accessor :description
  attr_accessor :hasdate
  attr_accessor :priority
  attr_accessor :progress
  attr_accessor :uid
  attr_accessor :rid
  attr_accessor :rinfo

  def initialize(summary, categories,completed,dateday,datemonth,dateyear,description,hasdate,priority,progress,uid,rid,rinfo)
    @summary=summary
    @categories=categories
    @completed=completed
    @dateday=dateday
    @datemonth=datemonth
    @dateyear=dateyear
    @description=description
    @hasdate=hasdate
    @priority=priority
    @progress=progress
    @uid=uid
    @rid=rid
    @rinfo=rinfo
  end

end





## -------------------------------------------------------------------
## purpose: parses todolist.xml
## -------------------------------------------------------------------
class ParseTodolist


  def initialize()
    @linenumber=0
    @numofentries=0
  end


  def incNumofentries()
    @numofentries+=1
    printf "  \b\b\b\b\b\b%4d",@numofentries
  end


  def parse(inputfilename, todolistArray)

    printf "\nbegin parsing of \"#{inputfilename}\"...\nNumber of Entries found:  %4d",0

    aFile = File.new(inputfilename)

    begin
      parser = NQXML::StreamingParser.new(aFile)
      parser.each { | entity |

        if entity.instance_of?(NQXML::Tag) && entity.name == 'Task' && entity.isTagEnd == false

          incNumofentries()

          raise "Parse error: found \"Task\" without any option in it." if entity.attrs.keys == nil

          todolistArray.push( TodolistArrayEntry.new(\
                                                     entity.attrs['Summary'],\
                                                     entity.attrs['Categories'],\
                                                     entity.attrs['Completed'],\
                                                     entity.attrs['DateDay'],\
                                                     entity.attrs['DateMonth'],\
                                                     entity.attrs['DateYear'],\
                                                     entity.attrs['Description'],\
                                                     entity.attrs['HasDate'],\
                                                     entity.attrs['Priority'],\
                                                     entity.attrs['Progress'],\
                                                     entity.attrs['Uid'],\
                                                     entity.attrs['rid'],\
                                                     entity.attrs['rinfo']))
        end#if

      }
    rescue NQXML::ParserError => ex
      puts "Error in line \"#{ex.line}\" at character #{ex.column}: #{$!}"
    end

    printf "\n"

  end# parse

end# class ParseTodolist






## -------------------------------------------------------------------
## purpose: parses Categories.xml
## -------------------------------------------------------------------
class ParseCategories



  def initialize()
    @linenumber=0
    @numofentries=0
  end


  def incNumofentries()
    @numofentries+=1
    printf "  \b\b\b\b\b\b%4d",@numofentries
  end


  def parse(inputfilename, categoryHash)

    printf "\nbegin parsing of \"#{inputfilename}\"...\nNumber of Entries found:  %4d",0

    aFile = File.new(inputfilename)

    begin
      parser = NQXML::StreamingParser.new(aFile)
      parser.each { | entity |

        if entity.instance_of?(NQXML::Tag) && entity.name == 'Category' && entity.isTagEnd == false

          incNumofentries()

          raise "Parse error: found \"Category\" without any option in it." if entity.attrs.keys == nil

          categoryHash.update({ entity.attrs['id'] => entity.attrs['name']})

        end#if

      }
    rescue NQXML::ParserError => ex
      puts "Error in line \"#{ex.line}\" at character #{ex.column}: #{$!}"
    end

    printf "\n"

  end# parse

end# class ParseCategories






## -------------------------------------------------------------------
## purpose: defines entries of the todolistArray
## -------------------------------------------------------------------
class DatebookArrayEntry

  attr_accessor :alarm
  attr_accessor :categories
  attr_accessor :created
  attr_accessor :description
  attr_accessor :end
  attr_accessor :enddt
  attr_accessor :location
  attr_accessor :note
  attr_accessor :rfreq
  attr_accessor :rhasenddate
  attr_accessor :rid
  attr_accessor :rinfo
  attr_accessor :rtype
  attr_accessor :rposition
  attr_accessor :rweekdays
  attr_accessor :sound
  attr_accessor :start
  attr_accessor :type
  attr_accessor :uid

  def initialize(alarm,categories,created,description,theend,enddt,location,note,rfreq,rhasenddate,rid,rinfo,rtype,rposition,rweekdays,sound,start,type,uid)
    @alarm=alarm
    @categories=categories
    @created=created
    @description=description
    @end=theend
    @enddt=enddt
    @location=location
    @note=note
    @rfreq=rfreq
    @rhasenddate=rhasenddate
    @rid=rid
    @rinfo=rinfo
    @rtype=rtype
    @rposition=rposition
    @rweekdays=rweekdays
    @sound=sound
    @start=start
    @type=type
    @uid=uid
  end

end## DatebookArrayEntry





## -------------------------------------------------------------------
## purpose: parses datebook.xml
## -------------------------------------------------------------------
class ParseDatebook


  def initialize()
    @linenumber=0
    @numofentries=0
  end


  def incNumofentries()
    @numofentries+=1
    printf "  \b\b\b\b\b\b%4d",@numofentries
  end


  def parse(inputfilename, datebookArray)

    printf "\nbegin parsing of \"#{inputfilename}\"...\nNumber of Entries found:  %4d",0

    aFile = File.new(inputfilename)

    begin
      parser = NQXML::StreamingParser.new(aFile)
      parser.each { | entity |

        if entity.instance_of?(NQXML::Tag) && entity.name == 'event' && entity.isTagEnd == false

          incNumofentries()

          raise "Parse error: found \"event\" without any option in it." if entity.attrs.keys == nil

          datebookArray.push( DatebookArrayEntry.new(\
                                                     entity.attrs['alarm'],\
                                                     entity.attrs['categories'],\
                                                     entity.attrs['created'],\
                                                     entity.attrs['description'],\
                                                     entity.attrs['end'],\
                                                     entity.attrs['enddt'],\
                                                     entity.attrs['location'],\
                                                     entity.attrs['note'],\
                                                     entity.attrs['rfreq'],\
                                                     entity.attrs['rhasenddate'],\
                                                     entity.attrs['rid'],\
                                                     entity.attrs['rinfo'],\
                                                     entity.attrs['rtype'],\
                                                     entity.attrs['rposition'],\
                                                     entity.attrs['rweekdays'],\
                                                     entity.attrs['sound'],\
                                                     entity.attrs['start'],\
                                                     entity.attrs['type'],\
                                                     entity.attrs['uid']) )
        end#if

      }
    rescue NQXML::ParserError => ex
      puts "Error in line \"#{ex.line}\" at character #{ex.column}: #{$!}"
    end

    printf "\n"

  end# parse

end# class ParseDatebook





## -------------------------------------------------------------------
## purpose: writes out the addressbook
## -------------------------------------------------------------------
class FormatAddressbook

  def addLeadingZeroIfLowerThanTen(string)
    string.length<2 ? "0"+string : string
  end


  def generatePalcalListBirthdayOnly(addressbookArray, categoryHash)

    outputArray = Array.new()

    ## take only birthday entries
    selectedAddressbook = addressbookArray.select {|item| !item.birthday.nil? && !item.birthday.empty? }

    selectedAddressbook.each { |entry|

      birthday=entry.birthday.split('.',3)
      ## FIXXME: do an intelligent guess, what is the day, month or year of the string "entry.birthday"
      ## ASSUMPTION: birthday[0] == day
      ## ASSUMPTION: birthday[1] == month
      ## ASSUMPTION: birthday[2] == year
      next if !birthday[0] || !birthday[0].to_i.between?(1,31)
      next if !birthday[1] || !birthday[1].to_i.between?(1,12)

      day=addLeadingZeroIfLowerThanTen(birthday[0])
      month=addLeadingZeroIfLowerThanTen(birthday[1])

      line="0000#{month}#{day} (GEB"
      line+=" #{birthday[2]}" if !birthday[2].nil? && !birthday[2].empty?
      line+=")"
      line+=" [#{categoryHash[entry.categories].ljust($OPTION_categorywidth.to_i)}]" if not ( entry.categories.nil? || entry.categories.empty? || categoryHash[entry.categories].nil? )
      line+=" #{entry.fileas}" if !entry.fileas.empty?
      line+=" #{entry.firstname} #{entry.lastname})" if ( entry.fileas.empty? && ( !entry.firstname.empty? && !entry.lastname.empty? ) )
      outputArray.push( line )
    }

    puts "\nWrote #{selectedAddressbook.length.to_s} of #{addressbookArray.length.to_s} contacts (only contacts with birthdays)"

    outputArray.sort!

    return outputArray.join("\n")

  end

end## FormatAddressbook






## -------------------------------------------------------------------
## purpose: writes out the todolist
## -------------------------------------------------------------------
class FormatTodolist

  def generatePalcalList(todolistArray, categoryHash, lowestpriority, highestpriority)

    outputArray = Array.new()

    ## take only uncompleted tasks of right priorities (caution! 'highest possible priority'==1)
    selectedTodolist = todolistArray.select {|item| item.completed=="0" && item.priority.to_i.between?(highestpriority,lowestpriority) }

    selectedTodolist.each { |entry|
      line="TODO [#{entry.priority}]"
      line+="[#{categoryHash[entry.categories].ljust($OPTION_categorywidth.to_i)}]" if not ( entry.categories.nil? || entry.categories.empty? || categoryHash[entry.categories].nil? )
      line+="[#{entry.progress}%]" if not  ( entry.progress.nil? || entry.progress.empty? )
      line+=" #{entry.summary.strip}"
      line+=" (#{entry.description.strip})" if not ( entry.description.nil? || entry.description.empty? )
      line+=" (faellig: #{entry.dateday}.#{entry.datemonth}.#{entry.dateyear}.)" if entry.hasdate=='1'
      outputArray.push( line )
    }

    puts "\nWrote #{selectedTodolist.length.to_s} of #{todolistArray.length.to_s} tasks (only uncompleted tasks with priorities between #{highestpriority} and #{lowestpriority})"

    outputArray.sort!

    return outputArray.join("\n")

  end

end## FormatTodolist











## -------------------------------------------------------------------
## purpose: writes out the datebook informations
## -------------------------------------------------------------------
class FormatDatebook

  private

  ## getSeconds(5,'d') returns the seconds of five days
  ## known time ranges: s (second),m (minute),h (hour),d (day),w (week),y (year)
  def getSeconds(quantity, quality)

    minuteSeconds    = 60
    hourSeconds      = ( minuteSeconds * 60 )
    daySeconds       = ( hourSeconds * 24 )
    weekSeconds      = ( daySeconds * 7 )
    yearSeconds      = ( daySeconds * 365 )
    
    case quality
    when 's'
      value=1
    when 'm'
      value=minuteSeconds
    when 'h'
      value=hourSeconds
    when 'd'
      value=daySeconds
    when 'w'
      value=weekSeconds
    when 'y'
      value=yearSeconds
    else
      raise "getSeconds(#{quantity},#{quality}): Error, unknown time range: [#{quality}]"
    end

    return quantity * value
  end



  public

  def initialize(lowestDayoffsetfromnow, highestDayoffsetfromnow)
    @lowestDayoffsetfromnow=lowestDayoffsetfromnow
    @highestDayoffsetfromnow=highestDayoffsetfromnow

    if lowestDayoffsetfromnow.nil?
      @low=Time.at(0)
    else
      @low=Time.at(Time.now.to_i - getSeconds(lowestDayoffsetfromnow,'d'))
    end
    if highestDayoffsetfromnow.nil?
      ## CAUTION: this is a hardcoded upper limit of 5 yrs; may be bad coding style
      ##          Please supply a better solution!
      @high=Time.at(getSeconds(5,'y') + Time.now.to_i) # five years in future == inf
    else
      @high=Time.at(Time.now.to_i + getSeconds(highestDayoffsetfromnow,'d'))
    end
  end


  def generatePalcalList(datebookArray, categoryHash)

    outputArray = Array.new()

    numberofwrittenevents=0

    weekdays = Hash.new("UNKNOWNDAY")
    weekdays = { "1"=>"MON", "2"=>"TUE", "4"=>"WED", "8"=>"THU", "16"=>"FRI", "32"=>"SAT", "64"=>"SUN" }

    datebookArray.each { |entry|

      ## convert timestamps of entry to a Ruby-Time-object
      entryStartTime=Time.at(entry.start.to_i)
      entryEndTime=Time.at(entry.end.to_i)
      entryRecurringEndTime=Time.at(entry.enddt.to_i) if not entry.enddt.nil?

      ## skip, if no recurring task and starts too late or end is too old
      next if entry.rfreq.nil? && ( entryStartTime > @high || entryEndTime < @low )

      ## skip, if recurring task but given end date is too old
      next if (not entry.rfreq.nil?) && (not entry.enddt.nil?) && entryRecurringEndTime < @low

      ## FIXXME: 2do: skip, if recurring task, which has no occurance in given time period

      numberofwrittenevents+=1

      ## build up the string that defines the timestamp format in palcal-format
      if entry.rfreq.nil?

        ## no recurring task
        line=entryStartTime.strftime("%Y%m%d")

      elsif entry.rfreq!="1"

        ## can't handle rfrequ!=1 in pal-format!
        ## FIXXME: 2do: create a one-time-event for each occurence in given time period?
        line=entryStartTime.strftime("%Y%m%d [+#{entry.rfreq}*#{entry.rtype}]")

      else

        ## recurring task
        case entry.rtype
        when "Daily"
          raise "generatePalcalList() ERROR: found daily event but no end-date" if ( entry.enddt.nil? || entry.enddt.empty? )
          line=entryStartTime.strftime("DAILY")
        when "Weekly"
          raise "generatePalcalList() ERROR: found weekly event but no rweekdays" if ( entry.rweekdays.nil? || entry.rweekdays.empty? )
          line=weekdays[entry.rweekdays]
        when "Monthly"
          line=entryStartTime.strftime("000000%d")
        when "MonthlyDay"
          raise "generatePalcalList() ERROR: found MonthlyDay event but no RPosition" if ( entry.rposition.nil? || entry.rposition.empty? )
          ## every 2nd tuesday in month
          ## Ruby: sun=0, sat=6
          ## palcal: sun=1, sat=7  => add 1
          line="*00#{entry.rposition}" + (entryStartTime.strftime("%w").to_i+1).to_s
        when "MonthlyDate"
          ## e.g. every 2nd week in month (-> RPosition==2)
          raise "generatePalcalList() ERROR: found MonthlyDate but it is not yet supported" ## FIXXME: 2do
        when "Yearly"
          line=entryStartTime.strftime("0000%m%d")
        else
          raise "generatePalcalList() ERROR: found unknown recurring type: [#{entry.rtype}]"
        end

        ## recurring task with end date
        if not entry.enddt.nil?
          line+=entryStartTime.strftime(":%Y%m%d")+entryRecurringEndTime.strftime(":%Y%m%d")
        end

      end

      def addLeadingZeroIfLowerThan(number,boundary)
        number<boundary ? "0"+number.to_s : number.to_s
      end

      line+=" #{addLeadingZeroIfLowerThan(entryStartTime.hour,10)}:#{addLeadingZeroIfLowerThan(entryStartTime.min,10)}"
      line+="-#{addLeadingZeroIfLowerThan(entryEndTime.hour,10)}:#{addLeadingZeroIfLowerThan(entryEndTime.min,10)}"

      if not ( entry.categories.nil? || entry.categories.empty? || categoryHash[entry.categories].nil? )
        line+=" [#{categoryHash[entry.categories].ljust($OPTION_categorywidth.to_i)}]" 
      else
        line+=" [".ljust($OPTION_categorywidth.to_i+2) + "]"
      end

      line+=" #{entry.description.strip}" if not ( entry.description.nil? || entry.description.empty? )

      line+=" (-> #{entry.location.strip})" if not ( entry.location.nil? || entry.location.empty? || entry.location.downcase=="(unbekannt)" || entry.location.downcase=="(unknown)")

      if entry.rhasenddate=="1"
        line+=" (end: #{entryRecurringEndTime.day}.#{entryRecurringEndTime.month}.#{entryRecurringEndTime.year}.)"
      end

      line += " (ALARM: #{entry.alarm}s)" if not entry.alarm.nil?

      outputArray.push( line )
    }
    
    message="\nWrote #{numberofwrittenevents.to_s} of #{datebookArray.length.to_s} events"
    message+=" (" if @lowestDayoffsetfromnow || @highestDayoffsetfromnow
    message+=" from #{@lowestDayoffsetfromnow} days in the past" if @lowestDayoffsetfromnow
    message+=" and" if @lowestDayoffsetfromnow && @highestDayoffsetfromnow
    message+=" until #{@highestDayoffsetfromnow} days in the future" if @highestDayoffsetfromnow
    message+=" )" if @lowestDayoffsetfromnow || @highestDayoffsetfromnow
    puts message

    return outputArray.sort.join("\n")

  end

end## FormatDatebook






## -------------------------------------------------------------------
## purpose: handels the writing of the output file, debug output, ...
## -------------------------------------------------------------------
class Outputwriter

  def initialize(filename)
    @outputfile = File.open(filename,"w")
  end

  ## prints out using the correct indentation
  def println(string)
    return if string == nil
    raise "Outputwriter.println(#{string.to_s}) ERROR: parameter is not a string" if string.class != String
    @outputfile.print(string.strip+"\n")
  end

  ## prints out to the user interface (stdout)
  def screen(string)
    raise "Outputwriter.println(#{string.to_s}) ERROR: parameter is not a string" if string.class != String
    puts string
  end

  ## prints out debug infos
  def debug(string)
    raise "Outputwriter.println(#{string.to_s}) ERROR: parameter is not a string" if string.class != String
    puts string
  end

end#class Outputwriter



def parseConfigurationFile(filename)

  aFile = File.new(filename)

  begin

    validconf=false
    parser = NQXML::StreamingParser.new(aFile)
    parser.each { | entity |

      if entity.instance_of?(NQXML::Tag) && entity.isTagEnd == false && !entity.attrs.keys.nil?

        case entity.name
        when "z2palconfiguration"
          validconf=true
        when "datebook"
          raise "Missing z2palconfiguration-tag in configuration file \"#{filename}\"" if !validconf
          ##  <datebook ignore="false" file="/home/vk/zaurus/mnt/card/root-symlinked/Applications/datebook/datebook.xml" />
          $OPTXML_datebook=entity.attrs['file'] if entity.attrs['file']
          $OPTXML_nodatebook=true if entity.attrs['ignore']=="true"
        when "todolist"
          raise "Missing z2palconfiguration-tag in configuration file \"#{filename}\"" if !validconf
          ##  <todolist ignore="false" file="/home/vk/zaurus/mnt/card/root-symlinked/Applications/todolist/todolist.xml" />
          $OPTXML_todolist=entity.attrs['file'] if entity.attrs['file']
          $OPTXML_notodolist=true if entity.attrs['ignore']=="true"
          $OPTXML_high=entity.attrs['high'] if entity.attrs['high']
          $OPTXML_low=entity.attrs['low'] if entity.attrs['low']
        when "addressbook"
          raise "Missing z2palconfiguration-tag in configuration file \"#{filename}\"" if !validconf
          ##  <addressbook ignore="true" file="" />
          $OPTXML_addressbook=entity.attrs['file'] if entity.attrs['file']
          $OPTXML_noaddressbook=true if entity.attrs['ignore']=="true"
        when "categories"
          raise "Missing z2palconfiguration-tag in configuration file \"#{filename}\"" if !validconf
          ##  <categories ignore="false" file="/home/vk/zaurus/mnt/card/root-symlinked/Settings/Categories.xml" />
          $OPTXML_categories=entity.attrs['file'] if entity.attrs['file']
          $OPTXML_nocategories=true if entity.attrs['ignore']=="true"
        when "output"
          raise "Missing z2palconfiguration-tag in configuration file \"#{filename}\"" if !validconf
          ##  <output categorywidth="8" append="false" from="30" to="10" file="output.pal" />
          $OPTXML_categorywidth=entity.attrs['categorywidth'] if entity.attrs['categorywidth']
          $OPTXML_append=true if entity.attrs['append']=="true"
          $OPTXML_from=entity.attrs['from'] if entity.attrs['from']
          $OPTXML_to=entity.attrs['to'] if entity.attrs['to']
          $OPTXML_output=entity.attrs['file'] if entity.attrs['file']
        else
          raise "Sorry, found unknown tag \"#{entity.name}\" in configuration file \"#{filename}\""
        end

      end#if

    }
  rescue NQXML::ParserError => ex
    puts "Error in configuration file \"#{filename}\" in line \"#{ex.line}\" at character #{ex.column}: #{$!}"
  end

end



## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------
## ------------------------------------------------------------------------------------------------------

DEFAULTCONFIGURATIONFILE=ENV['HOME']+"/.z2pal.xml"

parseConfigurationFile(DEFAULTCONFIGURATIONFILE) if File.exists?(DEFAULTCONFIGURATIONFILE)

parseArgs(0, nil, nil,\
          "high:", "low:",\
          "from:", "to:",\
          "datebook:", "todolist:", "addressbook:", "categories:",\
          "output:",\
          "notodolist", "noaddressbook", "nodatebook",\
          "append",\
          "categorywidth:",\
          "version", "help")


if ($OPT_version||$OPT_help)

  ## show only help
  eval($USAGE)
  exit

else

  ## merge options from commandline and configuration file
  def chooseOption(commandlineoption, xmloption)
    (commandlineoption.nil?)? xmloption : commandlineoption 
  end

  $OPTION_high=chooseOption($OPT_high, $OPTXML_high)
  $OPTION_low=chooseOption($OPT_low, $OPTXML_low)
  $OPTION_from=chooseOption($OPT_from, $OPTXML_from)
  $OPTION_to=chooseOption($OPT_to, $OPTXML_to)
  $OPTION_datebook=chooseOption($OPT_datebook, $OPTXML_datebook)
  $OPTION_todolist=chooseOption($OPT_todolist, $OPTXML_todolist)
  $OPTION_addressbook=chooseOption($OPT_addressbook, $OPTXML_addressbook)
  $OPTION_categories=chooseOption($OPT_categories, $OPTXML_categories)
  $OPTION_output=chooseOption($OPT_output, $OPTXML_output)
  $OPTION_notodolist=chooseOption($OPT_notodolist, $OPTXML_notodolist)
  $OPTION_noaddressbook=chooseOption($OPT_noaddressbook, $OPTXML_noaddressbook)
  $OPTION_nodatebook=chooseOption($OPT_nodatebook, $OPTXML_nodatebook)
  $OPTION_append=chooseOption($OPT_append, $OPTXML_append)
  $OPTION_categorywidth=chooseOption($OPT_categorywidth, $OPTXML_categorywidth)

  ## default parameters
  $OPTION_output="z2pal-output.pal" if not $OPTION_output
  $OPTION_from=nil if not $OPTION_from
  $OPTION_to=nil if not $OPTION_to
  $OPTION_categorywidth=8 if not $OPTION_categorywidth
  $OPTION_low=5 if not $OPTION_low
  $OPTION_high=1 if not $OPTION_high

  ## check numbers
  raise "Sorry, the given from-day (#{$OPTION_from}) is not a (valid) number." if $OPTION_from.to_i<=0 && (not $OPTION_from.nil?)
  raise "Sorry, the given to-day (#{$OPTION_to}) is not a (valid) number." if $OPTION_to.to_i<=0 && (not $OPTION_to.nil?)
  raise "Sorry, the given category width (#{$OPTION_categorywidth}) is not a (valid) number." if $OPTION_categorywidth.to_i<=0
  raise "Sorry, the given high-priority (#{$OPTION_high}) is less important than the given low-priority (#{$OPTION_low})." if $OPTION_high.to_i>$OPTION_low.to_i
  raise "Sorry, the given high-priority (#{$OPTION_high}) is not between 1 and 5." if !$OPTION_high.to_i.between?(1,5)
  raise "Sorry, the given low-priority (#{$OPTION_low}) is not between 1 and 5." if !$OPTION_low.to_i.between?(1,5)

  ## check if files are non existent
  raise "Sorry, the categories file (#{$OPTION_categories}) can not be found." if ( $OPTION_categories.nil? || !File.exist?($OPTION_categories) ) && !$OPTION_nocategories
  raise "Sorry, the todolist file (#{$OPTION_todolist}) can not be found." if ( $OPTION_todolist.nil? || !File.exist?($OPTION_todolist) ) && !$OPTION_notodolist
  raise "Sorry, the datebook file (#{$OPTION_datebook}) can not be found." if ( $OPTION_datebook.nil? || !File.exist?($OPTION_datebook) ) && !$OPTION_nodatebook
  raise "Sorry, the addressbook file (#{$OPTION_addressbook}) can not be found." if ( $OPTION_addressbook.nil? || !File.exist?($OPTION_addressbook) ) && !$OPTION_noaddressbook

  ## the output handling
  $output = Outputwriter.new($OPTION_output)

  $output.println("ZZ Zaurus")

  categoryHash = Hash.new()
  todolistArray = Array.new()
  datebookArray = Array.new()
  addressbookArray = Array.new()
  
  ParseCategories.new().parse($OPTION_categories,categoryHash) if !$OPTION_nocategories

  ParseTodolist.new().parse($OPTION_todolist,todolistArray) if !$OPTION_notodolist

  ParseDatebook.new().parse($OPTION_datebook,datebookArray) if !$OPTION_nodatebook

  ParseAddressbook.new().parseBirthdayOnly($OPTION_addressbook,addressbookArray) if !$OPTION_noaddressbook

  $output.println FormatTodolist.new.generatePalcalList(todolistArray, categoryHash, $OPTION_low.to_i, $OPTION_high.to_i) if !$OPTION_notodolist

  $output.println FormatDatebook.new($OPTION_from.to_i, $OPTION_to.to_i).generatePalcalList(datebookArray, categoryHash) if !$OPTION_nodatebook

  $output.println FormatAddressbook.new.generatePalcalListBirthdayOnly(addressbookArray, categoryHash) if !$OPTION_noaddressbook

  $output.screen "\nConversion successfully finished.\n"

end# if OPT...


## ------------------------------------------------------------------------------------------------------
##end
