#! /usr/bin/env python
# -*- coding: iso-8859-1 -*-
#
# ZULU WEBSITE ASSEMBLER
#
# Copyright (C) 2002,2003,2004,2005 Peter Maerki und Hans Maerki
#
# This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# History
#   2003-05-27, Hans Maerki, Optimized access to dictionaries according to
#                            http://www.python.org/peps/pep-0290.html#looping-over-dictionaries
#   2004-02-03, Hans Maerki, Now using regular expressions for even more speed
#                            Unified the BaseDir algorithm
#                            Code review
#                            New Option: 'TraceStageOutput:'
#   2004-02-19, Hans Maerki, Tested on Mac OS X. No changes needed in this script
#   2004-02-19, Hans Maerki, Bugfix: Path will be displayed correctly in Multilanguage sites. Thanks to BeSe.
#   2004-05-11, Hans Maerki, v2.1.2: Bugfix: Sitemap: Hidden pages are hidden.
#   2004-05-30, Hans Maerki, v2.1.3: Bugfix: Sitemap: Hidden pages are hidden. Now reaaly fixed:-)
#   2005-03-09, Hans Maerki, v2.1.5: Bugfix: Better error messages.
#                                    Bugfix: v2.1.4 had version number v2.1.3!!!
#   2005-09-29, Hans Maerki, v2.1.6: Bugfix: The 'BaseDir'- and 'Input'-Entires are not mandatory anymore.
#   2005-10-24, Hans Maerki, v2.1.8--: Bugfix: Better errormessages for readonly files. Thanks to Gernot Segelbacher.
#   2006-12-24, Hans Maerki, v2.1.8: Now supports the OpenDocument format used by OpenOffice 2.x
#   2006-12-24, Hans Maerki, v2.1.8: Successfully tested on Ubuntu 6.10 and OsX Tiger
#

sCopyright = 'Copyright Hans und Peter Maerki. LGPL.'
sVersion = 'v2.1.8'
sDate = '2006-12-24a'
sProduct = 'Zulu Website-Assembler'

import string, codecs, sys, os, stat, time, re
import types, inspect, time
from xml.parsers import expat
from cgi import escape
from xml.sax._exceptions import SAXException

dictTables = {}
dictEntries = {}
dictOutputFiles = {}

class ZuluException(Exception):
  """Base class for exceptions in this module."""
  pass

class ZuluDontCreateFileException(Exception):
  """No need to create this file."""
  pass

patternZuluTemplate = re.compile(r"""#
# The whole expression matches strings like
#    <!--Zulu:Template:PathDelimiter:Page:Begin--> -> <!--Zulu:Template:PathDelimiter:Page:End-->
#
# This matches '<!--Zulu:Template:PathDelimiter:Page:Begin-->'. ':PathDelimiter:Page' -> tag
<!--Zulu:Template(?P<tag>.*?):Begin-->
#
# This matches the template itself ' -> '
(?P<template>.*?)
#
# This matches '<!--Zulu:Template:PathDelimiter:Page:Begin-->'
<!--Zulu:Template(?P=tag):End-->""", re.S|re.X)

strPatternZuluTag = r"""#
# The whole expression matches strings like (The inner Tag will be matched first)
#    Begin123<!--Zulu:Outer<!--Zulu:Inner-->-->456
#
# This matches '<!--Zulu:'
%s
#
# This matches the name of the tag: 'Inner'
# We don't allow all characters to disallow a match with inner tags.
# Zulu:Outer mustn't be matched for this example: <!--Zulu:Outer<!--Zulu:Inner-->-->
(?P<tag>[_:a-zA-Z0-9]*?)
#
# This matches '-->'
-->"""

patternZuluTag = re.compile(strPatternZuluTag % '<!--Zulu:Tag:', re.S|re.X)
patternZuluPythonTag = re.compile(strPatternZuluTag % '<!--Zulu:Python:', re.S|re.X)
ZULU_STRUCTURE = 'zulu_structure'

