#!/bin/env python
#_____________________________________________
# Caolan McNamara caolanm@redhat.com
# converted from original java written by Andreas Schluens so we can continue
# to build 680 OpenOffice.org series without java
# this is not really a replacement for the existing java tool, just the
# minimum required to make it work for now, the existing tool is still
# the canonical base, changes to it will have to be mirrored here until
# there is a java which is available for use by all
#_____________________________________________

import sys, string, os.path, codecs 

CFGFILE             = os.environ["SOLARVER"] + "/" + os.environ["INPATH_FOR_BUILD"] + "/inc/l10ntools/FCFGMerge.cfg"

PROP_XMLVERSION      = "xmlversion"               # // <= global cfg file
PROP_XMLENCODING     = "xmlencoding"              # // <= global cfg file
PROP_XMLPATH         = "xmlpath"                  # // <= global cfg file
PROP_XMLPACKAGE      = "xmlpackage"               # // <= global cfg file
PROP_SETNAME_TYPES    = "setname_types"           # // <= global cfg file
PROP_SETNAME_FILTERS  = "setname_filters"         # // <= global cfg file
PROP_SETNAME_LOADERS  = "setname_frameloaders"    # // <= global cfg file
PROP_SETNAME_HANDLERS = "setname_contenthandlers" # // <= global cfg file
PROP_SUBDIR_TYPES    = "subdir_types"             # // <= global cfg file
PROP_SUBDIR_FILTERS  = "subdir_filters"           # // <= global cfg file
PROP_SUBDIR_LOADERS  = "subdir_frameloaders"      # // <= global cfg file
PROP_SUBDIR_HANDLERS = "subdir_contenthandlers"   # // <= global cfg file
PROP_EXTENSION_XCU   = "extension_xcu"            # // <= global cfg file
PROP_EXTENSION_PKG   = "extension_pkg"            # // <= global cfg file
PROP_DELIMITER       = "delimiter"                # // <= global cfg file
PROP_TRIM            = "trim"                     # // <= global cfg file
PROP_DECODE          = "decode"                   # // <= global cfg file
PROP_FRAGMENTSDIR    = "fragmentsdir"             # // <= cmdline
PROP_TEMPDIR         = "tempdir"                  # // <= cmdline
PROP_OUTDIR          = "outdir"                   # // <= cmdline
PROP_PKG             = "pkg"                      # // <= cmdline
PROP_TCFG            = "tcfg"                     # // <= cmdline
PROP_FCFG            = "fcfg"                     # // <= cmdline
PROP_LCFG            = "lcfg"                     # // <= cmdline
PROP_CCFG            = "ccfg"                     # // <= cmdline
PROP_LANGUAGEPACK    = "languagepack"             # // <= cmdline
PROP_ITEMS           = "items"                    # // <= pkg cfg files!

#---begin java.util.Properties copy---#
"""

An incomplete clean room implementation of 
java.util.Properties written in Python.

Copyright (C) 2002,2004 - Ollie Rutherfurd <oliver@rutherfurd.net>

Based on:

    http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html

Missing:

 - Currently, u\XXXX sequences are escaped when saving, but not unescaped
   when read..

License: Python License

Example Usage:

>>> from properties import Properties
>>> props = Properties()
>>> props['one'] = '1'
>>> props['your name'] = "I don't know"
>>> print('\n'.join(list(props.keys())))
your name
one
>>> from StringIO import StringIO
>>> buff = StringIO()
>>> props.store(buff, "a little example...")
>>> buff.seek(0)
>>> print(buff.read())
# a little example...
your\ name=I\ don\'t\ know
one=1
>>> print(props['your name'])
I don't know

$Id: pyAltFCFGMerge,v 1.3 2007-12-07 10:57:44 vg Exp $

"""

__all__ = ['Properties']


def dec2hex(n):
    h = hex(n)[2:].upper()
    return '\\u' + '0' * (4 - len(h)) + h


def escapestr(s):
    buff = []
    # QUESTION: escape leading or trailing spaces?
    for c in s:
        if c == '\\':
            buff.append('\\\\')
        elif c == '\t':
            buff.append('\\t')
        elif c == '\n':
            buff.append('\\n')
        elif c == '\r':
            buff.append('\\r')
        elif c == ' ':
            buff.append('\\ ')
        elif c == "'":
            buff.append("\\'")
        elif c == '"':
            buff.append('\\"')
        elif c == '#':
            buff.append('\\#')
        elif c == '!':
            buff.append('\\!')
        elif c == '=':
            buff.append('\\=')
        elif 32 <= ord(c) <= 126:
            buff.append(c)
        else:
            buff.append(dec2hex(c))

    return ''.join(buff)


