Ejemplo n.º 1
0
class Xetex (Manager) :

    # Shared values
    xmlConfFile     = 'xetex.xml'

    def __init__(self, project, cfg, cType) :
        '''Do the primary initialization for this manager.'''

        super(Xetex, self).__init__(project, cfg)

#        import pdb; pdb.set_trace()

        # Create all the values we can right now for this manager.
        # Others will be created at run time when we know the cid.
        self.tools                  = Tools()
        self.project                = project
        self.local                  = project.local
        self.log                    = project.log
        self.cfg                    = cfg
        self.pid                    = project.projectIDCode
        self.gid                    = project.gid
        self.cType                  = cType
        self.Ctype                  = cType.capitalize()
        self.mType                  = project.projectMediaIDCode
        self.renderer               = 'xetex'
        self.manager                = self.cType + '_' + self.renderer.capitalize()
        self.managers               = project.managers
        self.pg_back                = ProjBackground(self.pid, self.gid)
        self.fmt_diagnose           = ProjDiagnose(self.pid, self.gid)
        self.proj_config            = Config(self.pid, self.gid)
        self.proj_config.getProjectConfig()
        self.proj_config.getLayoutConfig()
        self.proj_config.getFontConfig()
        self.proj_config.getMacroConfig()
        # Bring in some manager objects we will need
        self.proj_font              = ProjFont(self.pid)
        self.proj_illustration      = ProjIllustration(self.pid, self.gid)
        self.proj_hyphenation       = ProjHyphenation(self.pid, self.gid)
        self.usfmData               = UsfmData()
        self.cidChapNumDict         = self.usfmData.cidChapNumDict()
        self.cidPtIdDict            = self.usfmData.cidPtIdDict()
        # Get config objs
        self.projectConfig          = self.proj_config.projectConfig
        self.layoutConfig           = self.proj_config.layoutConfig
        self.fontConfig             = self.proj_config.fontConfig
        self.macroConfig            = self.proj_config.macroConfig
        self.userConfig             = self.project.userConfig
        self.macPackId              = self.projectConfig['CompTypes'][self.Ctype]['macroPackage']
        # Some config settings

        self.pdfViewerCmd           = self.tools.getPdfViewerCommand(self.userConfig, self.projectConfig)
        self.sourceEditor           = self.projectConfig['CompTypes'][self.Ctype]['sourceEditor']
        self.useBackground          = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground'])
        self.useDiagnostic          = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic'])
        self.useDocInfo             = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo'])

        # Get settings for this component
        self.managerSettings = self.projectConfig['Managers'][self.manager]
        for k, v in self.managerSettings.iteritems() :
            if v == 'True' or v == 'False' :
                setattr(self, k, self.tools.str2bool(v))
            else :
                setattr(self, k, v)

        # Set some Booleans (this comes after persistant values are set)
        # Setting hyphenation is a 2 step process, first check global, then group
        self.useHyphenation         = False
        if self.tools.str2bool(self.projectConfig['ProjectInfo']['hyphenationOn']) :
            if self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useHyphenation']) :
                self.useHyphenation = True
                
        # In case the macro is not installed we need to skip over this
        try :
            self.chapNumOffSingChap     = self.tools.str2bool(self.macroConfig['Macros'][self.macPackId]['ChapterVerse']['omitChapterNumberOnSingleChapterBook'])
        except :
            self.chapNumOffSingChap     = None
        # Make any dependent folders if needed
        if not os.path.isdir(self.local.projGidFolder) :
            os.makedirs(self.local.projGidFolder)

        # Record some error codes
        # FIXME: much more needs to be done with this
        self.xetexErrorCodes =  {
            0   : 'Rendering succeful.',
            256 : 'Something really awful happened.'
                                }

        # Log messages for this module
        self.errorCodes     = {

            '1005' : ['ERR', 'PDF viewer failed with error: [<<1>>]'],
            '1010' : ['ERR', 'Style file [<<1>>] could not be created.'],
            '1040' : ['LOG', 'Created: [<<1>>]'],

            '0420' : ['WRN', 'TeX settings file has been frozen for debugging purposes.'],
            '0440' : ['LOG', 'Created: [<<1>>]'],
            '0460' : ['LOG', 'Settings changed in [<<1>>], [<<2>>] needed to be recreated.'],
            '0465' : ['LOG', 'File: [<<1>>] missing, created a new one.'],
            '0470' : ['ERR', 'Macro package [<<1>>] is not recognized by the system.'],

            '0600' : ['MSG', '<<1>> cannot be viewed, PDF viewer turned off.'],
            '0610' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] group.'],
            '0615' : ['ERR', 'XeTeX failed to execute with error: <<1>>'],
            '0617' : ['ERR', 'XeTeX failed to execute with this error: [<<1>>]'],
            '0620' : ['DBG', 'xetex command in <<1>>: <<2>> <<3>>'],
            '0625' : ['MSG', 'Rendering of [<<1>>] successful.'],
            '0630' : ['ERR', 'Rendering [<<1>>] was unsuccessful. <<2>> (<<3>>)'],
            '0635' : ['ERR', 'XeTeX error code [<<1>>] not understood by Rapuma.'],
            '0650' : ['ERR', 'Component type [<<1>>] not supported!'],
            '0690' : ['MSG', 'Dependent files unchanged, rerendering of [<<1>>] un-necessary.'],
            '0695' : ['MSG', 'Routing <<1>> to PDF viewer.'],
            '0700' : ['ERR', 'Rendered file not found: <<1>>'],
            '0710' : ['WRN', 'PDF viewing is disabled.'],
            '0720' : ['MSG', 'Saved rendered file to: [<<1>>]'],
            '0730' : ['ERR', 'Failed to save rendered file to: [<<1>>]'],

            '1000' : ['WRN', 'XeTeX debugging is set to [<<1>>]. These are the paths XeTeX is seeing: [<<2>>]'],
            '1090' : ['ERR', 'Invalid value [<<1>>] used for XeTeX debugging. Must use an integer of 0, 1, 2, 4, 8, 16, or 32']

        }

        # FIXME: It would be good if we could do a check for dependent files here


###############################################################################
############################ Manager Level Functions ##########################
###############################################################################
######################## Error Code Block Series = 1000 #######################
###############################################################################

    def checkStartPageNumber (self) :
        '''Adjust page number for the current group. The current logic is
        if there is no number in the startPageNumber setting, we can put
        one in there as a suggestion. If there is already one there, the
        user will be responsible for seeing that it is correct.'''