class Zulu:
  'The Zulu Class'

  def open(self):
    """
      Loads the structure file: OpenOffice 'structure.sxc' is used if it exists, otherwise
      'structure.xls'is ued.
      The file is parsed an all structures prepared.
    """
    self.listNavigations = []
    self.dictTemplates = {}
    self.dictBaseDirs = {}
    self.listTransform = []
    if len(sys.argv) > 1:
      try:
        import getopt
        opts, args = getopt.getopt(sys.argv[1:], 'hs:', ['help', 'structure='])
      except getopt.GetoptError:
        # print help information and exit:
        # self.usage()
        raise ZuluException()
      for o, a in opts:
        if o in ('-h', '--help'):
          usage()
          raise ZuluException()
        if o in ('-s', '--structure'):
          self.sFilenameStructure = a
    else:
      if os.path.exists(ZULU_STRUCTURE + '.ods'):
        self.sFilenameStructure = ZULU_STRUCTURE + '.ods'
      elif os.path.exists(ZULU_STRUCTURE + '.sxc'):
        self.sFilenameStructure = ZULU_STRUCTURE + '.sxc'
      else:
        self.sFilenameStructure = ZULU_STRUCTURE + '.xls'
    sFilenameXMLStructure = self.sFilenameStructure
    for strExtension in ('.sxc', '.ods', '.xls'):
      sFilenameXMLStructure = sFilenameXMLStructure.replace(strExtension, '.xml')

    self.objLogger = Logger('zulu_errorlog.html', self.sFilenameStructure)
    try:
      if (self.sFilenameStructure.endswith('.sxc') or self.sFilenameStructure.endswith('.ods')):
        # If it is a OpenOffice-document: transform it first into a xml-interrims-file
        self.objLogger.info('Transforming OpenOffice "%s" -> "%s".' % (self.sFilenameStructure, sFilenameXMLStructure))
        parser = OpenOfficeSaxParser()
        parser.Parse(self.sFilenameStructure, sFilenameXMLStructure)
      self.objLogger.info('Assembling structure "%s".' % sFilenameXMLStructure)
      parser = SaxParser()
      parser.Parse(sFilenameXMLStructure)
      dictNavigations = dictTables['Navigation']
    except (SAXException, IOError), e:
      self.objLogger.error('Failed to open the document. (%s: %s)' % (sys.exc_type, str(e)))
      raise ZuluException()

    for nav, value in dictNavigations.iteritems():
      self.listNavigations.append(Navigation(self, nav, value))

    for dictBaseDir in dictEntries.get('BaseDir', []):
      self.dictBaseDirs[dictBaseDir['tag']] = ''
      self.listTransform.append(TransformBaseDir(dictBaseDir))
    if dictEntries.has_key('Slash2backslash'):
      for listSlash2backslash in dictEntries['Slash2backslash']:
        self.listTransform.append(TransformSlash2Backslash(listSlash2backslash))

    if len(self.listNavigations) == 0:
      self.objLogger.error('No keyword "<navigation>" found: Need at least one!');
      raise ZuluException()

    self.objLogger.info('<navigation>-Keywords found: %d' % len(self.listNavigations));

  def callPython(self, objTemplate, objProcessingState, sText):
    """
      sText hast the form "MODULE_NAME:CLASS_NAME:PARAMETER"
      This method loads 'MODULE_NAME' and calls 'CLASS_NAME'.doit().
      This allows to write own handlers for additional functionality.
    """
    try:
      (sModule, sClass, sParameter) = string.split(sText, ':', 2)
    except ValueError:
      self.objLogger.error('Expected something like "<!--Zulu:Python:MODULE_NAME:CLASS_NAME:PARAMETER-->" but got "<!--Zulu:Python:%s-->".' % sText, objTemplate.strTemplateFilename)
      raise ZuluException()
    # return 'mod=%s, class=%s, par=%s' % (sModule, sClass, sParameter)

    try:
      mod = __import__(sModule)
    except ImportError:
      self.objLogger.error('Could not import Module "%s". See "<!--Zulu:Python:%s-->".' % (sModule, sText), objTemplate.strTemplateFilename)
      raise ZuluException()

    try:
      klass = getattr(mod, sClass)
    except AttributeError:
      self.objLogger.error('Could not find Class "%s". See "<!--Zulu:Python:%s-->".' % (sClass, sText), objTemplate.strTemplateFilename)
      raise ZuluException()

    try:
      if (type(klass) is types.ClassType):
        # print 'is Class'
        class EmptyClass:
          pass
        instance = EmptyClass()
        instance.__class__ = klass
        instance.__init__()
        return instance.doit(self, objTemplate, objProcessingState, sParameter)
    except Exception, e:
      self.objLogger.error('Error in call to "<!--Zulu:Python:%s-->". See traceback.' % sText, objTemplate.strTemplateFilename)
      raise ZuluException()

  def getTransform(self, sValue):
    objTemplate = Template(sValue)
    objTemplate.replace_transform(self)
    return objTemplate.strTemplate

  def replace_tags(self, objProcessingState, strTemplate, listlistReplace):
    objTemplate = Template(strTemplate)
    objTemplate.replace_tags(HandlerNavTag(listlistReplace), objProcessingState)
    return objTemplate.strTemplate

  def get_navigation_by_name(self, sName):
    for objNavigation in self.listNavigations:
      if sName == objNavigation.strName:
        return objNavigation
    raise IndexError('get_navigation_by_name("%s") not found' % sName)

  def create_page(self, objProcessingState):
    # Verify if we have to create the file: If Tag 'Folder' containts '-', the page creation may be skipped
    for strBaseDirTag in self.dictBaseDirs.keys():
      for objEntry in objProcessingState.listEntries:
        if objEntry.dictTags.get(strBaseDirTag) == '-':
          self.objLogger.info('File for "%s" will not be created: Tag "%s" is "-".' % (objProcessingState.getIdentification(), strBaseDirTag))
          return

    #
    # get template
    #
    strTemplateFilename = self.getOptionSingle('Template')
    objTemplate = Template(strTemplateFilename)
    objTemplate.replace_tags(HandlerTag(objProcessingState), objProcessingState)
    strTemplateFilename = objTemplate.strTemplate
    f = codecs.open(strTemplateFilename, 'r', 'Latin-1')
    objTemplate = Template(f.read(), strTemplateFilename)
    f.close()
    objProcessingState.dictGlobalTags['ZuluTemplateName'] = strTemplateFilename
    objProcessingState.dictGlobalTags['ZuluFilenameTemplate'] = strTemplateFilename

    #
    # Add input to dictGlobalTags
    #
    for dictInput in dictEntries.get('Input', []):
      strInputTag = dictInput['tag']
      strInputFilename = dictInput['a']

      objInputTemplate = Template(strInputFilename)
      objInputTemplate.replace_tags(HandlerTag(objProcessingState), objProcessingState)
      strInputFilename = objInputTemplate.strTemplate
      self.verifyFilename(objProcessingState, strInputFilename)
      try:
        f = codecs.open(strInputFilename, 'r', 'Latin-1')
        timeLastModified = time.localtime(os.stat(strInputFilename)[stat.ST_MTIME])
        objProcessingState.dictGlobalTags['InputLastModifiedDateTime'] = time.strftime('%Y-%m-%d %H:%M:%S', timeLastModified)
        objProcessingState.dictGlobalTags['InputLastModifiedDate'] = time.strftime('%Y-%m-%d', timeLastModified)
        objProcessingState.strInputFilename = strInputFilename
      except IOError, err:
        self.objLogger.error('%s does not exist (%s)' % (strInputFilename, str(err)))
        return

      objInputTemplate = Template(f.read(), strInputFilename)
      f.close()

      #
      # extract the Comment-Template
      #
      objInputTemplate.extract_comment(self, '')
      objInputTemplate.extract_comment(self, 'Input')

      # objProcessingState.dictGlobalTags[strInputTag] = objInputTemplate.strTemplate
      objTemplate.strTemplate = objTemplate.strTemplate.replace('<!--Zulu:Tag:%s-->' % strInputTag, objInputTemplate.strTemplate)

    #
    # extract the Comment-Template
    #
    objTemplate.extract_comment(self, 'Template')

    #
    # extract the templates
    #
    self.dictTemplates = objTemplate.extract_templates()
    self.write_output(objTemplate, objProcessingState, '_ZULUTRACE_EXTRACT_TEMPLATE')

    #
    # Replace everything in the replace list
    #
    objTemplate.replace_subst(self, 'Template')
    self.write_output(objTemplate, objProcessingState, '_ZULUTRACE_SUBST-TEMPLATE')

    #
    # replace the Python-Tags
    #
    objTemplate.replace_tags(HandlerPython(self, objProcessingState, objTemplate), objProcessingState)
    self.write_output(objTemplate, objProcessingState, '_ZULUTRACE_PYTHON')

    try:
      #
      # transform
      #
      for objTransform in self.listTransform:
        objTransform.transform(objTemplate, objProcessingState)
      self.write_output(objTemplate, objProcessingState, '_ZULUTRACE_TRANSFROM')

      #
      # replace the tags
      #
      objTemplate.replace_tags(HandlerTag(objProcessingState), objProcessingState)

      #
      # Replace everything in the replace list
      #
      objTemplate.replace_subst(self, 'Output')
      self.write_output(objTemplate, objProcessingState, '_ZULUTRACE_SUBST-OUTPUT')

      #
      # write output
      #
      self.write_output(objTemplate, objProcessingState, '')
    except ZuluDontCreateFileException, e:
      self.objLogger.info(str(e))

  def write_output(self, objTemplate, objProcessingState, sStage):
    iTraceStageOutput = self.getOptionSingle('TraceStageOutput', '0')
    if iTraceStageOutput != "1":
      # Skip StageOutput
      if sStage != '':
        return
    strOutputFilename = self.getOptionSingle('Output')
    strOutputFilename = strOutputFilename.replace('<!--Zulu:Stage-->', sStage)
    objOutputTemplate = Template(strOutputFilename)
    # print 'strOutputFilename', strOutputFilename
    objOutputTemplate.replace_tags(HandlerTag(objProcessingState), objProcessingState)
    strOutputFilename = objOutputTemplate.strTemplate
    # print 'strOutputFilename', strOutputFilename
    self.verifyFilename(objProcessingState, strOutputFilename)
    if self.getOptionSingle('PreserveArchiveBit', '0') == '1':
      # On Windows, it is handy to send only the files to the
      # server which have the archive-bit set. But when using
      # this mechanism, it is important not to write a file
      # if it hasn't been changed: To rewrite a file will reset the
      # archive bit.
      try:
        if not dictOutputFiles.has_key(strOutputFilename):
          # We don't know this file: Load it into the cache
          f = codecs.open(strOutputFilename, 'r', 'Latin-1')
          dictOutputFiles[strOutputFilename] = f.read()
          f.close()
        if dictOutputFiles[strOutputFilename] == objTemplate.strTemplate:
          # Content didn't change
          return
      except IOError, e:
        pass
      # We have to write the file
      dictOutputFiles[strOutputFilename] = objTemplate.strTemplate
    try:
      f = codecs.open(strOutputFilename, 'w', 'Latin-1')
    except IOError, e:
      if e.errno == 13: # Permission denied
        self.objLogger.error('This file is write protected. Please remove the write protection and try again. The error message was "%s"' % str(e), strOutputFilename)
        return
      self.zuluility_createFolderRecursive(strOutputFilename)
      f = codecs.open(strOutputFilename, 'w', 'Latin-1')
    f.write(objTemplate.strTemplate)
    f.close()

  def verifyFilename(self, objProcessingState, strFilename):
    if strFilename.find('//') >= 0:
      self.objLogger.warning('Filename for "%s" includes double "//": "%s".' % (objProcessingState.getIdentification(), strFilename))
    if strFilename.find('...') >= 0:
      self.objLogger.warning('Filename for "%s" includes tripple "..": "%s".' % (objProcessingState.getIdentification(), strFilename))
    if strFilename.find('/./') >= 0:
      self.objLogger.warning('Filename for "%s" includes tripple "/./": "%s".' % (objProcessingState.getIdentification(), strFilename))

  def getOptionSingle(self, strOption, strDefault=None):
    if strDefault == None:
      return dictEntries[strOption][0]['a']
    try:
      return dictEntries[strOption][0]['a']
    except:
      return strDefault

  def combine(self, listNavigations, listEntries):
    #
    # Use recursion to create all combinations of the navigations.
    # Example: 6 Pages, 2 Outputs (normal, print), 3 Languages (d, e, f)
    # This will create 6x2x3=36 cominations. Zulu will eventually
    # create 36 html-Pages.
    #
    for objEntry in listNavigations[0].listEntries:
      if len(listNavigations) == 1:
        self.create_page(ProcessingState(self, listEntries + [objEntry]))
      else:
        self.combine(listNavigations[1:], listEntries + [objEntry])

  def zulu(self):
    self.combine(self.listNavigations, [])
    self.objLogger.close()

  def zuluility_get_now(self):
      return time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime())

  def zuluility_createFolderRecursive(self, strFilename):
    strPath = os.path.dirname(strFilename)
    self.objLogger.info('Creating folder "%s".' % strFilename, None)
    os.makedirs(strPath)