# TODO: add support for \uXXXX?
def unescapestr(line):
    buff = []
    escape = 0
    for i in range(len(line)):
        c = line[i]
        if c == '\\':
            if escape:
                escape = 0
                buff.append('\\')
                continue
            else:
                # this is to deal with '\'
                # acting as a line continuation
                # character
                if i == len(line) - 1:
                    buff.append('\\')
                    break
                else:
                    escape = 1
                    continue
        elif c == 'n':
            if escape:
                escape = 0
                buff.append('\n')
                continue
        elif c == 'r':
            if escape:
                escape = 0
                buff.append('\r')
                continue
        elif c == 't':
            if escape:
                escape = 0
                buff.append('\t')
                continue

        buff.append(c)

        # make sure escape doesn't stay one
        # all expected escape sequences either break
        # or continue, so this should be safe
        if escape:
            escape = 0

    return ''.join(buff)


class Properties(dict):
    def __init__(self, defaults={}):
        dict.__init__(self)
        for n,v in list(defaults.items()):
            self[n] = v

    def __getittem__(self,key):
        try:
            return dict.__getittem__(self,key)
        except KeyError:
            return None

    def read(self,filename):
        """
        Reads properties from a file (java Property class 
        reads from an input stream -- see load()).
        """
        f = None
        try:
            f = open(filename)
            self.load(f)
        finally:
            if f:
                f.close()

    def load(self, buff):
        """
        Reads properties from a stream (StringIO, file, etc...)
        """
        props = readprops(buff)
        for n,v in list(props.items()):
            self[n] = v

def readprops(buff):
    name,value = None,''
    props = {}
    continued = 0

    while 1:
        line = buff.readline()
        if not line:
            break
        line = line.strip()

        # empty line
        if not line:
            continue

        # comment
        if line[0] in ('#','!'):
            continue

        # find name
        i,escaped = 0,0
        while i < len(line):
            c = line[i]

            if c == '\\':
                if escaped:
                    escaped = 0
                else:
                    escaped = 1
                i += 1
                continue

            elif c in (' ', '\t', ':', '=') and not escaped:
                name = unescapestr(line[:i])
                break

            # make sure escaped doesn't stay on
            if escaped:
                escaped = 0

            i += 1

        # no dlimiter was found, name is entire line, there is no value
        if name == None:
            name = unescapestr(line.lstrip())

        # skip delimiter
        while line[i:i + 1] in ('\t', ' ', ':', '='):
            i += 1

        value = unescapestr(line[i:].strip())
        while value[-1:] == '\\':
            value = value[:-1]    # remove \
            line = buff.readline()
            if not line:
                break
            value += unescapestr(line.strip())

        props[name] = value

    return props
#---end java.util.Properties copy---#

#   Its a simple command line tool, which can merge different XML fragments
#   together. Such fragments must exist as files on disk, will be moved into
#   one file together on disk.
# 
#  @author  Andreas Schluens
# 
def run(sCmdLine):
    printCopyright()

    aCfg = ConfigHelper(CFGFILE, sCmdLine)

    # help requested?
    if aCfg.isHelp():
        printHelp()
        sys.exit(-1)

    #create new merge object and start operation
    aMerger = Merger(aCfg)
    aMerger.merge()

    sys.exit(0)

#prints out a copyright message on stdout.
def printCopyright():
    print("FCFGMerge")
    print("Copyright: 2003 by Red Hat, Inc., based on FCFGMerge.java` by Sun")
    print("All Rights Reserved.")

# Prints out a help message on stdout.
def printHelp():
    print("____________________________________________________________")
    print("usage: FCFGMerge cfg=<file name>"                            )
    print("parameters:"                                                 )
    print("\tcfg=<file name>"                                           )
    print("\t\tmust point to a system file, which contains"             )
    print("\t\tall neccessary configuration data for the merge process.")
    print("\tFurther cou can specify every parameter allowed in the"    )
    print("\tconfig file as command line parameter too, to overwrite"   )
    print("\tthe value from the file."                                  )