#        import pdb; pdb.set_trace()

        try :
            # Simply try to return anything that is in the field
            cStrPgNo = self.projectConfig['Groups'][self.gid]['startPageNumber']
            if cStrPgNo != '' :
                return cStrPgNo
        except :
            # If nothing is there, we'll make a suggestion
            pGrp = str(self.projectConfig['Groups'][self.gid]['precedingGroup'])
            if pGrp == 'None' :
                self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1
                self.tools.writeConfFile(self.projectConfig)
                return '1'
            else :
                # Calculate the suggested number based on the preceeding group
                try :
                    cStrPgNo    = str(self.projectConfig['Groups'][self.gid]['startPageNumber'])
                except :
                    cStrPgNo    = 1
                    self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1
                try :
                    pGrpPgs     = int(self.projectConfig['Groups'][pGrp]['totalPages'])
                    pGrpStrPgNo = int(self.projectConfig['Groups'][pGrp]['startPageNumber'])
                except :
                    # FIXME: Maybe this could go out and find out exactly how many pages were in the preceeding group
                    pGrpPgs     = 1
                    pGrpStrPgNo = 1
                    self.projectConfig['Groups'][pGrp]['totalPages'] = 1
                    self.projectConfig['Groups'][pGrp]['startPageNumber'] = 1
                # Whether this is right or wrong set it the way it is
                self.projectConfig['Groups'][self.gid]['startPageNumber'] = (pGrpStrPgNo + pGrpPgs)
                self.tools.writeConfFile(self.projectConfig)
                return self.projectConfig['Groups'][pGrp]['startPageNumber']


    def makeExtFile (self, fileName, description) :
        '''Generic function to create an extension file if one does not already exist.'''

        if not os.path.exists(fileName) :
            with codecs.open(fileName, "w", encoding='utf_8') as writeObject :
                writeObject.write(self.tools.makeFileHeader(fileName, description, False))
            self.log.writeToLog(self.errorCodes['1040'], [self.tools.fName(fileName)])
            return True


    def makeCmpExtTexFileOn (self, fileName) :
        '''Create a component TeX extention macro "on" file for a specified component. A matching "off"
        file will be created as well.'''

        description = 'This is a component (on) TeX macro extension file which may override any macros \
        which were loaded for this rendering process. This file is read just before the component \
        working file. After the component is rendered, the accompanying off TeX file will be \
        loaded which will turn off any modified macro commands that this TeX file has set. The \
        user must edit this file in order for it to work right.'

        return self.makeExtFile(fileName, description)


    def makeCmpExtTexFileOff (self, fileName) :
        '''Create a component TeX extention macro "off" file for a specified component. This is to
        match the "on" file that was created.'''

        description = 'This is a component (off) style extension file which overrides the settings \
        that were loaded for this rendering process just prior to loading the component working \
        file. The commands in this style file will off-set the "on" settings causing the macro to \
        render as it did before the "on" styles were loaded. The user must edit this file for it \
        to work properly.'

        return self.makeExtFile(fileName, description)


    def makeCmpExtStyFileOn (self, fileName) :
        '''Create a component style extentions "on" file for a specified component. A matching "off"
        file will be created as well.'''

        description = 'This is a component (on) style extension file which overrides any settings \
        which were loaded for this rendering process. This file is read just before the component \
        working file. After the component is rendered, the accompanying off style file will be \
        loaded which will turn off any modified style commands that this style file has set. The \
        user must edit this file in order for it to work right.'

        return self.makeExtFile(fileName, description)


    def makeCmpExtStyFileOff (self, fileName) :
        '''Create a component style extentions "off" file for a specified component. This is to
        match the "on" file that was created.'''

        description = 'This is a component (off) style extension file which overrides the settings \
        that were loaded for this rendering process just prior to loading the component working \
        file. The commands in this style file will off-set the "on" settings causing the macro to \
        render as it did before the "on" styles were loaded. The user must edit this file for it \
        to work properly.'

        return self.makeExtFile(fileName, description)


    def makeGrpExtTexFile (self) :
        '''Create a group TeX extentions file for a specified group.'''

        description = 'This is the group TeX extention macro file which overrides settings in \
        the global TeX extension macro file.'

        return self.makeExtFile(self.local.grpExtTexFile, description)


    def makeGrpExtStyFile (self) :
        '''Create a group Style extentions file to a specified group.'''

        description = 'This is the group style extention file which overrides settings in \
        the main default component extentions settings style file.'

        return self.makeExtFile(self.local.grpExtStyFile, description)


###############################################################################
############################# DEPENDENCY FUNCTIONS ############################
###############################################################################
######################## Error Code Block Series = 0400 #######################
###############################################################################


    def makeSettingsTexFile (self) :
        '''Create the primary TeX settings file.'''

#        import pdb; pdb.set_trace()

        description = 'This is the primary TeX settings file for the ' + self.gid + ' group. \
        It is auto-generated so editing can be a rather futile exercise. This is unless you \
        set freezeTexSettings to True in the XeTeX manager configuration of the project.conf \
        file. Doing that will prevent the file from being remade. However, no configuration \
        changes will be reflected in the static settings file. Use this with care.'

        # Setting for internal testing
        outputTest = False

        # Check for freezeTexSettings in project.conf
        if self.projectConfig['Managers'][self.cType + '_Xetex'].has_key('freezeTexSettings') and \
                self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex']['freezeTexSettings']) :
            self.log.writeToLog(self.errorCodes['0420'])
            return False

        def appendLine(line, realVal) :
            '''Use this to shorten the code and look for listy things.'''
            if type(line) == list :
                for s in line :
                    linesOut.append(self.proj_config.processNestedPlaceholders(s, realVal))
            else :
                linesOut.append(self.proj_config.processNestedPlaceholders(line, realVal))

        # Open a fresh settings file
        with codecs.open(self.local.macSettingsFile, "w", encoding='utf_8') as writeObject :
            writeObject.write(self.tools.makeFileHeader(self.local.macSettingsFileName, description))
            # Build a dictionary from the default XML settings file
            # Create a dict that contains only the data we need here
            macPackDict = self.tools.xmlFileToDict(self.local.macPackConfXmlFile)

            for sections in macPackDict['root']['section'] :
                for section in sections :
                    secItem = sections[section]
                    linesOut = []
                    if type(secItem) is list :
                        if outputTest :
                            print sections['sectionID']
                        linesOut.append('% ' + sections['sectionID'].upper())
                        for setting in secItem :
                            for k in setting.keys() :
                                if k == 'texCode' :
                                    if outputTest :
                                        print '\t', setting['key']
                                    realVal = self.macroConfig['Macros'][self.macPackId][sections['sectionID']][setting['key']]
                                    # Test any boolDepends that this setting might have
                                    if setting.has_key('boolDepend') :
                                        result = []
                                        if type(setting['boolDepend']) == list :
                                            for i in setting['boolDepend'] :
                                                result.append(self.affirm(i))
                                        else :
                                            result.append(self.affirm(setting['boolDepend']))
                                        # If 'None' didn't end up in the list, that means
                                        # every bool tested good so we can output the line
                                        if None not in result :
                                            if outputTest :
                                                print '\t', setting.get(k)
                                            appendLine(setting['texCode'], realVal)
                                    # Normal setting output
                                    elif setting.get(k) :
                                        if setting.get(k) != None :
                                            # We filter out zero values here (But what if we need one of them?)
                                            if not self.proj_config.processNestedPlaceholders(realVal) == '0' :
                                                if outputTest :
                                                    print '\t', setting.get(k)
                                                appendLine(setting['texCode'], realVal)

                    # Only write out sections that have something in them
                    if len(linesOut) > 1 :
                        writeObject.write('\n')
                        for line in linesOut :
                            writeObject.write(line + '\n')


            # Continue here with injecting the font settings which are guided by
            # the config file because the XML source(s) could vary
            writeObject.write('\n% INSTALLED FONTS\n')
            installedFonts = self.fontConfig['Fonts'].keys()
            cTypeFont = self.projectConfig['CompTypes'][self.cType.capitalize()]['fontName']
            for font in installedFonts :
                if font == cTypeFont :
                    # Output the primary font
                    for key in self.fontConfig['Fonts'][font]['TexMapping']['PrimaryFont'].keys() :
                        writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['PrimaryFont'][key]) + '\n')
                    # Output the seconday settings as well for this font
                    for key in self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'].keys() :
                        writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'][key]) + '\n')
                else :
                    # There can only be one primary font, this is not it
                    for key in self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'].keys() :
                        writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'][key]) + '\n')

                writeObject.write('\n')

            # Die here if testing
            if outputTest :
                self.tools.dieNow()
            # Report finished if not
            return True


    def affirm (self, boolDependInfo) :
        '''Affirm by returning True if the actual bool matches its
        state setting. Returning 'None' will cause a setting to be skipped.'''


        realBool = self.returnConfRefValue(boolDependInfo['#text']).lower()
        if boolDependInfo['@state'].lower() == realBool.lower() :
            return True


    def returnConfRefValue (self, ref) :
        '''Return the value of a given config reference. The ref syntax is
        as follows: [config:configObj|section|key]. This should be able to
        recuse as deep as necessary.'''