class TransformBaseDir:
  "Transform BaseDir"
  def __init__(self, dictBaseDir):
    self.strTag = dictBaseDir['tag']
    self.strSearch = dictBaseDir['a']

  def transform(self, objTemplate, objProcessingState):
    """
      print 'strTag=', self.strTag, ' strValue=', strValue, ' base_dir()=', self.base_dir(objProcessingState, strValue)

      BaseDir
      Tag             Transform        Resulting Path for '/install/index.html'
      ''              '.'                  './install/index.html'
      '/x'            '..'                '../install/index.html'
      '/x/y'          '../..'          '../../install/index.html'
      '-'             This case should never get here
    """
    strValue = HandlerTag(objProcessingState).getValue(self.strTag)
    if strValue == '-':
      raise 'Internal programming error'

    if strValue == '':
      # Special case
      objTemplate.strTemplate = objTemplate.strTemplate.replace(self.strSearch, '.')
      return

    if strValue == '/':
      objProcessingState.objZulu.objLogger.error('"%s": "/" does not make sense. If the file is in the root folder, leave the field empty.' % objProcessingState.getIdentification())
      return

    if not strValue.startswith('/'):
      objProcessingState.objZulu.objLogger.error('"%s": "%s" is "%s", expected to start with "/".' % (objProcessingState.getIdentification(), self.strTag, strValue))
      return

    iCount = strValue.count('/')-1
    strReplace = '..' + iCount * '/..'
    objTemplate.strTemplate = objTemplate.strTemplate.replace(self.strSearch, strReplace)

class TransformSlash2Backslash:
  def __init__(self, dictSlash2Backslash):
    self.strTag = dictSlash2Backslash('tag')
    self.strSearch = dictSlash2Backslash('a')

  def transform(self, objTemplate, objProcessingState):
    pass