# Return a list of tokens given a base string and a string of
# separators, optionally including the separators if asked for"""
def StringTokenizer(mstring, separators, isSepIncluded = 0):
    token = ''
    tokenList = []
    for c in mstring:
        if c in separators:
            if token != '':
                tokenList.append(token)
                token = ''
                if isSepIncluded:
                    tokenList.append(c)
        else:
            token += c
    if token:
        tokenList.append(token)
    return tokenList

# Can be used to analyze command line parameters
# and merge it together with might existing config
# files. That provides the possibility to overwrite
# config values via command line parameter.
# 
# @author Andreas Schluens
class ConfigHelper:
    def __init__(self, sPropFile, lCommandLineArgs):
        self.m_bEmpty = 1
        # first load prop file, so its values can be overwritten
        # by command line args later
        # Do it only, if a valid file name was given.
        # But in case this file name is wrong, throw an exception.
        # So the outside code can react!
        if sPropFile != None and len(sPropFile) > 0:
            self.props = Properties()
            self.props.read(sPropFile)

        count = 0
        if lCommandLineArgs != None:
            count = len(lCommandLineArgs)
        self.m_bEmpty = (count < 1)

        print(lCommandLineArgs, "and len is", count)
        for arg in range(count):
            # is it a named-value argument?
            # Note: We ignores double "=" signs! => search from left to right
            pos = lCommandLineArgs[arg].find('=')
            if pos != -1:
                sArg   = lCommandLineArgs[arg][0:pos]
                sValue = lCommandLineArgs[arg][pos + 1:]
                self.props[sArg] = sValue
                continue

            # is it a boolean argument?
            # Note: Because "--" and "-" will be interpreted as the same
            # we search from right to left!
            pos = lCommandLineArgs[arg].rfind('-')
            if pos == -1:
                pos = lCommandLineArgs[arg].rfind('/')
            if pos != -1:
                sArg = lCommandLineArgs[arg][pos + 1:]
                self.props[sArg] = 1
                continue
            
            raise Exception("Invalid command line detected. The argument \"" + \
                lCommandLineArgs[arg] + "\" use an unsupported format.")

    def isHelp(self):
        return ("help" in self.props) or ("?" in self.props) or ("?" in self.props)

    def getValue(self, sProp):
        if sProp not in self.props:
            raise Exception("The requested config value \"" + sProp + "\" "\
                "does not exists!");
        return self.props[sProp];

    def getValueWithDefault(self, sProp, default):
        if sProp not in self.props:
            return default;
        return self.props[sProp];

    def getStringList(self, sProp, sDelimiter, bTrim, bDecode):
        if sProp not in self.props:
            raise Exception("The requested config value \"" + sProp + "\" does "\
                "not exists!");
        sValue = self.props[sProp]

        lValue = []
        lTokens = StringTokenizer(sValue, sDelimiter)
        for sToken in lTokens:
            if bTrim:
                sToken = sToken.strip()
            # remove ""
            if ((bDecode) and (sToken.find("\"") == 0) and \
                (sToken.rfind("\"") == len(sToken) - 1)):
                sToken = sToken[1, len(sToken) - 1]
            lValue.append(sToken)

        return lValue

def generateHeader(sVersion, sEncoding, sPath, sPackage, bLanguagePack):
    sHeader = "<?xml version=\""
    sHeader += sVersion
    sHeader += "\" encoding=\""
    sHeader += sEncoding
    sHeader += "\"?>\n"

    if bLanguagePack:
        sHeader += "<oor:component-data oor:package=\""
        sHeader += sPath
        sHeader += "\" oor:name=\""
        sHeader += sPackage
        sHeader += "\" xmlns:install=\"http://openoffice.org/2004/installation\""
        sHeader += " xmlns:oor=\"http://openoffice.org/2001/registry\""
        sHeader += " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
        sHeader += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
    else:
        sHeader += "<oor:component-data xmlns:oor=\"http://openoffice.org/2001/registry\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" oor:package=\""
        sHeader += sPath
        sHeader += "\" oor:name=\""
        sHeader += sPackage
        sHeader += "\">\n"
    return sHeader

def generateFooter():
    return "</oor:component-data>\n"