#        import pdb; pdb.set_trace()

        ref = ref.lstrip('[').rstrip(']')
        (holderType, holderKey) = ref.split(':', 1)
        if holderType.lower() == 'config' :
            val = holderKey.split('|')
            dct = ['self.' + val[0]]
            val.remove(val[0])
            for i in val :
                i = self.proj_config.processNestedPlaceholders(i, '')
                dct.append('["' + i + '"]')

            return eval(''.join(dct))


    def makeGidTexFile (self, cidList) :
        '''Create the main gid TeX control file.'''

        description = 'This is the group TeX control file. XeTeX will \
            read this file to get all of links to other instructions (macros) \
            needed to render the group, or a component of a group.'

        # Since a render run could contain any number of components
        # in any order, we will remake this file on every run. No need
        # for dependency checking
        if os.path.exists(self.local.gidTexFile) :
            os.remove(self.local.gidTexFile)

        # Create the main TeX settings file (made on every run)
        self.makeSettingsTexFile()

        # Start writing out the gid.tex file. Check/make dependencies as we go.
        # If we fail to make a dependency it will die and report during that process.
        # We bring in each element in the order necessary
        with codecs.open(self.local.gidTexFile, "w", encoding='utf_8') as gidTexObject :
            # Write out the file header
            gidTexObject.write(self.tools.makeFileHeader(self.local.gidTexFileName, description))
            # First bring in the main macro file
            gidTexObject.write('\\input \"' + self.local.primaryMacroFile + '\"\n')
            # Check for a preStyle extension file and load if it is there
            if os.path.exists(self.local.preStyTexExtFile) :
                gidTexObject.write('\\input \"' + self.local.preStyTexExtFile + '\"\n')
            ########
            # FIXME? To avoid problems with the usfmTex marginalverses macro code, we bring
            # in the stylesheets now. Will this cause any problems with other macPacks?
            ########
            # Load style files (default and extention come with the package)
            gidTexObject.write('\\stylesheet{' + self.local.defaultStyFile + '}\n')
            # Load the global style extensions
            gidTexObject.write('\\stylesheet{' + self.local.glbExtStyFile + '}\n')
            # Load the group style extensions (if needed)
            if self.projectConfig['Groups'][self.gid].has_key('useGrpStyOverride') and self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useGrpStyOverride']) :
                self.makeGrpExtStyFile()
                gidTexObject.write('\\stylesheet{' + self.local.grpExtStyFile + '}\n')
            # Load the settings (usfmTex: if marginalverses, load code in this)
            gidTexObject.write('\\input \"' + self.local.macSettingsFile + '\"\n')
            # Load the TeX macro extensions for this macro package
            gidTexObject.write('\\input \"' + self.local.extTexFile + '\"\n')
            # Load the group TeX macro extensions (if needed)
            if self.projectConfig['Groups'][self.gid].has_key('useGrpTexOverride') and self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useGrpTexOverride']) :
                self.makeGrpExtTexFile()
                gidTexObject.write('\\input \"' + self.local.grpExtTexFile + '\"\n')
            # Load hyphenation data if needed
            if self.useHyphenation :
                # This is the main hyphenation settings file, this must be loaded first
                gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphSetTexFile + '\"\n')
                # This is the character definition file for hyphenation, this should be loaded second
                gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphCharTexFile + '\"\n')
                # This is the exception words list (all the hyphenated words), this is loaded last
                gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphExcTexFile + '\"\n')
            # If this is less than a full group render, just go with default pg num (1)
            if cidList == self.projectConfig['Groups'][self.gid]['cidList'] :
                # Check if this setting is there
                startPageNumber = self.checkStartPageNumber()
                if startPageNumber > 1 :
                    gidTexObject.write('\\pageno = ' + str(startPageNumber) + '\n')
            # Insert Document properties and x1a compliant info if needed
            gidTexObject.write(self.makeXoneACompliant())
            # Now add in each of the components
            for cid in cidList :
                # Output files and commands for usfm cType
                if self.cType == 'usfm' :
                    cidSource       = os.path.join(self.local.projComponentFolder, cid, self.project.groups[self.gid].makeFileNameWithExt(cid))
                    cidTexFileOn    = os.path.join(self.local.projTexFolder, self.gid + '-' + cid + '-On-ext.tex')
                    cidTexFileOff   = os.path.join(self.local.projTexFolder, self.gid + '-' + cid + '-Off-ext.tex')
                    cidStyFileOn    = os.path.join(self.local.projStyleFolder, self.gid + '-' + cid + '-On-ext.sty')
                    cidStyFileOff   = os.path.join(self.local.projStyleFolder, self.gid + '-' + cid + '-Off-ext.sty')
                    # Check to see if a TeX macro override is needed
                    if self.projectConfig['Groups'][self.gid].has_key('compTexOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compTexOverrideList'] :
                        self.makeCmpExtTexFileOn(cidTexFileOn)
                        gidTexObject.write('\\input \"' + cidTexFileOn + '\"\n')
                    # Check to see if a style override is needed (if so create "on" file)
                    if self.projectConfig['Groups'][self.gid].has_key('compStyOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compStyOverrideList'] :
                        self.makeCmpExtStyFileOn(cidStyFileOn)
                        gidTexObject.write('\\stylesheet{' + cidStyFileOn + '}\n')
                    # Check for short books add omit statement
                    if self.chapNumOffSingChap and self.cidChapNumDict[cid] == 1 :
                        gidTexObject.write('\\OmitChapterNumbertrue\n') 
                    # Add the working file here
                    gidTexObject.write('\\ptxfile{' + cidSource + '}\n')
                    # Check again for short books turn off omit statement
                    if self.chapNumOffSingChap and self.cidChapNumDict[cid] == 1 :
                        gidTexObject.write('\\OmitChapterNumberfalse\n') 
                    # Check for for style override and add the "Off" style file here
                    if self.projectConfig['Groups'][self.gid].has_key('compStyOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compStyOverrideList'] :
                        self.makeCmpExtStyFileOn(cidStyFileOff)
                        gidTexObject.write('\\stylesheet{' + cidStyFileOff + '}\n')
                    # Check for for TeX macro override and add the "Off" TeX file here
                    if self.projectConfig['Groups'][self.gid].has_key('compTexOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compTexOverrideList'] :
                        self.makeCmpExtTexFileOff(cidTexFileOff)
                        gidTexObject.write('\\input \"' + cidTexFileOff + '\"\n')
                else :
                    self.log.writeToLog(self.errorCodes['0650'], [self.cType])

            # This can only hapen once in the whole process, this marks the end
            gidTexObject.write('\\bye\n')

        return True


    def makeXoneACompliant (self) :
        '''Insert the necessary TeX code into the header to give the
        appearance of being PDF x1-a compliant. If the feature is turned
        off then it will only inject relevant document properties that
        are good to have included no mater what. XeTeX output is x1-a
        for the most part, it just doesn't brag about it. The output
        here mostly works. :-) '''

