1#!/usr/bin/env python
2#_____________________________________________
3# Caolan McNamara caolanm@redhat.com
4# converted from original java written by Andreas Schluens so we can continue
5# to build 680 OpenOffice.org series without java
6# this is not really a replacement for the existing java tool, just the
7# minimum required to make it work for now, the existing tool is still
8# the canonical base, changes to it will have to be mirrored here until
9# there is a java which is available for use by all
10#_____________________________________________
11
12import sys, string, os.path
13
14CFGFILE             = os.environ["SOLARVER"] + "/" + os.environ["INPATH"] + "/inc/l10ntools/FCFGMerge.cfg"
15
16PROP_XMLVERSION      = "xmlversion"               # // <= global cfg file
17PROP_XMLENCODING     = "xmlencoding"              # // <= global cfg file
18PROP_XMLPATH         = "xmlpath"                  # // <= global cfg file
19PROP_XMLPACKAGE      = "xmlpackage"               # // <= global cfg file
20PROP_SETNAME_TYPES    = "setname_types"           # // <= global cfg file
21PROP_SETNAME_FILTERS  = "setname_filters"         # // <= global cfg file
22PROP_SETNAME_LOADERS  = "setname_frameloaders"    # // <= global cfg file
23PROP_SETNAME_HANDLERS = "setname_contenthandlers" # // <= global cfg file
24PROP_SUBDIR_TYPES    = "subdir_types"             # // <= global cfg file
25PROP_SUBDIR_FILTERS  = "subdir_filters"           # // <= global cfg file
26PROP_SUBDIR_LOADERS  = "subdir_frameloaders"      # // <= global cfg file
27PROP_SUBDIR_HANDLERS = "subdir_contenthandlers"   # // <= global cfg file
28PROP_EXTENSION_XCU   = "extension_xcu"            # // <= global cfg file
29PROP_EXTENSION_PKG   = "extension_pkg"            # // <= global cfg file
30PROP_DELIMITER       = "delimiter"                # // <= global cfg file
31PROP_TRIM            = "trim"                     # // <= global cfg file
32PROP_DECODE          = "decode"                   # // <= global cfg file
33PROP_FRAGMENTSDIR    = "fragmentsdir"             # // <= cmdline
34PROP_TEMPDIR         = "tempdir"                  # // <= cmdline
35PROP_OUTDIR          = "outdir"                   # // <= cmdline
36PROP_PKG             = "pkg"                      # // <= cmdline
37PROP_TCFG            = "tcfg"                     # // <= cmdline
38PROP_FCFG            = "fcfg"                     # // <= cmdline
39PROP_LCFG            = "lcfg"                     # // <= cmdline
40PROP_CCFG            = "ccfg"                     # // <= cmdline
41PROP_LANGUAGEPACK    = "languagepack"             # // <= cmdline
42PROP_ITEMS           = "items"                    # // <= pkg cfg files!
43
44#---begin java.util.Properties copy---#
45"""
46
47An incomplete clean room implementation of
48java.util.Properties written in Python.
49
50Copyright (C) 2002,2004 - Ollie Rutherfurd <oliver@rutherfurd.net>
51
52Based on:
53
54        http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html
55
56Missing:
57
58 - Currently, u\XXXX sequences are escaped when saving, but not unescaped
59   when read..
60
61License: Python License
62
63Example Usage:
64
65>>> from properties import Properties
66>>> props = Properties()
67>>> props['one'] = '1'
68>>> props['your name'] = "I don't know"
69>>> print '\n'.join(props.keys())
70your name
71one
72>>> from StringIO import StringIO
73>>> buff = StringIO()
74>>> props.store(buff, "a little example...")
75>>> buff.seek(0)
76>>> print buff.read()
77# a little example...
78your\ name=I\ don\'t\ know
79one=1
80>>> print props['your name']
81I don't know
82
83$Id: pyAltFCFGMerge,v 1.3 2007-12-07 10:57:44 vg Exp $
84
85"""
86
87__all__ = ['Properties']
88
89
90def dec2hex(n):
91
92    h = hex(n)[2:].upper()
93    return '\\u' + '0' * (4 - len(h)) + h
94
95
96def escapestr(s):
97
98    buff = []
99    # QUESTION: escape leading or trailing spaces?
100    for c in s:
101        if c == '\\':
102            buff.append('\\\\')
103        elif c == '\t':
104            buff.append('\\t')
105        elif c == '\n':
106            buff.append('\\n')
107        elif c == '\r':
108            buff.append('\\r')
109        elif c == ' ':
110            buff.append('\\ ')
111        elif c == "'":
112            buff.append("\\'")
113        elif c == '"':
114            buff.append('\\"')
115        elif c == '#':
116            buff.append('\\#')
117        elif c == '!':
118            buff.append('\\!')
119        elif c == '=':
120            buff.append('\\=')
121        elif 32 <= ord(c) <= 126:
122            buff.append(c)
123        else:
124            buff.append(dec2hex(c))
125
126    return ''.join(buff)
127
128
129# TODO: add support for \uXXXX?
130def unescapestr(line):
131
132    buff = []
133    escape = 0
134    for i in range(len(line)):
135        c = line[i]
136        if c == '\\':
137            if escape:
138                escape = 0
139                buff.append('\\')
140                continue
141            else:
142                # this is to deal with '\'
143                # acting as a line continuation
144                # character
145                if i == len(line) - 1:
146                    buff.append('\\')
147                    break
148                else:
149                    escape = 1
150                    continue
151        elif c == 'n':
152            if escape:
153                escape = 0
154                buff.append('\n')
155                continue
156        elif c == 'r':
157            if escape:
158                escape = 0
159                buff.append('\r')
160                continue
161        elif c == 't':
162            if escape:
163                escape = 0
164                buff.append('\t')
165                continue
166
167        buff.append(c)
168
169        # make sure escape doesn't stay one
170        # all expected escape sequences either break
171        # or continue, so this should be safe
172        if escape:
173            escape = 0
174
175    return ''.join(buff)
176
177
178
179class Properties(dict):
180
181    def __init__(self, defaults={}):
182        dict.__init__(self)
183        for n,v in defaults.items():
184            self[n] = v
185
186    def __getittem__(self,key):
187        try:
188            return dict.__getittem__(self,key)
189        except KeyError:
190            return None
191
192    def read(self,filename):
193        """
194        Reads properties from a file (java Property class
195        reads from an input stream -- see load()).
196        """
197        f = None
198        try:
199            f = open(filename)
200            self.load(f)
201        finally:
202            if f:
203                f.close()
204
205    def load(self, buff):
206        """
207        Reads properties from a stream (StringIO, file, etc...)
208        """
209        props = readprops(buff)
210        #for n,v in props.iteritems():
211        for n in props.keys():
212            self[n] = props[n]
213
214def readprops(buff):
215
216    name,value = None,''
217    props = {}
218    continued = 0
219
220    while 1:
221        line = buff.readline()
222        if not line:
223            break
224        line = line.strip()
225
226        # empty line
227        if not line:
228            continue
229
230        # comment
231        if line[0] in ('#','!'):
232            continue
233
234        # find name
235        i,escaped = 0,0
236        while i < len(line):
237            c = line[i]
238
239            if c == '\\':
240                if escaped:
241                    escaped = 0
242                else:
243                    escaped = 1
244                i += 1
245                continue
246
247            elif c in (' ', '\t', ':', '=') and not escaped:
248                name = unescapestr(line[:i])
249                break
250
251            # make sure escaped doesn't stay on
252            if escaped:
253                escaped = 0
254
255            i += 1
256
257        # no dlimiter was found, name is entire line, there is no value
258        if name == None:
259            name = unescapestr(line.lstrip())
260
261        # skip delimiter
262        while line[i:i+1] in ('\t', ' ', ':', '='):
263            i += 1
264
265        value = unescapestr(line[i:].strip())
266        while value[-1:] == '\\':
267            value = value[:-1]      # remove \
268            line = buff.readline()
269            if not line:
270                break
271            value += unescapestr(line.strip())
272
273        #print 'value:',value   ##
274        props[name] = value
275
276    return props
277#---end java.util.Properties copy---#
278
279#   Its a simple command line tool, which can merge different XML fragments
280#   together. Such fragments must exist as files on disk, will be moved into
281#   one file together on disk.
282#
283#  @author  Andreas Schluens
284#
285def run(sCmdLine):
286    printCopyright()
287
288    aCfg = ConfigHelper(CFGFILE, sCmdLine)
289
290    # help requested?
291    if aCfg.isHelp():
292        printHelp()
293        sys.exit(-1)
294
295    #create new merge object and start operation
296    aMerger = Merger(aCfg)
297    aMerger.merge()
298
299    sys.exit(0)
300
301#prints out a copyright message on stdout.
302def printCopyright():
303    print("FCFGMerge")
304    print("Copyright: 2003 by Red Hat, Inc., based on FCFGMerge.java` by Sun")
305    print("All Rights Reserved.")
306
307#prints out a help message on stdout.
308def printHelp():
309    print("____________________________________________________________")
310    print("usage: FCFGMerge cfg=<file name>")
311    print("parameters:")
312    print("\tcfg=<file name>")
313    print("\t\tmust point to a system file, which contains")
314    print("\t\tall neccessary configuration data for the merge process.")
315    print("\tFurther cou can specify every parameter allowed in the")
316    print("\tconfig file as command line parameter too, to overwrite")
317    print("\tthe value from the file.")
318
319def StringTokenizer(mstring, separators, isSepIncluded=0):
320#Return a list of tokens given a base string and a string of
321#separators, optionally including the separators if asked for"""
322    token=''
323    tokenList=[]
324    for c in mstring:
325        if c in separators:
326            if token != '':
327                tokenList.append(token)
328                token=''
329                if isSepIncluded:
330                    tokenList.append(c)
331        else:
332            token+=c
333    if token:
334        tokenList.append(token)
335    return tokenList
336
337# can be used to analyze command line parameters
338# and merge it together with might existing config
339# files. That provides the possibility to overwrite
340# config values via command line parameter.
341#
342# @author Andreas Schluens
343class ConfigHelper:
344    def __init__(self, sPropFile, lCommandLineArgs):
345        self.m_bEmpty = 1
346        # first load prop file, so its values can be overwritten
347        # by command line args later
348        # Do it only, if a valid file name was given.
349        # But in case this file name is wrong, throw an exception.
350        # So the outside code can react!
351        if sPropFile != None and len(sPropFile) > 0:
352            self.props = Properties()
353            self.props.read(sPropFile)
354
355        count = 0
356        if lCommandLineArgs != None:
357            count = len(lCommandLineArgs)
358            print("Count is {c}".format(c=count))
359        self.m_bEmpty = (count < 1)
360
361        #print( lCommandLineArgs, "and len is", count )
362        for arg in range(count):
363            # is it a named-value argument?
364            # Note: We ignores double "=" signs! => search from left to right
365            pos = lCommandLineArgs[arg].find('=')
366            if pos != -1:
367                sArg   = lCommandLineArgs[arg][0:pos]
368                sValue = lCommandLineArgs[arg][pos+1:]
369                self.props[sArg] = sValue
370                continue
371
372            # is it a boolean argument?
373            # Note: Because "--" and "-" will be interpreted as the same
374            # we search from right to left!
375            pos = lCommandLineArgs[arg].rfind('-')
376            if pos == -1:
377                pos = lCommandLineArgs[arg].rfind('/')
378            if pos != -1:
379                sArg = lCommandLineArgs[arg][pos+1:]
380                self.props[sArg] = 1
381                continue
382
383            raise Exception("Invalid command line detected. The argument \""+lCommandLineArgs[arg]+"\" use an unsupported format.")
384
385#        for item in self.props:
386#            print item, '->', self.props[item]
387
388    def isHelp(self):
389        return (
390                ("help" in self.props) or
391                ("?" in self.props   ) or
392                ("h" in self.props   )
393               )
394
395    def getValue(self, sProp):
396        if not sProp in self.props:
397            raise Exception("The requested config value \""+sProp+"\" "\
398                "does not exists!");
399        return self.props[sProp];
400
401    def getValueWithDefault(self, sProp, default):
402        if not sProp in self.props:
403            return default;
404        return self.props[sProp];
405
406    def getStringList(self, sProp, sDelimiter, bTrim, bDecode):
407        if not sProp in self.props:
408            raise Exception("The requested config value \""+sProp+"\" does "\
409                "not exists!");
410        sValue = self.props[sProp]
411
412        lValue = []
413        lTokens = StringTokenizer(sValue, sDelimiter)
414        for sToken in lTokens:
415            if bTrim:
416                sToken = sToken.strip()
417
418            # remove ""
419            if ((bDecode) and (sToken.find("\"") == 0) and \
420                (sToken.rfind("\"") == len(sToken)-1)):
421                sToken = sToken[1, len(sToken)-1]
422            lValue.append(sToken)
423
424        return lValue
425
426def generateHeader(sVersion, sEncoding, sPath, sPackage, bLanguagePack):
427    sHeader = "<?xml version=\""
428    sHeader += sVersion
429    sHeader += "\" encoding=\""
430    sHeader += sEncoding
431    sHeader += "\"?>\n"
432
433    if bLanguagePack:
434        sHeader += "<oor:component-data oor:package=\""
435        sHeader += sPath
436        sHeader += "\" oor:name=\""
437        sHeader += sPackage
438        sHeader += "\" xmlns:install=\"http://openoffice.org/2004/installation\""
439        sHeader += " xmlns:oor=\"http://openoffice.org/2001/registry\""
440        sHeader += " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
441        sHeader += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
442    else:
443        sHeader += "<oor:component-data xmlns:oor=\"http://openoffice.org/2001/registry\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" oor:package=\""
444        sHeader += sPath
445        sHeader += "\" oor:name=\""
446        sHeader += sPackage
447        sHeader += "\">\n"
448    return sHeader
449
450def generateFooter():
451    return "</oor:component-data>\n"
452
453# can merge different xml fragments together.
454#
455#   @author Caolan McNamara converted from the original java by Andreas Schluens
456#
457class Merger:
458    def __init__(self, aCfg):
459        self.m_aCfg = aCfg
460
461        self.m_aFragmentsDir = self.m_aCfg.getValue(PROP_FRAGMENTSDIR)
462
463        sDelimiter = self.m_aCfg.getValue(PROP_DELIMITER)
464        bTrim = self.m_aCfg.getValue(PROP_TRIM)
465        bDecode = self.m_aCfg.getValue(PROP_DECODE)
466
467        try:
468            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_TCFG), None)
469            self.m_lTypes = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
470        except:
471            self.m_lTypes = []
472
473        try:
474            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_FCFG), None)
475            self.m_lFilters = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
476        except:
477            print( "Filters are empty" )
478            self.m_lFilters = []
479
480        try:
481            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_LCFG), None)
482            self.m_lLoaders = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
483        except:
484            self.m_lLoaders = []
485
486        try:
487            aFcfg = ConfigHelper(self.m_aCfg.getValue(PROP_CCFG), None)
488            self.m_lHandlers = aFcfg.getStringList(PROP_ITEMS, sDelimiter, bTrim, bDecode)
489        except:
490            self.m_lHandlers = []
491
492    def merge(self):
493        sPackage = self.m_aCfg.getValue(PROP_PKG)
494
495        print("create package \""+sPackage+"\" ...")
496        print("generate package header ... ")
497
498        sBuffer = generateHeader(\
499                self.m_aCfg.getValue(PROP_XMLVERSION ),\
500                self.m_aCfg.getValue(PROP_XMLENCODING),\
501                self.m_aCfg.getValue(PROP_XMLPATH    ),\
502                self.m_aCfg.getValue(PROP_XMLPACKAGE ),\
503                self.m_aCfg.getValueWithDefault(PROP_LANGUAGEPACK, False))
504
505        # counts all transfered fragments
506        # Can be used later to decide, if a generated package file
507        # contains "nothing"!
508        nItemCount = 0
509
510        for i in range(4):
511            sSetName = None
512            sSubDir = None
513            lFragments = None
514
515            try:
516                if i == 0: #types
517                    print("generate set for types ... ")
518                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_TYPES)
519                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_TYPES )
520                    lFragments = self.m_lTypes
521                elif i == 1: # filters
522                    print("generate set for filter ... ")
523                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_FILTERS)
524                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_FILTERS )
525                    lFragments = self.m_lFilters
526                elif i == 2: # loaders
527                    print("generate set for frame loader ... ")
528                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_LOADERS)
529                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_LOADERS )
530                    lFragments = self.m_lLoaders
531                elif i == 3: # handlers
532                    print("generate set for content handler ... ")
533                    sSetName = self.m_aCfg.getValue(PROP_SETNAME_HANDLERS)
534                    sSubDir = self.m_aCfg.getValue(PROP_SUBDIR_HANDLERS )
535                    lFragments = self.m_lHandlers
536            except:
537                continue
538
539            print("Length of Fragments: {f} Set Name {setname} Subdir {subdir}".
540             format(f=len(lFragments),setname=sSetName,subdir=sSubDir))
541            #sys.stdin.readline()
542            nItemCount = nItemCount + len(lFragments)
543
544            sBuffer = sBuffer + self.getFragments(\
545                os.path.join(self.m_aFragmentsDir, sSubDir), \
546                sSetName, lFragments, 1)
547
548        print("generate package footer ... ")
549        sBuffer = sBuffer + generateFooter()
550
551        # Attention!
552        # If the package seems to be empty, it make no sense to generate a
553        # corresponding xml file. We should suppress writing of this file on
554        # disk completely ...
555        if nItemCount < 1:
556            print("Package is empty and will not result into an xml file on disk!? Please check configuration file.")
557            return
558        print("package contains "+str(nItemCount)+" items")
559
560        aPackage = open(sPackage, mode="w")
561        print("write temp package {pkg}".format(pkg=sPackage))
562        aPackage.write(sBuffer)
563
564    def getFragments(self, aDir, sSetName, lFragments, nPrettyTabs):
565        sBuffer = ''
566        sExtXcu = self.m_aCfg.getValue(PROP_EXTENSION_XCU);
567
568        if len(lFragments) < 1:
569            return sBuffer
570
571        for tabs in range(nPrettyTabs):
572            sBuffer = sBuffer + "\t"
573        sBuffer = sBuffer + "<node oor:name=\""+sSetName+"\">\n"
574        nPrettyTabs = nPrettyTabs + 1
575
576        for sFragment in lFragments:
577            sFragPath = os.path.join(aDir, sFragment+"."+sExtXcu)
578            try:
579                aFragmentFile = open(sFragPath)
580            except:
581                # handle simple files only and check for existence!
582                raise Exception("fragment \""+sFragPath+"\" does not exists.")
583
584            print("merge fragment \""+sFragPath+"\" ...")
585            sBuffer = sBuffer + aFragmentFile.read()
586
587            sBuffer = sBuffer + "\n"
588
589        nPrettyTabs = nPrettyTabs - 1
590        for tabs in range(nPrettyTabs):
591            sBuffer = sBuffer + "\t"
592        sBuffer = sBuffer + "</node>\n"
593        return sBuffer
594
595run(sys.argv)
596