# Can merge different xml fragments together.
#   @author Caolan McNamara converted from the original java by Andreas Schluens
class Merger:
    def __init__(self, aCfg):
        self.m_aCfg = aCfg

        self.m_aFragmentsDir = self.m_aCfg.getValue(PROP_FRAGMENTSDIR)

        sDelimiter = self.m_aCfg.getValue(PROP_DELIMITER)
        bTrim = self.m_aCfg.getValue(PROP_TRIM)
        bDecode = self.m_aCfg.getValue(PROP_DECODE)

        try:
            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_TCFG), None)
            self.m_lTypes = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
        except:
            self.m_lTypes = []

        try:
            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_FCFG), None)
            self.m_lFilters = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
        except:
            self.m_lFilters = []

        try:
            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_LCFG), None)
            self.m_lLoaders = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
        except:
            self.m_lLoaders = []

        try:
            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_CCFG), None)
            self.m_lHandlers = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
        except:
            self.m_lHandlers = []

    # Merges the xml sets returned by getFragments(...), adds an xml header
    # and footer and writes the result to a file.
    def merge(self):
        sPackage = self.m_aCfg.getValue(PROP_PKG)

        print("create package \"" + sPackage + "\" ...")
        print("generate package header ... ")

        sBuffer = generateHeader(\
                self.m_aCfg.getValue(PROP_XMLVERSION ),\
                self.m_aCfg.getValue(PROP_XMLENCODING),\
                self.m_aCfg.getValue(PROP_XMLPATH    ),\
                self.m_aCfg.getValue(PROP_XMLPACKAGE ),\
                self.m_aCfg.getValueWithDefault(PROP_LANGUAGEPACK, False))

        # counts all transfered fragments
        # Can be used later to decide, if a generated package file
        # contains "nothing"!
        nItemCount = 0

        for i in range(4):
            sSetName = None
            sSubDir = None
            lFragments = None

            try:
                if i == 0: # types
                    print("generate set for types ... ")
                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_TYPES)
                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_TYPES)
                    lFragments = self.m_lTypes
                elif i == 1: # filters
                    print("generate set for filter ... ")
                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_FILTERS)
                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_FILTERS)
                    lFragments = self.m_lFilters
                elif i == 2: # loaders
                    print("generate set for frame loader ... ")
                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_LOADERS)
                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_LOADERS)
                    lFragments = self.m_lLoaders
                elif i == 3: # handlers
                    print("generate set for content handler ... ")
                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_HANDLERS)
                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_HANDLERS)
                    lFragments = self.m_lHandlers
            except:
                continue

            nItemCount = nItemCount + len(lFragments)

            sBuffer = sBuffer + self.getFragments(\
                os.path.join(self.m_aFragmentsDir, sSubDir), \
                sSetName, lFragments, 1)

        print("generate package footer ... ")
        sBuffer = sBuffer + generateFooter()

        # Attention!
        # If the package seem to be empty, it make no sense to generate a
        # corresponding xml file. We should suppress writing of this file on 
        # disk completly ...
        if nItemCount < 1:
            print("Package is empty and will not result into a xml file on "\
                "disk!? Please check configuration file.")
            return
        print("package contains " + str(nItemCount) + " items")

        aPackage = codecs.open(sPackage, 'w', "utf-8")
        print("write temp package \"" + sPackage)
        aPackage.write(sBuffer)

    # Reads the fragment files with the file names lFragments in directory aDir,
    # formats them and returns a string that contains the merged fragments.
    def getFragments(self, aDir, sSetName, lFragments, nPrettyTabs):
        sBuffer = '' 
        sExtXcu = self.m_aCfg.getValue(PROP_EXTENSION_XCU);

        if len(lFragments) < 1:
            return sBuffer

        for tabs in range(nPrettyTabs):
            sBuffer = sBuffer + "\t"
        sBuffer = sBuffer + "<node oor:name=\"" + sSetName + "\">\n"
        nPrettyTabs = nPrettyTabs + 1

        for sFragment in lFragments:
            sFragPath = os.path.join(aDir, sFragment + "." + sExtXcu)
            try:
                aFragmentFile = codecs.open(sFragPath, "r", "utf-8")
            except:
                # handle simple files only and check for existence!
                raise Exception("fragment \"" + sFragPath + "\" does not exists.")
            print("merge fragment \"" + sFragPath + "\" ...")
            sBuffer = sBuffer + aFragmentFile.read()
            sBuffer = sBuffer + "\n"

        nPrettyTabs = nPrettyTabs - 1
        for tabs in range(nPrettyTabs):
            sBuffer = sBuffer + "\t"
        sBuffer = sBuffer + "</node>\n"
        return sBuffer

run(sys.argv)