#        import pdb; pdb.set_trace()
        
        allLines = ''
        # Set some of the vars for the output
        title = self.projectConfig['ProjectInfo']['projectTitle']
        subject = self.projectConfig['ProjectInfo']['projectDescription']
        author = ''.join(self.projectConfig['ProjectInfo']['translators'])
        creator = ''.join(self.projectConfig['ProjectInfo']['typesetters'])
        # I don't think this next bit is not right, what does +7 mean anyway?
        # It works for CDT time anyway, which I thought was -6
        offSet = "+07\'00\'"
        # To get the date stamp right, we strip out all the non-number
        # characters so we are left with: yyyymmddhhmmss
        comp = self.projectConfig['ProjectInfo']['projectCreateDate'].replace('-', '').replace(':', '').replace(' ', '')
        cDate = 'D:' + comp + offSet
        mDate = 'D:' + self.tools.fullFileTimeStamp() + offSet
        lines = [   
                    '\special{pdf:docinfo<<',
                    '/Title(' + title + ')%',
                    '/Subject(' + subject + ')%',
                    '/Author(' + author + ')%',
                    '/Creator(' + creator + ')%',
                    '/CreationDate(' + cDate + ')%',
                    '/ModDate(' + mDate + ')%',
                    '/Producer(XeTeX with Rapuma)%',
                    '/Trapped /False',
                    '/GTS_PDFXVersion(PDF/X-1:2003)%',
                    '/GTS_PDFXConformance(PDF/X-1a:2003)%',
                    '>> }'
                ]
        # Add PDF header declairations for PDF X1-A:2003 compliance (default is True)
        if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['pdfX1a']) :
            icc = os.path.join(self.local.rapumaConfigFolder, 'ps_cmyk.icc')
            # Now create the insert line list
            xtralines =  [   '\special{pdf:fstream @OBJCVR (' + icc + ')}',
                                '\special{pdf:put @OBJCVR <</N 4>>}',
                                '%\special{pdf:close @OBJCVR}',
                                '\special{pdf:docview <<',
                                '/OutputIntents [ <<',
                                '/Type/OutputIndent',
                                '/S/GTS_PDFX',
                                '/OutputCondition (An Unknown print device)',
                                '/OutputConditionIdentifier (Custom)',
                                '/DestOutputProfile @OBJCVR',
                                '/RegistryName (http://www.color.og)',
                                '>> ] >>}'
                        ]
            lines = lines + xtralines

        # Whatever our output, process the lines
        for l in lines : 
            allLines = allLines + l + ' \n'

        return allLines


###############################################################################
################################# Main Function ###############################
###############################################################################
######################## Error Code Block Series = 0600 #######################
###############################################################################

    def run (self, gid, cidList, pgRange, override, save) :
        '''This will check all the dependencies for a group and then
        use XeTeX to render the whole group or a subset of components
        and even a page range in a single component.'''

#        import pdb; pdb.set_trace()

        # There must be a cidList. If one was not passed, default to
        # the group list
        cidListSubFileName      = ''
        saveFile                = ''
        saveFileName            = ''
        if not cidList :
            cidList = self.projectConfig['Groups'][gid]['cidList']
        else :
            # If there is a cidList, create an alternate ouput name.
            # This is so if the file is saved it will have a unique
            # name. the name needs to be ordered by ###-cid-gid.
            # We need to do this sooner than later.
            if len(cidList) > 1 :
                cidListSubFileName = '-'.join(cidList)
            else :
                cid = cidList[0]
                # Add a filler character to the ID
                cnid = "{:0>3}".format(self.cidPtIdDict[cid])
                cidListSubFileName = cnid + '-' + cid

        # Create, if necessary, the gid.tex file
        # First, go through and make/update any dependency files
        self.makeSettingsTexFile()
        # Now make the gid main setting file
        self.makeGidTexFile(cidList)
        # Dynamically create a dependency list for the render process
        # Note: gidTexFile is remade on every run, do not test against that file
        dep = [self.local.extTexFile, self.local.projectConfFile, self.local.layoutConfFile, 
                self.local.macroConfFile, self.local.illustrationConfFile, ]
        # Add component dependency files
        for cid in cidList :
            cidUsfm = self.project.groups[gid].getCidPath(cid)
            cidIlls = self.proj_illustration.getCidPiclistFile(cid)
            for f in [cidUsfm, cidIlls] :
                if os.path.exists(f) :
                    dep.append(f)
            # Treat adjustment file separate
            if self.cType == 'usfm' :
                cidAdj = self.project.groups[gid].getCidAdjPath(cid)
                if os.path.exists(cidAdj) :
                    dep.append(cidAdj)

        # Call the renderer
        # Create the environment that XeTeX will use. This will be temporarily set
        # by subprocess.call() just before XeTeX is run.
        texInputsLine = self.project.local.projHome + ':' \
                        + self.local.projStyleFolder + ':' \
                        + self.local.projTexFolder + ':' \
                        + self.local.projMacPackFolder + ':' \
                        + self.local.projMacroFolder + ':' \
                        + self.local.projGidFolder + ':.'

        # Create the environment dictionary that will be fed into subprocess.call()
        #envDict = dict(os.environ)
        envDict={}
        # These are project environment vars
        envDict['TEXINPUTS'] = texInputsLine
        # These are XeTeX environment vars that are run if the internal (fast) version
        # of XeTeX is being run, which is the default. If runExternalXetex is set to
        # False, the following special environment vars will be run. If set to true,
        # an external version of XeTeX, provided it is installed, will run with its own
        # environment vars set elsewhere
        runExternal = self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex'].get('runExternalXetex', ''))
        if not runExternal :
            envDict['PATH'] = os.path.join(self.local.rapumaXetexFolder, 'bin', 'x86_64-linux')
            envDict['TEXMFCNF'] = os.path.join(self.local.rapumaXetexFolder, 'texmf-local', 'web2c')
            envDict['TEXFORMATS'] = os.path.join(self.local.rapumaXetexFolder, 'texmf-local', 'web2c', 'xetex')
            # To help with debugging the following hook has been added. This is not
            # something the user would ever use. It is only for developer diagnostics.
            # for infomation on what integers can be used refer to this URL:
            # http://www.dcs.ed.ac.uk/home/latex/Informatics/Obsolete/html/kpathsea/kpathsea.html
            debugXetex = self.projectConfig['Managers'][self.cType + '_Xetex'].get('debugKpse', None)
            if debugXetex :
                try :
                    if int(debugXetex) > 0 :
                        envDict['KPATHSEA_DEBUG'] = debugXetex
                        self.log.writeToLog(self.errorCodes['1000'], [str(debugXetex), str(envDict)])
                except :
                    self.log.writeToLog(self.errorCodes['1090'], [debugXetex])
        else :
            envDict.update(os.environ)

        # Create the XeTeX command argument list that subprocess.call() will run with
        # the environment vars we set above
        cmds = ['xetex', '-output-directory=' + self.local.projGidFolder, self.local.gidTexFile]

        # For debugging purposes, output the following DBG message
        if self.projectConfig['Managers'][self.cType + '_Xetex'].has_key('freezeTexSettings') and \
                self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex']['freezeTexSettings']) :
            self.log.writeToLog(self.errorCodes['0620'], [os.getcwd(), str(envDict), " ".join(cmds)])

        # Run the XeTeX and collect the return code for analysis
        try :
            rCode = subprocess.call(cmds, env = envDict)
            # Analyse the return code
            if rCode == int(0) :
                self.log.writeToLog(self.errorCodes['0625'], [self.local.gidTexFileName])
            elif rCode in self.xetexErrorCodes :
                self.log.writeToLog(self.errorCodes['0630'], [self.local.gidTexFileName, self.xetexErrorCodes[rCode], str(rCode)])
            else :
                self.log.writeToLog(self.errorCodes['0635'], [str(rCode)])
        except Exception as e :
            # If subprocess fails it might be because XeTeX did not execute
            # we will try to report back something useful
            self.log.writeToLog(self.errorCodes['0615'], [str(e)])

        # Collect the page count and record in group (Write out at the end of the opp.)
        self.projectConfig['Groups'][gid]['totalPages'] = str(PdfFileReader(open(self.local.gidPdfFile)).getNumPages())
        # Write out any changes made to the project.conf file that happened during this opp.
        self.tools.writeConfFile(self.projectConfig)

        # Pull out pages if requested (use the same file for output)
        if pgRange :
            self.tools.pdftkPullPages(self.local.gidPdfFile, self.local.gidPdfFile, pgRange)

        # The gidPdfFile is the residue of the last render and if approved, can be
        # used for the binding process. In regard to saving and file naming, the
        # gidPdfFile will be copied but never renamed. It must remain intact.

        # If the user wants to save this file or use a custom name, do that now
        if save and not override :
            saveFileName = self.pid + '_' + gid
            if cidListSubFileName :
                saveFileName = saveFileName + '_' + cidListSubFileName
            if pgRange :
                saveFileName = saveFileName + '_pg(' + pgRange + ')'
            # Add date stamp
            saveFileName = saveFileName + '_' + self.tools.ymd()
            # Add render file extention
            saveFileName = saveFileName + '.pdf'
            # Save this to the Deliverable folder (Make sure there is one)
            if not os.path.isdir(self.local.projDeliverableFolder) :
                os.makedirs(self.local.projDeliverableFolder)
            # Final file name and path
            saveFile = os.path.join(self.local.projDeliverableFolder, saveFileName)
            # Copy, no news is good news
            if shutil.copy(self.local.gidPdfFile, saveFile) :
                self.log.writeToLog(self.errorCodes['0730'], [saveFileName])
            else :
                self.log.writeToLog(self.errorCodes['0720'], [saveFileName])            

        # If given, the override file name becomes the file name 
        if override :
            saveFile = override
            # With shutil.copy(), no news is good news
            if shutil.copy(self.local.gidPdfFile, saveFile) :
                self.log.writeToLog(self.errorCodes['0730'], [saveFileName])
            else :
                self.log.writeToLog(self.errorCodes['0720'], [saveFileName])

        # Once we know the file is successfully generated, add a background if defined
        viewFile = ''
        if self.useBackground :
            if saveFile :
                viewFile = self.pg_back.addBackground(saveFile)
            else :
                viewFile = self.pg_back.addBackground(self.local.gidPdfFile)
                
        # Add a timestamp and doc info if requested in addition to background
        if self.useDocInfo :
            if saveFile :
                if os.path.isfile(viewFile) :
                    viewFile = self.pg_back.addDocInfo(viewFile)
                else :
                    viewFile = self.pg_back.addDocInfo(saveFile)
            else :
                if os.path.isfile(viewFile) :
                    viewFile = self.pg_back.addDocInfo(viewFile)
                else :
                    viewFile = self.pg_back.addDocInfo(self.local.gidPdfFile)

        # Add a diagnostic layer to the rendered output. Normally this is
        # not used with a normal background layer
        if self.useDiagnostic :
            if saveFile :
                viewFile = self.fmt_diagnose.addTransparency(saveFile)
            else :
                viewFile = self.fmt_diagnose.addTransparency(self.local.gidPdfFile)

        # To avoid confusion with file names, if this is a saved file,
        # and it has a background, we need to remove the original, non-
        # background file (remembering originals are kept in the group
        # Component folder), then rename the -view version to whatever
        # the saved name should be
        if save or override :
            if os.path.isfile(saveFile) and os.path.isfile(viewFile) :
                # First remove
                os.remove(saveFile)
                # Next rename
                os.rename(viewFile, saveFile)

        ##### Viewing #####
        # First get the right file name to view
        if saveFile :
            # If there was a saveFile, that will be the viewFile
            viewFile = saveFile
        else :
            # The view file in this case is just temporary
            if not os.path.isfile(viewFile) :
                viewFile = self.local.gidPdfFile.replace(gid + '.pdf', gid + '-view.pdf')
                shutil.copy(self.local.gidPdfFile, viewFile)

        # Now view it
        if os.path.isfile(viewFile) :
            if self.pdfViewerCmd :
                # Add the file to the viewer command
                self.pdfViewerCmd.append(viewFile)
                # Run the XeTeX and collect the return code for analysis
                try :
                    subprocess.Popen(self.pdfViewerCmd)
                    return True
                except Exception as e :
                    # If we don't succeed, we should probably quite here
                    self.log.writeToLog(self.errorCodes['1005'], [str(e)])
            else :
                self.log.writeToLog(self.errorCodes['0710'])
        else :
            self.log.writeToLog(self.errorCodes['0700'], [self.tools.fName(viewFile)])
            
        # If we made it this far, return True
        return True