class Entry:
  "The Entry Class"
  def __init__(self, objNavigation, dictTags):
    self.objNavigation = objNavigation
    self.dictTags = dictTags
    self.strPath = self.dictTags[objNavigation.strName]
    if string.find(self.strPath, '-hidden') == -1:
      self.bHidden = 0
    else:
      self.bHidden = 1
      self.strPath = self.strPath.replace('-hidden', '')
      self.dictTags[objNavigation.strName] = self.strPath

  def to_be_displayed(self, strPathOfPageToBeCreated):
    # ActualEntry.to_be_displayed(strPathOfPageToBeCreated)
    # true if the parent of 'ActualEntry' is a parent
    # of 'strPathOfPageToBeCreated' or one of its parents.
    if self.objParent == None:
      # Entries with no parent are always displayed
      if self.bHidden:
        # This entry is marked as hidden (has '-hidden' in its path).
        # The entry is only displayed if a child is selected.
        return string.find(strPathOfPageToBeCreated, self.strPath) == 0
      return 1
    iPos = string.rfind(self.strPath, '/')
    if iPos == -1:
      raise 'to_be_displayed(): Internal programming error'
    strParent = self.strPath[:iPos]
    if string.find(strPathOfPageToBeCreated, strParent) == 0:
      if self.bHidden:
        # This entry is marked as hidden (has '-hidden' in its path).
        # The entry is only displayed if a child is selected.
        return string.find(strPathOfPageToBeCreated, self.strPath) == 0
      return 1
    return 0

  def anchester_is_hidden(self):
    if self.objParent == None:
      return 0
    if self.objParent.bHidden:
      return 1
    return self.objParent.anchester_is_hidden()

class Navigation:
  "The Navigation Class"
  def __init__(self, objZulu, strName, listEntries):
    self.i = 0
    self.listEntries = []
    self.strName = strName
    for dictTags in listEntries:
      self.listEntries.append(Entry(self, dictTags))

    # Calculate iLevel for each 'Entry'
    for objEntry in self.listEntries:
      objEntry.iLevel = self.get_level(objEntry.strPath)

    for objEntry in self.listEntries:
      objEntry.objParent = self.get_parent(objEntry.strPath)
      if objEntry.objParent == None:
        if string.find(objEntry.strPath, '/', 1) != -1:
          if not objEntry.bHidden:
            objZulu.objLogger.warning('Navigation "%s": Hidden entry "%s".' % (self.strName, objEntry.strPath), objZulu.sFilenameStructure)

  def get_parent(self, strPath):
    # if strPath == '/referenz/private/child':
    #   print 'hallo'
    iPos = string.rfind(strPath, '/', 1)
    if iPos == -1:
      # We are a top-item
      return None
    strPath = strPath[:iPos]
    for objEntry in self.listEntries:
      if strPath == objEntry.strPath:
        # This is our Parent!
        return objEntry
    return None

  def get_level(self, strPath):
    #
    # Examples:  strPath                  return
    #            '/'                       0
    #            '/anleitung'              0
    #            '/anleitung/einfuehrung'  1
    #
    if strPath == '/':
      # special case
      return 0
    return strPath.count('/', 1)


class ProcessingState:
  "This calls keeps track of the current state of the processing of the Zulu-Site"

  def __init__(self, objZulu, listEntries):
    self.objZulu = objZulu
    self.listEntries = listEntries
    self.dictGlobalTags = {'ZuluVersion' :sVersion,
                'ZuluProductName'        :sProduct+' '+sVersion,
                'ZuluExcelName'          :objZulu.sFilenameStructure,
                'ZuluFilenameStructure'  :objZulu.sFilenameStructure,
                'ZuluAssembledDateTime'  :objZulu.zuluility_get_now()}

  def getPath(self, sTag):
    'Example: sTag="Page", return="<!--Zulu:Root-->sample_sites/index.html"'

    for objEntry in self.listEntries:
      if objEntry.dictTags.has_key(sTag):
        return objEntry.dictTags[sTag]

  def getIdentification(self):
    list = map(lambda l: l.strPath, self.listEntries)
    return ' <-> '.join(list)

class HandlerTag:
  "This class handles '<!--Zulu:Tag:XXX-->'"

  def __init__(self, objProcessingState):
    self.patternZuluTag = patternZuluTag
    self.objProcessingState = objProcessingState

  def getValue(self, sTag):
    for objEntry in self.objProcessingState.listEntries:
      if objEntry.dictTags.has_key(sTag):
        return objEntry.dictTags[sTag]
    try:
      return self.objProcessingState.dictGlobalTags[sTag]
    except KeyError:
      raise IndexError('"%s": Tag <!--Zulu:Tag:%s--> not found' % (self.objProcessingState.getIdentification() ,sTag))

class HandlerNavTag:
  "This class handles '<!--Zulu:Tag:XXX-->'"

  def __init__(self, listEntries):
    self.patternZuluTag = patternZuluTag
    self.listEntries = listEntries

  def getValue(self, sTag):
    for objEntry in self.listEntries:
      if objEntry.dictTags.has_key(sTag):
        return objEntry.dictTags[sTag]
    return '<!--Hidden_Zulu:Tag:%s-->' % sTag

class HandlerPython:
  "This class handles '<!--Zulu:Python:XXX-->'"

  def __init__(self, objZulu, objProcessingState, objTemplate):
    self.patternZuluTag = patternZuluPythonTag
    self.objZulu = objZulu
    self.objProcessingState = objProcessingState
    self.objTemplate = objTemplate;

  def getValue(self, sTag):
    return self.objZulu.callPython(self.objTemplate, self.objProcessingState, sTag)

def getText(nodelist):
  rc = ''
  for node in nodelist:
    if node.nodeType == node.TEXT_NODE:
      rc = rc + node.data
  return rc

class Template:
  'The Template Class'
  def __init__(self, strTemplate, strTemplateFilename = None):
    self.strTemplate = strTemplate
    self.strTemplateFilename = strTemplateFilename

  def extract_comment(self, objZulu, strType):
    for dictComment in dictEntries['Comment']:
      if dictComment['tag'] == strType:
        strStart = dictComment['a']
        strEnd = dictComment['b']
        while True:
          # Find 'strStart'
          if strStart == '^':
            iStartPos = 0
          else:
            # Find 'strEnd'
            iStartPos = self.strTemplate.find(strStart)
            if iStartPos==-1:
              # Keine Template gefunden
              break
          if strEnd == '$':
            iEndPos = len(self.strTemplate)-1
          else:
            iEndPos = self.strTemplate.find(strEnd, iStartPos+len(strStart))
            if iEndPos == -1:
              if strStart == '^':
                break
              objZulu.objLogger.error('After "%s", expected to find "%s".' % (strStart, strEnd), self.strTemplateFilename)
              raise ZuluException()
          # remove comment
          self.strTemplate = self.strTemplate[0:iStartPos] + self.strTemplate[iEndPos+len(strEnd):]

  def extract_templates(self):
    def template_replace(objMatch):
      dictMatch = objMatch.groupdict()
      dictTemplates[dictMatch['tag']] = dictMatch['template']
      return ''

    dictTemplates = {}
    self.strTemplate = re.sub(patternZuluTemplate, template_replace, self.strTemplate)
    return dictTemplates

  def replace_tags(self, objHandler, objProcessingState):
    # Substitute now the Zulu-Tags
    def tag_replace(objMatch):
      strTag = objMatch.groupdict()['tag']
      # TODO:
      # strTmp = objHandler.getValue(strTag)
      return objHandler.getValue(strTag)

    while True:
      strBefore = self.strTemplate
      self.strTemplate = re.sub(objHandler.patternZuluTag, tag_replace, strBefore)
      if strBefore == self.strTemplate:
        # Keep replaceing: This will allow inner Tags: "<!--Zulu:Tag:Title<!--Zulu:Tag:Langextension-->-->"
        return

  def replace_subst(self, objZulu, strType):
    try:
      for dictSubst in dictEntries['Subst']:
        if dictSubst['tag'] == strType:
          self.strTemplate = self.strTemplate.replace(dictSubst['a'], dictSubst['b'])
    except:
      objZulu.objLogger.warning('Subst: "%s"->"%s". No classification ("Template" or "Output").' % (dictSubst['a'], dictSubst['b']), objZulu.sFilenameStructure)