Ejemplo n.º 2
0
class Usfm (Group) :
    '''This class contains information about a type of component 
    used in a type of project.'''

    # Shared values
    xmlConfFile     = 'usfm.xml'

    def __init__(self, project, cfg) :
        super(Usfm, self).__init__(project, cfg)

#        import pdb; pdb.set_trace()

        # Set values for this manager
        self.pid                    = project.projectIDCode
        self.gid                    = project.gid
        self.cType                  = 'usfm'
        self.Ctype                  = self.cType.capitalize()
        self.project                = project
        self.local                  = project.local
        self.tools                  = Tools()
        self.proj_font              = ProjFont(self.pid)
        self.proj_illustration      = ProjIllustration(self.pid, self.gid)
        self.proj_config            = Config(self.pid, self.gid)
        self.proj_config.getProjectConfig()
        self.proj_config.getAdjustmentConfig()
        self.projectConfig          = self.proj_config.projectConfig
        self.adjustmentConfig       = self.proj_config.adjustmentConfig
        self.log                    = project.log
        self.cfg                    = cfg
        self.mType                  = project.projectMediaIDCode
        self.renderer               = project.projectConfig['CompTypes'][self.Ctype]['renderer']
        self.sourceEditor           = project.projectConfig['CompTypes'][self.Ctype]['sourceEditor']
        self.macPackId              = project.projectConfig['CompTypes'][self.Ctype]['macroPackage']
        # Get the comp settings
        self.compSettings           = project.projectConfig['CompTypes'][self.Ctype]
        # Build a tuple of managers this component type needs to use
        self.usfmManagers = ('text', self.renderer)

        # Init the general managers
        for self.mType in self.usfmManagers :
            self.project.createManager(self.mType)

        # Create the internal ref names we use in this module
        self.text                   = self.project.managers[self.cType + '_Text']
        # File names

        # Folder paths
        self.projScriptFolder       = self.local.projScriptFolder
        self.projComponentFolder    = self.local.projComponentFolder
        self.gidFolder              = os.path.join(self.projComponentFolder, self.gid)
        # File names with folder paths
        self.rapumaXmlCompConfig    = os.path.join(self.project.local.rapumaConfigFolder, self.xmlConfFile)



        # Get persistant values from the config if there are any
        newSectionSettings = self.tools.getPersistantSettings(self.projectConfig['CompTypes'][self.Ctype], self.rapumaXmlCompConfig)
        if newSectionSettings != self.projectConfig['CompTypes'][self.Ctype] :
            self.projectConfig['CompTypes'][self.Ctype] = newSectionSettings
        # Set them here
        for k, v in self.compSettings.iteritems() :
            setattr(self, k, v)

        # Module Error Codes
        self.errorCodes     = {

            #'USFM-000' : ['MSG', 'Messages for the USFM module.'],
            #'USFM-005' : ['MSG', 'Unassigned error message ID.'],
            #'USFM-010' : ['ERR', 'Could not process character pair. This error was found: [<<1>>]. Process could not complete. - usfm.pt_tools.getNWFChars()'],
            #'USFM-020' : ['ERR', 'Improper character pair found: [<<1>>].  Process could not complete. - usfm.pt_tools.getNWFChars()'],
            #'USFM-025' : ['WRN', 'No non-word-forming characters were found in the PT settings file. - usfm.pt_tools.getNWFChars()'],
            #'USFM-040' : ['ERR', 'Hyphenation source file not found: [<<1>>]. Process halted!'],
            #'USFM-080' : ['LOG', 'Normalizing Unicode text to the [<<1>>] form.'],
            #'USFM-090' : ['ERR', 'USFM file: [<<1>>] did NOT pass the validation test. Because of an encoding conversion, the terminal output is from the file [<<2>>]. Please only edit [<<1>>].'],
            #'USFM-095' : ['WRN', 'Validation for USFM file: [<<1>>] was turned off.'],
            #'USFM-100' : ['MSG', 'Source file editor [<<1>>] is not recognized by this system. Please double check the name used for the source text editor setting.'],
            #'USFM-110' : ['ERR', 'Source file name could not be built because the Name Form ID for [<<1>>] is missing or incorrect. Double check to see which editor created the source text.'],
            #'USFM-120' : ['ERR', 'Source file: [<<1>>] not found! Cannot copy to project. Process halting now.'],
            #'USFM-130' : ['ERR', 'Failed to complete preprocessing on component [<<1>>]'],
            #'USFM-140' : ['MSG', 'Completed installation on [<<1>>] component working text.'],
            #'USFM-150' : ['ERR', 'Unable to copy [<<1>>] to [<<2>>] - error in text.'],

            '0010' : ['LOG', 'Created the [<<1>>] master adjustment file.'],
            '0220' : ['ERR', 'Cannot find: [<<1>>] working file, unable to complete preprocessing for rendering.'],
            '0230' : ['LOG', 'Created the [<<1>>] component adjustment file.'],
            '0240' : ['LOG', 'Could not find adjustments section for [<<1>>], created place holder setting.'],
            '0245' : ['LOG', 'Could not find adjustments for [<<1>>]. No ajustment file has been output.'],
            '0255' : ['LOG', 'Illustrations not being used. The piclist file has been removed from the [<<1>>] illustrations folder.'],
            '0260' : ['LOG', 'Piclist file for [<<1>>] has been created.'],
            '0265' : ['ERR', 'Failed to create piclist file for [<<1>>]!'],
            '0300' : ['ERR', 'One or more illustration files are missing from the project. Please import these files before continuing.']
        }


###############################################################################
############################ Functions Begin Here #############################
###############################################################################
######################## Error Code Block Series = 0200 #######################
###############################################################################

    def makeFileName(self, cid) :
        '''From what we know, return the full file name.'''

        # FIXME: We default this to "base" but for a diglot implementation
        # this is not going to work because we need to have a second
        # file name. Cross that bridge...

        return cid + '_base'


    def makeFileNameWithExt(self, cid) :
        '''From what we know, return the full file name.'''

        return self.makeFileName(cid) + '.' + self.cType


    def getCidPath (self, cid) :
        '''Return the full path of the cName working text file. This assumes
        the cid is valid.'''

        return os.path.join(self.local.projComponentFolder, cid, self.makeFileNameWithExt(cid))


    def getCidAdjPath (self, cid) :
        '''Return the full path of the cName working text adjustments file. 
        This assumes the cName is valid. Note that all macro packages that have
        a manual adjustment feature must use this naming scheme. The name syntax
        comes from the "mother" macro package which is ptx2pdf.'''

        return os.path.join(self.local.projComponentFolder, cid, self.makeFileNameWithExt(cid) + '.adj')


    def render(self, gid, cidList, pages, override, save) :
        '''Does USFM specific rendering of a USFM component'''

#        import pdb; pdb.set_trace()

        # If the whole group is being rendered, we need to preprocess it
        cids = []
        if not cidList :
            cids = self.projectConfig['Groups'][gid]['cidList']
        else :
            cids = cidList

        # Preprocess all subcomponents (one or more)
        # Stop if it breaks at any point
        for cid in cids :
            if not self.preProcessGroup(gid, [cid]) :
                return False

        # With everything in place we can render the component.
        # Note: We pass the cidList straight through
        self.project.managers['usfm_' + self.renderer.capitalize()].run(gid, cidList, pages, override, save)

        return True


    def preProcessGroup (self, gid, cidList) :
        '''This will prepare a component group for rendering by checking for
        and/or creating any dependents it needs to render properly.'''

#        import pdb; pdb.set_trace()

        # Get some relevant settings
        # FIXME: Note page border has not really been implemented yet.
        # It is different from backgound management
        useIllustrations        = self.tools.str2bool(self.projectConfig['Groups'][gid]['useIllustrations'])
        useManualAdjustments    = self.tools.str2bool(self.projectConfig['Groups'][gid]['useManualAdjustments'])

        # See if the working text is present for each subcomponent in the
        # component and try to install it if it is not
        for cid in cidList :
            cType = self.cfg['cType']
            cidUsfm = self.getCidPath(cid)
            # Test for source here and die if it isn't there
            if not os.path.isfile(cidUsfm) :
                self.log.writeToLog(self.errorCodes['0220'], [cidUsfm], 'usfm.preProcessGroup():0220')
            # Add/manage the dependent files for this cid

# FIXME: Some changes may be needed here to guide creation of adjustment files
            # Component adjustment file
            cidAdjFile = self.getCidAdjPath(cid)
            if useManualAdjustments :
                self.createCompAdjustmentFile(cid)
            else :
                # If no adjustments, remove any exsiting file
                if os.path.isfile(cidAdjFile) :
                    os.remove(cidAdjFile)
            # Component piclist file
            cidPiclistFile = self.proj_illustration.getCidPiclistFile(cid)
            if useIllustrations :
                if self.proj_illustration.hasIllustrations(cid) :
                    # Check for missing illustrations (die here if not found)
                    if self.proj_illustration.missingIllustrations(cid) :
                        self.log.writeToLog(self.errorCodes['0300'])
                    # Create piclist file if not there or if the config has changed
                    if not os.path.isfile(cidPiclistFile) or self.tools.isOlder(cidPiclistFile, self.local.illustrationConfFile) :
                        # Now make a fresh version of the piclist file
                        if self.proj_illustration.createPiclistFile(cid) :
                            self.log.writeToLog(self.errorCodes['0260'], [cid])
                        else :
                            self.log.writeToLog(self.errorCodes['0265'], [cid])
                    else :
                        for f in [self.local.layoutConfFile, self.local.illustrationConfFile] :
                            if self.tools.isOlder(cidPiclistFile, f) or not os.path.isfile(cidPiclistFile) :
                                # Remake the piclist file
                                if self.proj_illustration.createPiclistFile(cid) :
                                    self.log.writeToLog(self.errorCodes['0260'], [cid])
                                else :
                                    self.log.writeToLog(self.errorCodes['0265'], [cid])
                else :
                    # Does not seem to be any illustrations for this cid
                    # clean out any piclist file that might be there
                    if os.path.isfile(cidPiclistFile) :
                        os.remove(cidPiclistFile)
            else :
                # If we are not using illustrations then any existing piclist file will be removed
                if os.path.isfile(cidPiclistFile) :
                    os.remove(cidPiclistFile)
                    self.log.writeToLog(self.errorCodes['0255'], [cid])

        # Any more stuff to run?

        return True

# FIXME: Moved this to xetex.py as that was the only place it was called from
    #def checkStartPageNumber (self) :
        #'''Adjust page number for the current group. The current logic is
        #if there is no number in the startPageNumber setting, we can put
        #one in there as a suggestion. If there is already one there, the
        #user will be responsible for seeing that it is correct.'''

##        import pdb; pdb.set_trace()

        #try :
            ## Simply try to return anything that is in the field
            #cStrPgNo = self.projectConfig['Groups'][self.gid]['startPageNumber']
            #if cStrPgNo != '' :
                #return cStrPgNo
        #except :
            ## If nothing is there, we'll make a suggestion
            #pGrp = str(self.projectConfig['Groups'][self.gid]['precedingGroup'])
            #if pGrp == 'None' :
                #self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1
                #self.tools.writeConfFile(self.projectConfig)
                #return '1'
            #else :
                ## Calculate the suggested number based on the preceeding group
                #try :
                    #cStrPgNo    = str(self.projectConfig['Groups'][self.gid]['startPageNumber'])
                #except :
                    #cStrPgNo    = 1
                    #self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1
                #try :
                    #pGrpPgs     = int(self.projectConfig['Groups'][pGrp]['totalPages'])
                    #pGrpStrPgNo = int(self.projectConfig['Groups'][pGrp]['startPageNumber'])
                #except :
                    ## FIXME: Maybe this could go out and find out exactly how many pages were in the preceeding group
                    #pGrpPgs     = 1
                    #pGrpStrPgNo = 1
                    #self.projectConfig['Groups'][pGrp]['totalPages'] = 1
                    #self.projectConfig['Groups'][pGrp]['startPageNumber'] = 1
                ## Whether this is right or wrong set it the way it is
                #self.projectConfig['Groups'][self.gid]['startPageNumber'] = (pGrpStrPgNo + pGrpPgs)
                #self.tools.writeConfFile(self.projectConfig)
                #return self.projectConfig['Groups'][pGrp]['startPageNumber']


    def createCompAdjustmentFile (self, cid) :
        '''Create an adjustment file for this cid. If entries exsist in
        the adjustment.conf file.'''

        description = 'Auto-generated text adjustments file for: ' + cid + '\n'