#
# XML Helper function
#
# See xml_sample.py for examples how to used these helpers
#
def getXmlElementsFilter(xmlnode):
  """To be used by getXmlElements()"""
  return xmlnode.nodeType == xmlnode.ELEMENT_NODE

def getXmlElements(xmlnodeParent):
  """for node in getXmlElements(parent):   # returns a list of ELEMENT-subnodes"""
  return filter(getXmlElementsFilter, xmlnodeParent.childNodes)

def getXmlElementText(xmlnodeElement):
  """concatenates all strings of the TEXT-subnodes"""
  sReturn = ''
  for xmlnode in xmlnodeElement.childNodes:
    if xmlnode.nodeType == xmlnode.TEXT_NODE:
      sReturn = sReturn + xmlnode.data
  return sReturn

def getXmlSubnodeText(xmlnodeParent, sElementName, sDefault = None):
  """getXmlSubnodeText("<parent><a>Hello</a></parent>") returns "Hello" """
  for xmlnode in xmlnodeParent.getElementsByTagName(sElementName):
    return getXmlElementText(xmlnode)
  if sDefault is None:
    raise KeyError(sElementName)
  return sDefault

class Logger:
  "A HTML Logger"
  def __init__(self, sFilenameLog, sFilenameStructure):
    self.iErrors = 0
    self.iWarnings = 0
    self.iInfos = 0
    self.dictMessages = { }
    self.sFilenameStructure = sFilenameStructure
    self.file = codecs.open(sFilenameLog, 'w', 'Latin-1')
    self.file.write("""<html>
              <style>
              <!--
                .info { COLOR: green }
                .warning { COLOR: orange }
                .error { COLOR: red }
              -->
              </style>
              <body>
              <h1>%s %s</h1>
              running on %s<br>
              from \"%s\"<br>""" % (sProduct, sVersion, self.get_now(), self.sFilenameStructure))

  def warning(self, sWarning, sFilename = None):
    self.iWarnings = self.iWarnings + 1
    self.generic('warning', sWarning, sFilename)

  def error(self, sError, sFilename = None):
    self.iErrors = self.iErrors + 1
    self.generic('error', sError, sFilename)
    self.print_exception()

  def info(self, sInfo, sFilename = None):
    self.iInfos = self.iInfos + 1
    self.generic('info', sInfo, sFilename)

  def generic(self, sClass, sInfo, sFilename):
    if sFilename is None:
      sFilename = self.sFilenameStructure
    strMessage = '<a href="%s">%s</a>: <code class="%s">%s</code><br>' % (sFilename, sFilename, sClass, escape(sInfo))
    if not self.dictMessages.has_key(strMessage):
      self.file.write(strMessage)
      self.dictMessages[strMessage] = ''

  def print_exception(self, type=None, value=None, tb=None, limit=None):
    if type is None:
      type, value, tb = sys.exc_info()
    import traceback
    self.file.write("<H3>Traceback (most recent call last):</H3>")
    list = traceback.format_tb(tb, limit) + \
        traceback.format_exception_only(type, value)
    self.file.write('<PRE>%s<B>%s</B></PRE>' % (
      escape(''.join(list[:-1])),
      escape(list[-1]),
      ))
    del tb

  def get_now(self):
    return time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime())

  def close(self):
    self.file.write("""</body>
              </html>""")
    self.file.close();

#
# PageNavigation
#
# The following code only refers:
# from zulu import Zulu
# import string
class path:
  "This class creates a HTML-Path based on templates"

  def __init__(self):
    pass

  def doit(self, objZulu, objTemplate, objProcessingState, sParameter):
    """Example: sParameter='Page'   # The path will be created for the navigation 'Page'"""

    objNavigation = objZulu.get_navigation_by_name(sParameter)
    # sPagePath ist the path of the page we are going to create
    sPagePath = objProcessingState.getPath(sParameter)
    # Get template '<!--Zulu:Template:PathDelimiter:Page'
    strTemplate = objZulu.dictTemplates[':PathDelimiter:' + sParameter]
    strTemplateDelimiter = objZulu.replace_tags(objProcessingState, strTemplate, [])
    strReturn = ''
    for objEntry in objNavigation.listEntries:
      # sCurrentPath is the page of the page we actually loop throu
      sCurrentPath = objEntry.strPath
      if string.find(sPagePath, sCurrentPath) == 0:
        # Example:
        #   sCurrentPath = '/anleitung'
        #   sPagePath = '/anleitung/einfuehrung'
        # Get template '<!--Zulu:Template:Path:Page'
        strTemplate = objZulu.dictTemplates[':Path:' + sParameter]
        # strTemplatePath = objZulu.replace_tags(objProcessingState, strTemplate, [objEntry])
        strTemplatePath = objZulu.replace_tags(objProcessingState, strTemplate, [objEntry] + objProcessingState.listEntries)
        strTemplatePath = strTemplatePath.replace('<!--Hidden_Zulu:', '<!--Zulu:')
        if sPagePath == sCurrentPath:
          return strReturn + strTemplatePath
        else:
          strReturn = strReturn + strTemplatePath + strTemplateDelimiter
    raise IndexError('Internal Programming error')