#        import pdb; pdb.set_trace()

        # Check for a master adj conf file
        if os.path.exists(self.local.adjustmentConfFile) :
            adjFile = self.getCidAdjPath(cid)
            # Clean up old file if there is one so we can start fresh
            if os.path.exists(adjFile) :
                os.remove(adjFile)
            # Nothing to do if no gid section is found
            if not self.adjustmentConfig.has_key(self.gid) :
                self.tools.buildConfSection(self.adjustmentConfig, self.gid)
            if not self.adjustmentConfig[self.gid].has_key(cid) :
                self.tools.buildConfSection(self.adjustmentConfig[self.gid], cid)
                self.adjustmentConfig[self.gid][cid]['%1.1'] = '1'
                self.tools.writeConfFile(self.adjustmentConfig)
                self.log.writeToLog(self.errorCodes['0240'], [cid])
                return False
            # Sort through commented adjustment lines ()
            if self.adjustmentConfig[self.gid].has_key(cid) :
                c = False
                for k in self.adjustmentConfig[self.gid][cid].keys() :
                    if not re.search(r'%|#', k) :
                        c = True
                if not c :
                    self.log.writeToLog(self.errorCodes['0245'], [cid])
                    return False
            # If we make it this far, create the new adjustment file
            with codecs.open(adjFile, "w", encoding='utf_8') as writeObject :
                writeObject.write(self.tools.makeFileHeader(adjFile, description, True))
                # Output like this: JAS 1.13 +1
                for k, v in self.adjustmentConfig[self.gid][cid].iteritems() :
                    if re.search(r'%|#', k) :
                        continue
                    adj = v
                    if int(v) > 0 : 
                        adj = '+' + str(v)
                    writeObject.write(cid.upper() + ' ' + k + ' ' + adj + '\n')

                self.log.writeToLog(self.errorCodes['0230'], [self.tools.fName(adjFile)])

            return True


    def createProjAdjustmentConfFile (self) :
        '''Create a project master component adjustment file that group component
        ajustment files will be created automatically from. This will run every 
        time preprocess is run but after the first time it will only add a sections
        for new groups or components.'''

        if not os.path.exists(self.adjustmentConfFile) :
            self.adjustmentConfig = ConfigObj(self.adjustmentConfFile, encoding='utf-8')
            self.adjustmentConfig.filename = self.adjustmentConfFile
            self.updateCompAdjustmentConf()
        return True


    def updateCompAdjustmentConf (self) :
        '''Update an adjustmentConfig based on changes in the projectConfig.'''

        for gid in self.projectConfig['Groups'].keys() :
            if gid not in self.adjustmentConfig.keys() :
                self.tools.buildConfSection(self.adjustmentConfig, gid)
            for comp in self.projectConfig['Groups'][gid]['cidList'] :
                if not self.adjustmentConfig[gid].has_key(comp) :
                    self.tools.buildConfSection(self.adjustmentConfig[gid], comp)
                self.adjustmentConfig[gid][comp]['%1.1'] = '1'
        self.tools.writeConfFile(self.adjustmentConfig)
        return True


###############################################################################
######################## USFM Component Text Functions ########################
###############################################################################
######################## Error Code Block Series = 0400 #######################
###############################################################################


    def getComponentType (self, gid) :
        '''Return the cType for a component.'''