#
# PageNavigation
#
class menu:
  "This class creates a menu based on templates"

  def __init__(self):
    pass

  def doit(self, objZulu, objTemplate, objProcessingState, sParameter):
    """Example: sParameter='Page:0:0'   # The menu will be created for the navigation 'Page'. 0:0 stands for 'openAllMenues':'selectParentEntries'"""

    try:
      (sNavigation, sOpenAllMenues, sSelectParentEntries) = string.split(sParameter, ':')
    except ValueError:
      objZulu.objLogger.error('Expected something like "<!--Zulu:Python:MODULE_NAME:CLASS_NAME:NAVIGATION:1:1-->" but got "<!--Zulu:Python:menu:%s-->".' % sParameter, objTemplate.strTemplateFilename)
      raise ZuluException()

    objNavigation = objZulu.get_navigation_by_name(sNavigation)
    strReturn = ''
    sPagePath = objProcessingState.getPath(sNavigation)
    for objEntry in objNavigation.listEntries:
      sCurrentPath = objEntry.strPath
      # if self.to_be_displayed(sOpenAllMenues, objEntry):
      if sOpenAllMenues=='1' or objEntry.to_be_displayed(sPagePath):
        if sCurrentPath==sPagePath or (sSelectParentEntries=='1' and string.find(sPagePath, sCurrentPath)==0):
          strTemplateName = ':Selected:%s:%d' % (objNavigation.strName, objEntry.iLevel)
        else:
          strTemplateName = ':Normal:%s:%d' % (objNavigation.strName, objEntry.iLevel)
        # Examples ':Normal:Page:0', ':Selected:Page:0'
        try:
          strTemplate = objZulu.dictTemplates[strTemplateName]
          sTmp = objZulu.replace_tags(objProcessingState, strTemplate, [objEntry] + objProcessingState.listEntries)
          sTmp = sTmp.replace('<!--Hidden_Zulu:', '<!--Zulu:')
          strReturn = strReturn + sTmp;
        except KeyError:
          objZulu.objLogger.warning('Template <!--Zulu:Template%s--> not found.' % strTemplateName, objTemplate.strTemplateFilename)
    return strReturn

#
# Sitemap
#
class sitemap(menu):
  "This class creates a sitemap based on templates"

  def __init__(self):
    pass

  def doit(self, objZulu, objTemplate, objProcessingState, sParameter):
    """Example: sParameter='Page'   # The menu will be created for the navigation 'Page'."""

    try:
      (sNavigation, sOpenAllMenues, sSelectParentEntries) = string.split(sParameter, ':')
      # Backward-Compatibility
      objZulu.objLogger.warning('This version of Zulu expects something like "<!--Zulu:Python:sitemap:Page-->" but got "<!--Zulu:Python:sitemap:%s-->".' % (sParameter), objProcessingState.strInputFilename)
    except ValueError:
      # This is the normal case
      sNavigation = sParameter

    objNavigation = objZulu.get_navigation_by_name(sNavigation)
    strReturn = ''
    sPagePath = objProcessingState.getPath(sNavigation)
    for objEntry in objNavigation.listEntries:
      sCurrentPath = objEntry.strPath
      if objEntry.bHidden:
        continue
      if objEntry.anchester_is_hidden():
        continue
      strTemplateName = ':Sitemap:%s:%d' % (objNavigation.strName, objEntry.iLevel)
      # Examples ':Sitemap:Page:0', ':Selected:Page:0'
      try:
        strTemplate = objZulu.dictTemplates[strTemplateName]
        sTmp = objZulu.replace_tags(objProcessingState, strTemplate, [objEntry] + objProcessingState.listEntries)
        sTmp = sTmp.replace('<!--Hidden_Zulu:', '<!--Zulu:')
        strReturn = strReturn + sTmp;
      except KeyError:
        objZulu.objLogger.warning('Template <!--Zulu:Template%s--> not found.' % strTemplateName, objTemplate.strTemplateFilename)
    return strReturn

#
# PageNavigation
#
class menu_level:
  "This class creates a menu based on templates. Only the menu at the selected levels will be created."

  def __init__(self):
    pass

  def doit(self, objZulu, objTemplate, objProcessingState, sParameter):
    """Example: sNavigation='Page:0:1'   # The menu will be created for the navigation 'Page'. The levels 0 to 1 will be created."""

    try:
      (sNavigation, sLevelFrom, sLevelTo) = string.split(sParameter, ':')
      iLevelFrom = string.atoi(sLevelFrom)
      iLevelTo = string.atoi(sLevelTo)
    except ValueError:
      objZulu.objLogger.error('Expected something like "<!--Zulu:Python:MODULE_NAME:CLASS_NAME:NAVIGATION:1:1-->" but got "<!--Zulu:Python:menu:%s-->".' % sParameter, objProcessingState.strInputFilename)
      raise ZuluException()

    objNavigation = objZulu.get_navigation_by_name(sNavigation)
    strReturn = ''
    sPagePath = objProcessingState.getPath(sNavigation)
    for objEntry in objNavigation.listEntries:
      sCurrentPath = objEntry.strPath
      if objEntry.to_be_displayed(sPagePath):
        if (objEntry.iLevel >= iLevelFrom) and (objEntry.iLevel <= iLevelTo):
          if sCurrentPath==sPagePath or (string.find(sPagePath, sCurrentPath)==0):
            strTemplateName = ':Selected:%s:%d' % (objNavigation.strName, objEntry.iLevel)
          else:
            strTemplateName = ':Normal:%s:%d' % (objNavigation.strName, objEntry.iLevel)
          # Examples ':Normal:Page:0', ':Selected:Page:0'
          try:
            strTemplate = objZulu.dictTemplates[strTemplateName]
            sTmp = objZulu.replace_tags(objProcessingState, strTemplate, [objEntry] + objProcessingState.listEntries)
            sTmp = sTmp.replace('<!--Hidden_Zulu:', '<!--Zulu:')
            strReturn = strReturn + sTmp;
          except KeyError:
            objZulu.objLogger.warning('Template <!--Zulu:Template%s--> not found.' % strTemplateName, objTemplate.strTemplateFilename)
    return strReturn

"""
                                            <<-- SaxElementAll
    <content>                               <<-- SaxElementContent
      <creator>                           <<-- SaxElementEntry
      <date>2003-01-28 00:11:29</date>    <<-- SaxElementSimple
      </creator>
      <include>                           <<-- SaxElementIncludeEntry
        <a>include.xml</a>              <<-- SaxElementSimple
      </include>
      <Navigation>                        <<-- SaxElementTable
      <Page>                              <<-- SaxElementEntry
        <Title>Home</Title>             <<-- SaxElementSimple
        <Page>home</Page>
        <Folder></Folder>
        <Link>&lt;!--Zulu:Root--&gt;index.html</Link>
        <MetaDescription>Zulu by Positron and Maerki Informatik</MetaDescription>
        <MetaKeywords>Zulu Website Assembler Positron Maerki Informatik Schweiz Switzerland</MetaKeywords>
      </Page>
      <Page>
        <Title>Was ist Zulu?</Title>
        <Page>zulu</Page>
        <Folder>-</Folder>
        <Link>&lt;!--Zulu:Root--&gt;zulu/einfuehrung/index.html</Link>
        <MetaDescription></MetaDescription>
        <MetaKeywords></MetaKeywords>
      </Page>
      </Navigation>
"""

class OpenOfficeSaxBase:
  def __init__(self, parent, objXmlFile):
    self.parent = parent
    self.objXmlFile = objXmlFile
  def StartElement(self, strName, dictAttributes):
    raise IndexError('No sub element expected.')
  def EndElement(self, strName):
    pass
  def CharacterData(self, strText):
    pass

class OpenOfficeSaxElementUnknown(OpenOfficeSaxBase):
  def StartElement(self, strName, dictAttributes):
    return OpenOfficeSaxElementUnknown(self, self.objXmlFile)

class OpenOfficeSaxElementAll(OpenOfficeSaxBase):
  def StartElement(self, strName, dictAttributes):
    if strName != 'office:document-content':
      raise IndexError('Expected <office:document-content> at the top level!')
    return OpenOfficeSaxElementContent(self, self.objXmlFile)

class OpenOfficeSaxElementContent(OpenOfficeSaxBase):
  def StartElement(self, strName, dictAttributes):
    if strName!='office:body':
      return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
    return OpenOfficeSaxElementBody(self, self.objXmlFile)

class OpenOfficeSaxElementBody(OpenOfficeSaxBase):
  def StartElement(self, strName, dictAttributes):
    if strName=='office:spreadsheet':
      # This is the ODS-OpenDocument Format
      return OpenOfficeSaxElementSpreadsheet(self, self.objXmlFile)
    if strName!='table:table':
      return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
    # This is the old SXC-OpenOffice Format
    return OpenOfficeSaxElementTable(self, self.objXmlFile)

class OpenOfficeSaxElementSpreadsheet(OpenOfficeSaxBase):
  def StartElement(self, strName, dictAttributes):
    if strName!='table:table':
      return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
    return OpenOfficeSaxElementTable(self, self.objXmlFile)

class OpenOfficeSaxElementTable(OpenOfficeSaxBase):
  def __init__(self, parent, objXmlFile):
    OpenOfficeSaxBase.__init__(self, parent, objXmlFile)
    self.bInZuluTable = 0
  def StartElement(self, strName, dictAttributes):
    if strName!='table:table-row':
      return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
    self.dictColumns = {}
    return OpenOfficeSaxElementRow(self, self.objXmlFile, self.dictColumns)
  def EndElement(self, strName):
    if strName!='table:table-row':
      return
    strKeyword = self.dictColumns.get(1, '')
    if strKeyword!='':
      if strKeyword[-4:]=='&gt;':
        self.bInZuluTable = 1
        self.dictZuluTags = self.dictColumns
        self.strTableName = strKeyword[:-4]
        self.objXmlFile.write('  <%s type="table">\n' % self.strTableName)
        return
      if strKeyword[-1:]==':':
        if self.bInZuluTable:
          self.objXmlFile.write('    <%s>\n' % self.dictColumns.get(2, '--Tag is missing--'))
          for i, strTag in self.dictZuluTags.iteritems():
            if i>2:
              strValue = self.dictColumns.get(i, '')
              self.objXmlFile.write('      <%s>%s</%s>\n' % (strTag, strValue, strTag))
          self.objXmlFile.write('    </%s>\n' % self.dictColumns.get(2, '--Tag is missing--'))
        else:
          self.objXmlFile.write('  <%s>\n' % strKeyword[:-1])
          for i, strValue in self.dictColumns.iteritems():
            if i<=1:
              continue
            if i==2:
              strTag = 'tag'
            else:
              strTag = chr(i+ord('a')-3)
            self.objXmlFile.write('    <%s>%s</%s>\n' % (strTag, strValue, strTag))
          self.objXmlFile.write('  </%s>\n' % strKeyword[:-1])
        return
    if self.bInZuluTable:
      self.objXmlFile.write('  </%s>\n' % self.strTableName)
      self.bInZuluTable = 0

class OpenOfficeSaxElementRow(OpenOfficeSaxBase):
  def __init__(self, parent, objXmlFile, dictColumns):
    OpenOfficeSaxBase.__init__(self, parent, objXmlFile)
    self.iColumn = 0
    self.dictColumns = dictColumns
  def StartElement(self, strName, dictAttributes):
    if strName!='table:table-cell':
      return OpenOfficeSaxElementUnknown(self)
    iColumnFrom = self.iColumn
    if dictAttributes.has_key('table:number-columns-repeated'):
      self.iColumn = self.iColumn + string.atoi(dictAttributes['table:number-columns-repeated'])
    else:
      self.iColumn = self.iColumn + 1
    return OpenOfficeSaxElementCell(self, self.objXmlFile, self.dictColumns, iColumnFrom, self.iColumn)

class OpenOfficeSaxElementCell(OpenOfficeSaxBase):
  ""
  def __init__(self, parent, objXmlFile, dictColumns, iColumnFrom, iColumnTo):
    OpenOfficeSaxBase.__init__(self, parent, objXmlFile)
    self.iColumnFrom = iColumnFrom
    self.iColumnTo = iColumnTo
    self.dictColumns = dictColumns
  def StartElement(self, strName, dictAttributes):
    if strName!='text:p':
      return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
    self.child = OpenOfficeSaxElementText(self, self.objXmlFile)
    return self.child

  def EndElement(self, strName):
    if strName!='text:p':
      return
    strText = self.child.strText
    strText = strText.replace('&' , '&amp;')
    strText = strText.replace('>', '&gt;')
    strText = strText.replace('<', '&lt;')
    for i in range(self.iColumnFrom, self.iColumnTo):
      self.dictColumns[i] = strText

class OpenOfficeSaxElementText(OpenOfficeSaxBase):
  def __init__(self, parent, objXmlFile):
    OpenOfficeSaxBase.__init__(self, parent, objXmlFile)
    self.strText = ''
  def StartElement(self, strName, dictAttributes):
    return OpenOfficeSaxElementUnknown(self, self.objXmlFile)
  def CharacterData(self, strText):
    'SAX character data event handler'
    self.strText += strText