#        import pdb; pdb.set_trace()

        try :
            cType = self.projectConfig['Groups'][gid]['cType']
        except Exception as e :
            # If we don't succeed, we should probably quite here
            self.log.writeToLog('COMP-200', ['Key not found ' + str(e)])
            self.tools.dieNow()

        return cType


    def isCompleteComponent (self, gid, cid) :
        '''A two-part test to see if a component has a config entry and a file.'''

        if self.hasCidFile(gid, cid) :
            return True


    def hasUsfmCidInfo (self, cid) :
        '''Return True if this cid is in the PT USFM cid info dictionary.'''

        if cid in self.usfmCidInfo().keys() :
            return True


    def hasCidFile (self, gid, cid) :
        '''Return True or False depending on if a working file exists 
        for a given cName.'''

        cType = self.projectConfig['Groups'][gid]['cType']
        return os.path.isfile(os.path.join(self.local.projComponentFolder, cid, cid + '.' + cType))


    def usfmCidInfo (self) :
        '''Return a dictionary of all valid information about USFMs used in PT. Note
        that a couple special non-standard IDs have been added at the top of the list.'''

    #            ID     Comp Name                               Comp ID                         PT ID  Chps
        return {
                '_z_' : ['USFM InternalCaller',                 'usfm_internal_caller',         '00',   0], 
                'gen' : ['Genesis',                             'genesis',                      '01',  50], 
                'exo' : ['Exodus',                              'exodus',                       '02',  40], 
                'lev' : ['Leviticus',                           'leviticus',                    '03',  27], 
                'num' : ['Numbers',                             'numbers',                      '04',  36], 
                'deu' : ['Deuteronomy',                         'deuteronomy',                  '05',  34], 
                'jos' : ['Joshua',                              'joshua',                       '06',  24], 
                'jdg' : ['Judges',                              'judges',                       '07',  21], 
                'rut' : ['Ruth',                                'ruth',                         '08',   4], 
                '1sa' : ['1 Samuel',                            '1_samuel',                     '09',  31], 
                '2sa' : ['2 Samuel',                            '2_samuel',                     '10',  24], 
                '1ki' : ['1 Kings',                             '1_kings',                      '11',  22], 
                '2ki' : ['2 Kings',                             '2_kings',                      '12',  25], 
                '1ch' : ['1 Chronicles',                        '1_chronicles',                 '13',  29], 
                '2ch' : ['2 Chronicles',                        '2_chronicles',                 '14',  36], 
                'ezr' : ['Ezra',                                'ezra',                         '15',  10], 
                'neh' : ['Nehemiah',                            'nehemiah',                     '16',  13], 
                'est' : ['Esther',                              'esther',                       '17',  10], 
                'job' : ['Job',                                 'job',                          '18',  42], 
                'psa' : ['Psalms',                              'psalms',                       '19', 150], 
                'pro' : ['Proverbs',                            'proverbs',                     '20',  31], 
                'ecc' : ['Ecclesiastes',                        'ecclesiastes',                 '21',  12], 
                'sng' : ['Song of Songs',                       'song_of_songs',                '22',   8], 
                'isa' : ['Isaiah',                              'isaiah',                       '23',  66], 
                'jer' : ['Jeremiah',                            'jeremiah',                     '24',  52], 
                'lam' : ['Lamentations',                        'lamentations',                 '25',   5], 
                'ezk' : ['Ezekiel',                             'ezekiel',                      '26',  48], 
                'dan' : ['Daniel',                              'daniel',                       '27',  12], 
                'hos' : ['Hosea',                               'hosea',                        '28',  14], 
                'jol' : ['Joel',                                'joel',                         '29',   3], 
                'amo' : ['Amos',                                'amos',                         '30',   9], 
                'oba' : ['Obadiah',                             'obadiah',                      '31',   1], 
                'jon' : ['Jonah',                               'jonah',                        '32',   4], 
                'mic' : ['Micah',                               'micah',                        '33',   7], 
                'nam' : ['Nahum',                               'nahum',                        '34',   3], 
                'hab' : ['Habakkuk',                            'habakkuk',                     '35',   3], 
                'zep' : ['Zephaniah',                           'zephaniah',                    '36',   3], 
                'hag' : ['Haggai',                              'haggai',                       '37',   2], 
                'zec' : ['Zechariah',                           'zechariah',                    '38',  14], 
                'mal' : ['Malachi',                             'malachi',                      '39',   4],
                'mat' : ['Matthew',                             'matthew',                      '41',  28], 
                'mrk' : ['Mark',                                'mark',                         '42',  16], 
                'luk' : ['Luke',                                'luke',                         '43',  24], 
                'jhn' : ['John',                                'john',                         '44',  21], 
                'act' : ['Acts',                                'acts',                         '45',  28], 
                'rom' : ['Romans',                              'romans',                       '46',  16], 
                '1co' : ['1 Corinthians',                       '1_corinthians',                '47',  16], 
                '2co' : ['2 Corinthians',                       '2_corinthians',                '48',  13], 
                'gal' : ['Galatians',                           'galatians',                    '49',   6], 
                'eph' : ['Ephesians',                           'ephesians',                    '50',   6], 
                'php' : ['Philippians',                         'philippians',                  '51',   4], 
                'col' : ['Colossians',                          'colossians',                   '52',   4], 
                '1th' : ['1 Thessalonians',                     '1_thessalonians',              '53',   5], 
                '2th' : ['2 Thessalonians',                     '2_thessalonians',              '54',   3], 
                '1ti' : ['1 Timothy',                           '1_timothy',                    '55',   6], 
                '2ti' : ['2 Timothy',                           '2_timothy',                    '56',   4], 
                'tit' : ['Titus',                               'titus',                        '57',   3], 
                'phm' : ['Philemon',                            'philemon',                     '58',   1], 
                'heb' : ['Hebrews',                             'hebrews',                      '59',  13], 
                'jas' : ['James',                               'james',                        '60',   5], 
                '1pe' : ['1 Peter',                             '1_peter',                      '61',   5], 
                '2pe' : ['2 Peter',                             '2_peter',                      '62',   3], 
                '1jn' : ['1 John',                              '1_john',                       '63',   5], 
                '2jn' : ['2 John',                              '2_john',                       '64',   1], 
                '3jn' : ['3 John',                              '3_john',                       '65',   1], 
                'jud' : ['Jude',                                'jude',                         '66',   1], 
                'rev' : ['Revelation',                          'revelation',                   '67',  22], 
                'tob' : ['Tobit',                               'tobit',                        '68', '?'], 
                'jdt' : ['Judith',                              'judith',                       '69', '?'], 
                'esg' : ['Esther',                              'esther',                       '70', '?'], 
                'wis' : ['Wisdom of Solomon',                   'wisdom_of_solomon',            '71', '?'], 
                'sir' : ['Sirach',                              'sirach',                       '72', '?'], 
                'bar' : ['Baruch',                              'baruch',                       '73', '?'], 
                'lje' : ['Letter of Jeremiah',                  'letter_of_jeremiah',           '74', '?'], 
                's3y' : ['Song of the Three Children',          'song_3_children',              '75', '?'], 
                'sus' : ['Susanna',                             'susanna',                      '76', '?'], 
                'bel' : ['Bel and the Dragon',                  'bel_dragon',                   '77', '?'], 
                '1ma' : ['1 Maccabees',                         '1_maccabees',                  '78', '?'], 
                '2ma' : ['2 Maccabees',                         '2_maccabees',                  '79', '?'], 
                '3ma' : ['3 Maccabees',                         '3_maccabees',                  '80', '?'], 
                '4ma' : ['4 Maccabees',                         '4_maccabees',                  '81', '?'], 
                '1es' : ['1 Esdras',                            '1_esdras',                     '82', '?'], 
                '2es' : ['2 Esdras',                            '2_esdras',                     '83', '?'], 
                'man' : ['Prayer of Manasses',                  'prayer_of_manasses',           '84', '?'], 
                'ps2' : ['Psalms 151',                          'psalms_151',                   '85', '?'], 
                'oda' : ['Odae',                                'odae',                         '86', '?'], 
                'pss' : ['Psalms of Solomon',                   'psalms_of_solomon',            '87', '?'], 
                'jsa' : ['Joshua A',                            'joshua_a',                     '88', '?'], 
                'jdb' : ['Joshua B',                            'joshua_b',                     '89', '?'], 
                'tbs' : ['Tobit S',                             'tobit_s',                      '90', '?'], 
                'sst' : ['Susannah (Theodotion)',               'susannah_t',                   '91', '?'], 
                'dnt' : ['Daniel (Theodotion)',                 'daniel_t',                     '92', '?'], 
                'blt' : ['Bel and the Dragon (Theodotion)',     'bel_dragon_t',                 '93', '?'], 
                'frt' : ['Front Matter',                        'front_matter',                 'A0',   0], 
                'int' : ['Introductions',                       'introductions',                'A7',   0], 
                'bak' : ['Back Matter',                         'back_matter',                  'A1',   0], 
                'cnc' : ['Concordance',                         'concordance',                  'A8',   0], 
                'glo' : ['Glossary',                            'glossary',                     'A9',   0], 
                'tdx' : ['Topical Index',                       'topical_index',                'B0',   0], 
                'ndx' : ['Names Index',                         'names_index',                  'B1',   0], 
                'xxa' : ['Extra A',                             'extra_a',                      '94',   0], 
                'xxb' : ['Extra B',                             'extra_b',                      '95',   0], 
                'xxc' : ['Extra C',                             'extra_c',                      '96',   0], 
                'xxd' : ['Extra D',                             'extra_d',                      '97',   0],
                'xxe' : ['Extra E',                             'extra_e',                      '98',   0], 
                'xxf' : ['Extra F',                             'extra_f',                      '99',   0], 
                'xxg' : ['Extra G',                             'extra_g',                      '100',  0], 
                'oth' : ['Other',                               'other',                        'A2',   0], 
                'eza' : ['Apocalypse of Ezra',                  'apocalypse_of_ezra',           'A4', '?'], 
                '5ez' : ['5 Ezra',                              '5_ezra_lp',                    'A5', '?'], 
                '6ez' : ['6 Ezra (Latin Epilogue)',             '6_ezra_lp',                    'A6', '?'], 
                'dag' : ['Daniel Greek',                        'daniel_greek',                 'B2', '?'], 
                'ps3' : ['Psalms 152-155',                      'psalms_152-155',               'B3', '?'], 
                '2ba' : ['2 Baruch (Apocalypse)',               '2_baruch_apocalypse',          'B4', '?'], 
                'lba' : ['Letter of Baruch',                    'letter_of_baruch',             'B5', '?'], 
                'jub' : ['Jubilees',                            'jubilees',                     'B6', '?'], 
                'eno' : ['Enoch',                               'enoch',                        'B7', '?'], 
                '1mq' : ['1 Meqabyan',                          '1_meqabyan',                   'B8', '?'], 
                '2mq' : ['2 Meqabyan',                          '2_meqabyan',                   'B9', '?'], 
                '3mq' : ['3 Meqabyan',                          '3_meqabyan',                   'C0', '?'], 
                'rep' : ['Reproof (Proverbs 25-31)',            'reproof_proverbs_25-31',       'C1', '?'], 
                '4ba' : ['4 Baruch (Rest of Baruch)',           '4_baruch',                     'C2', '?'], 
                'lao' : ['Laodiceans',                          'laodiceans',                   'C3', '?'] 

               }