class OpenOfficeSaxParser:
  'XML to Object'

  def StartElement(self, strName, dictAttributes):
    'SAX start element even handler'
    self.current = self.current.StartElement(strName, dictAttributes)

  def EndElement(self, strName):
    'SAX end element event handler'
    self.current = self.current.parent
    self.current.EndElement(strName)

  def CharacterData(self, strText):
    'SAX character data event handler'
    self.current.CharacterData(strText)

  def Parse(self, strFilenameOpenOffice, strFilenameXML):
    import zipfile

    objZipFile = zipfile.ZipFile(strFilenameOpenOffice, 'r')
    strXmlOpenOfficeContent = objZipFile.read('content.xml')
    objXmlFile = codecs.open(strFilenameXML, 'w', 'Latin-1')
    objXmlFile.write("""<?xml version="1.0" encoding="ISO-8859-1"?>
<content>
  <creator>
    <program>%s %s</program>
    <date>%s</date>
    <openoffice_filename>%s</openoffice_filename>
    <openoffice_path>%s</openoffice_path>
  </creator>
""" % (sProduct, sVersion, time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime()), strFilenameOpenOffice, '...path...'))

    self.current = OpenOfficeSaxElementAll(None, objXmlFile)

    # Create a SAX parser
    Parser = expat.ParserCreate()

    # SAX event handlers
    Parser.StartElementHandler = self.StartElement
    Parser.EndElementHandler = self.EndElement
    Parser.CharacterDataHandler = self.CharacterData

    # Parse the XML File
    # codecs.open(strInputFilename, 'r', 'Latin-1')
    ParserStatus = Parser.Parse(strXmlOpenOfficeContent, 1)

    objXmlFile.write('</content>')
    # Seems to be a bug on python 2.3+ on Suse 9.0. Without flush(), parts of the file will be corrupted
    objXmlFile.flush()
    objXmlFile.close()

class SaxBase:
  def __init__(self, parent):
    self.parent = parent
  def StartElement(self,name,attributes):
    raise IndexError('No sub element expected.')
  def EndElement(self,name):
    pass
  def CharacterData(self,data):
    pass

class SaxElementAll(SaxBase):
  def StartElement(self,name,attributes):
    if name != 'content':
      raise IndexError('Expected <content> at the top level!')
    return SaxElementContent(self)

class SaxElementContent(SaxBase):
  def StartElement(self,name,attributes):
    if attributes.get('type', '') == 'table':
      return SaxElementTable(self, name)
    if name == 'Include':
      return SaxElementIncludeEntry(self)
    self.child = SaxElementEntry(self)
    if not dictEntries.has_key(name):
      dictEntries[name] = []
    dictEntries[name].append(self.child.dictTags)
    return self.child

class SaxElementTable(SaxBase):
  def __init__(self, parent, strTableName):
    self.parent = parent
    self.strTablename = strTableName
  def StartElement(self,name,attributes):
    self.child = SaxElementEntry(self)
    if not dictTables.has_key(self.strTablename):
      dictTables[self.strTablename] = {}
    if not dictTables[self.strTablename].has_key(name):
      dictTables[self.strTablename][name] = []
    dictTables[self.strTablename][name].append(self.child.dictTags)
    return self.child

class SaxElementSimple(SaxBase):
  "Expects <Tag>Content</Tag>"
  def __init__(self,parent):
    SaxBase.__init__(self,parent)
    self.cdata = ''
  def StartElement(self,name,attributes):
    raise IndexError('SaxElementSimple.StartElement(): No subelements expected here...')
  def EndElement(self,name):
    raise IndexError('SaxElementSimple.EndElement(): No subelements expected here...')
  def CharacterData(self,data):
    'SAX character data event handler'
    # if string.strip(data):
    self.cdata += data  # .encode()

class SaxElementEntry(SaxBase):
  "Expects <Subst>..SaxElementSimple... ...SaxElementSimple...</Subst>"
  def __init__(self,parent):
    SaxBase.__init__(self,parent)
    self.dictTags = {}
  def StartElement(self,name,attributes):
    self.child = SaxElementSimple(self)
    return self.child
  def EndElement(self,name):
    self.dictTags[name] = self.child.cdata

class SaxElementIncludeEntry(SaxBase):
  "Expects <include><a>include.xml</a></include>"
  def StartElement(self,name,attributes):
    if name != 'a':
      raise IndexError('Expected <include><a>file.xml</a></include> but got "<%s>"!', self.cdata)
    self.child = SaxElementSimple(self)
    return self.child
  def EndElement(self,name):
    # Read in file
    parser = SaxParser()
    element = parser.Parse(self.child.cdata)

class SaxParser:
  'XML to Object'
  def __init__(self):
    self.nodeStack = []
    self.current = SaxElementAll(None)

  def StartElement(self,name,attributes):
    'SAX start element even handler'
    # Instantiate an Element object
    self.current = self.current.StartElement(name, attributes)

  def EndElement(self,name):
    'SAX end element event handler'
    self.current = self.current.parent
    self.current.EndElement(name)

  def CharacterData(self, data):
    'SAX character data event handler'
    self.current.CharacterData(data)

  def Parse(self, filename):
    # Create a SAX parser
    Parser = expat.ParserCreate()

    # SAX event handlers
    Parser.StartElementHandler = self.StartElement
    Parser.EndElementHandler = self.EndElement
    Parser.CharacterDataHandler = self.CharacterData

    # Parse the XML File
    # codecs.open(strInputFilename, 'r', 'Latin-1')
    f=open(filename, 'r')
    document=f.read()
    f.close()
    ParserStatus = Parser.Parse(document, 1)

def main():
  try:
    #
    # 'chdir' into the same folder as 'zulu.py'
    # This may be needed on the macintosh or if
    # zulu.py is started from another directory
    #
    strModulePath = os.path.abspath(os.path.dirname(inspect.getfile(main)))
    strCurdirPath = os.path.abspath(os.curdir)
    if strCurdirPath != strModulePath:
      print "chdir() was: %s" % strCurdirPath
      print "chdir() now: %s" % strModulePath
      os.chdir(strModulePath)
  except NameError, e:
    print "NameError: ", e

  zulu = Zulu()
  try:
    zulu.open()
    zulu.zulu()
  except ZuluException, e:
    # Error was already logged
    pass
  except:
    zulu.objLogger.error('')
  if zulu.objLogger.iErrors > 0:
    return 1
  return 0

if __name__ == '__main__':
  sys.exit(main())


