def __init__( self ):
        """
        Constructor: expects the filepath of the source XML file.
        Loads (and crudely validates the XML file) into an element tree.
        """
        self._filenameBase = 'BibleOrganisationalSystems'

        # These fields are used for parsing the XML
        self._treeTag = 'BibleOrganisationalSystems'
        self._headerTag = 'header'
        self._mainElementTag = 'BibleOrganisationalSystem'

        # These fields are used for automatically checking/validating the XML
        self._compulsoryAttributes = ( 'type', )
        self._optionalAttributes = ()
        self._uniqueAttributes = ()
        self._compulsoryElements = ( 'referenceAbbreviation', 'languageCode', )
        self._optionalElements = ( 'name', 'completionDate', 'publicationDate', 'copyright', 'versificationSystem', 'punctuationSystem', 'bookOrderSystem', 'booksNamesSystem',
                                    'translator', 'publisher', 'derivedFrom', 'usesText', 'includesBooks', 'url', 'comment', )
        self._uniqueElements = ()
        self._allowedMultiple = ( 'name', 'translator', 'derivedFrom', 'usesText', 'url', 'comment', )

        # These are fields that we will fill later
        self.title, self.version, self.date = None, None, None
        self.header, self._XMLtree = None, None
        self.__dataDicts = None

        # Get the data tables that we need for proper checking
        self._ISOLanguages = ISO_639_3_Languages().loadData()
        self._BibleBookOrderSystems = BibleBookOrderSystems().loadData()
        self._BiblePunctuationSystems = BiblePunctuationSystems().loadData()
        self._BibleVersificationSystems = BibleVersificationSystems().loadData()
        self._BibleBooksNamesSystems = BibleBooksNamesSystems().loadData()
    def __init__( self ):
        """
        Constructor: expects the filepath of the source XML file.
        Loads (and crudely validates the XML file) into an element tree.
        """
        self._filenameBase = "BibleOrganizationalSystems"

        # These fields are used for parsing the XML
        self._treeTag = "BibleOrganizationalSystems"
        self._headerTag = "header"
        self._mainElementTag = "BibleOrganizationalSystem"

        # These fields are used for automatically checking/validating the XML
        self._compulsoryAttributes = ( "type", )
        self._optionalAttributes = ()
        self._uniqueAttributes = ()
        self._compulsoryElements = ( "referenceAbbreviation", "languageCode", )
        self._optionalElements = ( "name", "completionDate", "publicationDate", "copyright", "versificationSystem", "punctuationSystem", "bookOrderSystem", "booksNamesSystem",
                                    "translator", "publisher", "derivedFrom", "usesText", "includesBooks", "url", "comment", )
        self._uniqueElements = ()
        self._allowedMultiple = ( "name", "translator", "derivedFrom", "usesText", "url", "comment", )

        # These are fields that we will fill later
        self.title, self.version, self.date = None, None, None
        self.header, self._XMLtree = None, None
        self.__dataDicts = None

        # Get the data tables that we need for proper checking
        self._ISOLanguages = ISO_639_3_Languages().loadData()
        self._BibleBookOrderSystems = BibleBookOrderSystems().loadData()
        self._BiblePunctuationSystems = BiblePunctuationSystems().loadData()
        self._BibleVersificationSystems = BibleVersificationSystems().loadData()
        self._BibleBooksNamesSystems = BibleBooksNamesSystems().loadData()
class BibleOrganizationalSystemsConverter:
    """
    Class for handling and converting BibleOrganizationalSystems.
    """

    def __init__( self ):
        """
        Constructor: expects the filepath of the source XML file.
        Loads (and crudely validates the XML file) into an element tree.
        """
        self._filenameBase = "BibleOrganizationalSystems"

        # These fields are used for parsing the XML
        self._treeTag = "BibleOrganizationalSystems"
        self._headerTag = "header"
        self._mainElementTag = "BibleOrganizationalSystem"

        # These fields are used for automatically checking/validating the XML
        self._compulsoryAttributes = ( "type", )
        self._optionalAttributes = ()
        self._uniqueAttributes = ()
        self._compulsoryElements = ( "referenceAbbreviation", "languageCode", )
        self._optionalElements = ( "name", "completionDate", "publicationDate", "copyright", "versificationSystem", "punctuationSystem", "bookOrderSystem", "booksNamesSystem",
                                    "translator", "publisher", "derivedFrom", "usesText", "includesBooks", "url", "comment", )
        self._uniqueElements = ()
        self._allowedMultiple = ( "name", "translator", "derivedFrom", "usesText", "url", "comment", )

        # These are fields that we will fill later
        self.title, self.version, self.date = None, None, None
        self.header, self._XMLtree = None, None
        self.__dataDicts = None

        # Get the data tables that we need for proper checking
        self._ISOLanguages = ISO_639_3_Languages().loadData()
        self._BibleBookOrderSystems = BibleBookOrderSystems().loadData()
        self._BiblePunctuationSystems = BiblePunctuationSystems().loadData()
        self._BibleVersificationSystems = BibleVersificationSystems().loadData()
        self._BibleBooksNamesSystems = BibleBooksNamesSystems().loadData()
    # end of BibleOrganizationalSystemsConverter.__init__


    def __str__( self ):
        """
        This method returns the string representation of a Bible book code.

        @return: the name of a Bible object formatted as a string
        @rtype: string
        """
        result = ""
        if self.title: result += ('\n' if result else '') + self.title
        if self.version: result += ('\n' if result else '') + "  Version: {}".format( self.version )
        if self.date: result += ('\n' if result else '') + "  Date: {}".format( self.date )
        result += ('\n' if result else '') + "  Number of entries = {}".format( len(self._XMLtree) )
        return result
    # end of BibleOrganizationalSystemsConverter.__str__


    def __len__( self ):
        """ Returns the number of items loaded. """
        return len( self._XMLtree )
    # end of BibleOrganizationalSystemsConverter.__len__


    def loadAndValidate( self, XMLFilepath=None ):
        """
        Loads (and crudely validates the XML file) into an element tree.
            Allows the filepath of the source XML file to be specified, otherwise uses the default.
        """
        if self._XMLtree is None: # We mustn't have already have loaded the data
            if XMLFilepath is None:
                XMLFilepath = os.path.join( os.path.dirname(__file__), "DataFiles", self._filenameBase + ".xml" ) # Relative to module, not cwd

            self._load( XMLFilepath )
            if BibleOrgSysGlobals.strictCheckingFlag:
                self._validate()
        return self
    # end of BibleOrganizationalSystemsConverter.loadAndValidate


    def _load( self, XMLFilepath ):
        """
        Load the source XML file and remove the header from the tree.
        Also, extracts some useful elements from the header element.
        """
        assert( XMLFilepath )
        self.__XMLFilepath = XMLFilepath
        assert( self._XMLtree is None or len(self._XMLtree)==0 ) # Make sure we're not doing this twice

        if BibleOrgSysGlobals.verbosityLevel > 2: print( _("Loading BibleOrganisationalSystems XML file from {!r}...").format( self.__XMLFilepath ) )
        self._XMLtree = ElementTree().parse( self.__XMLFilepath )
        assert( self._XMLtree ) # Fail here if we didn't load anything at all

        if self._XMLtree.tag  == self._treeTag:
            header = self._XMLtree[0]
            if header.tag == self._headerTag:
                self.header = header
                self._XMLtree.remove( header )
                if len(header)>1:
                    logging.info( _("Unexpected elements in header") )
                elif len(header)==0:
                    logging.info( _("Missing work element in header") )
                else:
                    work = header[0]
                    if work.tag == "work":
                        self.version = work.find("version").text
                        self.date = work.find("date").text
                        self.title = work.find("title").text
                    else:
                        logging.warning( _("Missing work element in header") )
            else:
                logging.warning( _("Missing header element (looking for {!r} tag)").format( self._headerTag ) )
        else:
            logging.error( _("Expected to load {!r} but got {!r}").format( self._treeTag, self._XMLtree.tag ) )
    # end of BibleOrganizationalSystemsConverter._load


    def _validate( self ):
        """
        Check/validate the loaded data.
        """
        assert( self._XMLtree )

        uniqueDict = {}
        for elementName in self._uniqueElements: uniqueDict["Element_"+elementName] = []
        for attributeName in self._uniqueAttributes: uniqueDict["Attribute_"+attributeName] = []

        expectedID = 1
        for j,element in enumerate(self._XMLtree):
            if element.tag == self._mainElementTag:
                # Check compulsory attributes on this main element
                for attributeName in self._compulsoryAttributes:
                    attributeValue = element.get( attributeName )
                    if attributeValue is None:
                        logging.error( _("Compulsory {!r} attribute is missing from {} element in record {}").format( attributeName, element.tag, j ) )
                    if not attributeValue:
                        logging.warning( _("Compulsory {!r} attribute is blank on {} element in record {}").format( attributeName, element.tag, j ) )

                # Check optional attributes on this main element
                for attributeName in self._optionalAttributes:
                    attributeValue = element.get( attributeName )
                    if attributeValue is not None:
                        if not attributeValue:
                            logging.warning( _("Optional {!r} attribute is blank on {} element in record {}").format( attributeName, element.tag, j ) )

                # Check for unexpected additional attributes on this main element
                for attributeName in element.keys():
                    attributeValue = element.get( attributeName )
                    if attributeName not in self._compulsoryAttributes and attributeName not in self._optionalAttributes:
                        logging.warning( _("Additional {!r} attribute ({!r}) found on {} element in record {}").format( attributeName, attributeValue, element.tag, j ) )

                # Check the attributes that must contain unique information (in that particular field -- doesn't check across different attributes)
                for attributeName in self._uniqueAttributes:
                    attributeValue = element.get( attributeName )
                    if attributeValue is not None:
                        if attributeValue in uniqueDict["Attribute_"+attributeName]:
                            logging.error( _("Found {!r} data repeated in {!r} field on {} element in record {}").format( attributeValue, attributeName, element.tag, j ) )
                        uniqueDict["Attribute_"+attributeName].append( attributeValue )

                ID = element.find("referenceAbbreviation").text

                # Check compulsory elements
                for elementName in self._compulsoryElements:
                    if element.find( elementName ) is None:
                        logging.error( _("Compulsory {!r} element is missing in record with ID {!r} (record {})").format( elementName, ID, j ) )
                    elif not element.find( elementName ).text:
                        logging.warning( _("Compulsory {!r} element is blank in record with ID {!r} (record {})").format( elementName, ID, j ) )

                # Check optional elements
                for elementName in self._optionalElements:
                    if element.find( elementName ) is not None:
                        if not element.find( elementName ).text:
                            logging.warning( _("Optional {!r} element is blank in record with ID {!r} (record {})").format( elementName, ID, j ) )

                # Check for unexpected additional elements
                for subelement in element:
                    if subelement.tag not in self._compulsoryElements and subelement.tag not in self._optionalElements:
                        logging.warning( _("Additional {!r} element ({!r}) found in record with ID {!r} (record {})").format( subelement.tag, subelement.text, ID, j ) )

                # Check the elements that must contain unique information (in that particular element -- doesn't check across different elements)
                for elementName in self._uniqueElements:
                    if element.find( elementName ) is not None:
                        text = element.find( elementName ).text
                        if text in uniqueDict["Element_"+elementName]:
                            logging.error( _("Found {!r} data repeated in {!r} element in record with ID {!r} (record {})").format( text, elementName, ID, j ) )
                        uniqueDict["Element_"+elementName].append( text )

                # Special checks of particular fields
                if element.find("includesBooks") is not None:
                    bookList = element.find("includesBooks").text.split()
                    for BBB in bookList:
                        if not BibleOrgSysGlobals.BibleBooksCodes.isValidReferenceAbbreviation( BBB ):
                            logging.critical( _("Unrecognized {!r} Bible book code found in 'includesBooks' in record with ID {!r} (record {})").format( BBB, ID, j) )
                        if bookList.count( BBB ) > 1:
                            logging.error( _("Multiple {!r} Bible book codes found in 'includesBooks' in record with ID {!r} (record {})").format( BBB, ID, j) )

            else:
                logging.warning( _("Unexpected element: {} in record {}").format( element.tag, j ) )
    # end of BibleOrganizationalSystemsConverter._validate


    def importDataToPython( self ):
        """
        Loads (and pivots) the data (not including the header) into suitable Python containers to use in a Python program.
        (Of course, you can just use the elementTree in self._XMLtree if you prefer.)
        """
        assert( self._XMLtree )
        if self.__dataDicts: # We've already done an import/restructuring -- no need to repeat it
            return self.__dataDicts

        # We'll create a number of dictionaries with different elements as the key
        dataDict, indexDict, combinedIndexDict = {}, {}, {}
        for element in self._XMLtree:
            bits = {}
            # Get the required information out of the tree for this element
            # Start with the compulsory elements and type attribute
            referenceAbbreviation = element.find('referenceAbbreviation').text
            bits['referenceAbbreviation'] = referenceAbbreviation
            myType = element.get( 'type' )
            bits['type'] = myType
            if myType not in allowedTypes: logging.error( _("Unrecognized {!r} type for {!r} (expected one of {})").format(myType,referenceAbbreviation,allowedTypes) )
            languageCode = element.find('languageCode').text
            if self._ISOLanguages and not self._ISOLanguages.isValidLanguageCode( languageCode ): # Check that we have a valid language code
                if languageCode != '???':
                    logging.error( "Unrecognized {!r} ISO-639-3 language code in {!r} organisational system".format( languageCode, referenceAbbreviation ) )
            bits['languageCode'] = languageCode

            # Now work on the optional elements
            for name in ( 'name', 'publicationDate', 'versificationSystem', 'punctuationSystem', 'bookOrderSystem', 'booksNamesSystem', 'derivedFrom', 'usesText', 'includesBooks' ):
                for nameData in element.findall(name):
                    if name in self._allowedMultiple: # Put multiple entries into a list
                        if name not in bits: bits[name] = [nameData.text]
                        else: bits[name].append( nameData.text )
                    else: # Not allowed multiples
                        if name in bits: logging.error( _("Unexpected multiple {} elements found in {} {}").format(name, referenceAbbreviation, myType) )
                        if name=='includesBooks': # special handling
                            bits['includesBooks'] = nameData.text.split()
                            for BBB in bits['includesBooks']:
                                if not BibleOrgSysGlobals.BibleBooksCodes.isValidReferenceAbbreviation( BBB ):
                                    logging.error( _("Unrecognized {!r} Bible book code found in 'includesBooks' in {} {}").format( BBB, referenceAbbreviation, myType) )
                        else: bits[name] = nameData.text # normal handling

            extension = '_' + myType
            extendedRA = referenceAbbreviation if referenceAbbreviation.endswith(extension) else (referenceAbbreviation + extension)
            dataDict[extendedRA] = bits
            if referenceAbbreviation in indexDict: indexDict[referenceAbbreviation].append( extendedRA )
            else: indexDict[referenceAbbreviation] = [extendedRA]
            if referenceAbbreviation in combinedIndexDict: combinedIndexDict[referenceAbbreviation].append( extendedRA )
            else: combinedIndexDict[referenceAbbreviation] = [extendedRA]
            if extendedRA != referenceAbbreviation:
                #assert( extendedRA not in combinedIndexDict )
                if extendedRA in combinedIndexDict: logging.error( _("Found {} in combinedIndexDict").format( extendedRA ) )
                combinedIndexDict[extendedRA] = [extendedRA]
        assert( len(indexDict) <= len(dataDict) )
        assert( len(combinedIndexDict) >= len(indexDict) )

        if BibleOrgSysGlobals.strictCheckingFlag: # We'll do quite a bit more cross-checking now
            for extendedReferenceAbbreviation,data in dataDict.items():
                #print( extendedReferenceAbbreviation, data )
                systemType = data['type']
                if systemType=='edition':
                    if 'derivedFrom' in data: logging.error( _("{} shouldn't use 'derivedFrom' {!r}").format( extendedReferenceAbbreviation, data['derivedFrom'] ) )
                    if 'usesText' not in data: logging.error( _("{} doesn't specify 'usesText'").format( extendedReferenceAbbreviation ) )
                    else: # have a 'usesText' list
                        for textAbbrev in data['usesText']:
                            if textAbbrev not in indexDict: logging.error( _("{} specifies unknown {!r} text in 'usesText' field").format(extendedReferenceAbbreviation,textAbbrev) )
                            elif len(indexDict[textAbbrev]) > 1: # it could be ambiguous
                                found = 0
                                for thisType in ('revision','translation','original'): # but not 'edition'
                                    usesTextExtended = textAbbrev + '_' + thisType
                                    if usesTextExtended in dataDict:
                                        foundOne = usesTextExtended
                                        found += 1
                                assert( found > 0 )
                                if found==1: # ah, it's not actually ambiguous
                                    if BibleOrgSysGlobals.verbosityLevel > 2: print( _("Adjusted text used for {} from the ambiguous {!r} to the extended name {!r}").format( extendedReferenceAbbreviation, textAbbrev, foundOne ) )
                                    data['usesText'].remove( textAbbrev)
                                    data['usesText'].append( foundOne )
                                else: logging.warning( _("{} specifies ambiguous {!r} (could be {}) texts in 'usesText' field").format(extendedReferenceAbbreviation,textAbbrev,indexDict[textAbbrev]) )
                elif systemType=='revision':
                    if 'derivedFrom' not in data: logging.error( _("{} doesn't specify 'derivedFrom'").format( extendedReferenceAbbreviation ) )
                    else:
                        for df in data['derivedFrom']:
                            if df not in indexDict: logging.error( _("{} specifies unknown {!r} text in 'derivedFrom' field").format(extendedReferenceAbbreviation,df) )
                            elif len(indexDict[df]) > 1: logging.warning( _("{} specifies ambiguous {!r} (could be {}) texts in 'derivedFrom' field").format(extendedReferenceAbbreviation,df,indexDict[df]) )
                elif systemType=='translation':
                    if 'derivedFrom' not in data: logging.warning( _("{} doesn't specify 'derivedFrom'").format( extendedReferenceAbbreviation ) )
                    else:
                        for df in data['derivedFrom']:
                            if df not in indexDict: logging.error( _("{} specifies unknown {!r} text in 'derivedFrom' field").format(extendedReferenceAbbreviation,df) )
                            elif len(indexDict[df]) > 1: logging.warning( _("{} specifies ambiguous {!r} (could be {}) texts in 'derivedFrom' field").format(extendedReferenceAbbreviation,df,indexDict[df]) )
                elif systemType=='original':
                    if 'derivedFrom' in data: logging.error( _("{} shouldn't use 'derivedFrom' {!r}").format( extendedReferenceAbbreviation, data['derivedFrom'] ) )
                if 'versificationSystem' in data and data['versificationSystem'] not in ('None', 'Unknown'):
                    if not self._BibleVersificationSystems.isValidVersificationSystemName( data['versificationSystem'] ):
                        extra = "\n  Available systems are {}".format( self._BibleVersificationSystems.getAvailableVersificationSystemNames()) if BibleOrgSysGlobals.verbosityLevel > 2 else ''
                        logging.error( _("Unknown {!r} versification system name in {}{}").format(data['versificationSystem'],extendedReferenceAbbreviation,extra) )
                if 'punctuationSystem' in data and data['punctuationSystem'] not in ('None', 'Unknown'):
                    if not self._BiblePunctuationSystems.isValidPunctuationSystemName( data['punctuationSystem'] ):
                        extra = "\n  Available systems are {}".format( self._BiblePunctuationSystems.getAvailablePunctuationSystemNames()) if BibleOrgSysGlobals.verbosityLevel > 2 else ''
                        logging.error( _("Unknown {!r} punctuation system name in {}{}").format(data['punctuationSystem'],extendedReferenceAbbreviation,extra) )

        self.__dataDicts = dataDict, indexDict, combinedIndexDict
        return self.__dataDicts
    # end of importDataToPython


    def pickle( self, filepath=None ):
        """
        Writes the information tables to a .pickle file that can be easily loaded into a Python3 program.
        """
        import pickle

        assert( self._XMLtree )
        self.importDataToPython()
        assert( self.__dataDicts )

        if not filepath:
            folder = os.path.join( os.path.split(self.__XMLFilepath)[0], "DerivedFiles/" )
            if not os.path.exists( folder ): os.mkdir( folder )
            filepath = os.path.join( folder, self._filenameBase + "_Tables.pickle" )
        if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting to {}...").format( filepath ) )
        with open( filepath, 'wb' ) as myFile:
            pickle.dump( self.__dataDicts, myFile )
    # end of pickle


    def exportDataToPython( self, filepath=None ):
        """
        Writes the information tables to a .py file that can be cut and pasted into a Python program.
        """
        def exportPythonDict( theFile, theDict, dictName, keyComment, fieldsComment ):
            """Exports theDict to theFile."""
            theFile.write( "{} = {{\n  # Key is {}\n  # Fields are: {}\n".format( dictName, keyComment, fieldsComment ) )
            for dictKey in sorted(theDict.keys()):
                theFile.write( '  {}: {},\n'.format( repr(dictKey), theDict[dictKey] ) )
            theFile.write( "}}\n# end of {}\n\n".format( dictName ) )
        # end of exportPythonDict


        assert( self._XMLtree )
        self.importDataToPython()
        assert( self.__dataDicts )

        if not filepath: filepath = os.path.join( os.path.split(self.__XMLFilepath)[0], "DerivedFiles", self._filenameBase + "_Tables.py" )
        if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting to {}...").format( filepath ) )

        dataDict, indexDict, combinedIndexDict = self.importDataToPython()
        with open( filepath, 'wt' ) as myFile:
            myFile.write( "# {}\n#\n".format( filepath ) )
            myFile.write( "# This UTF-8 file was automatically generated by BibleOrganizationalSystemsConverter.py V{} on {}\n#\n".format( ProgVersion, datetime.now() ) )
            if self.title: myFile.write( "# {}\n".format( self.title ) )
            if self.version: myFile.write( "#  Version: {}\n".format( self.version ) )
            if self.date: myFile.write( "#  Date: {}\n#\n".format( self.date ) )
            myFile.write( "#   {} {} entries loaded from the original XML file.\n".format( len(self._XMLtree), self._treeTag ) )
            #myFile.write( "#   {} {} loaded from the original XML files.\n#\n\n".format( len(self.systems), self._treeTag ) )
            exportPythonDict( myFile, dataDict, "dataDict", "extendedReferenceAbbreviation", "referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)" )
            exportPythonDict( myFile, indexDict, "indexDict", "referenceAbbreviation", "id, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)" )
            exportPythonDict( myFile, combinedIndexDict, "combinedIndexDict", "referenceAbbreviation", "id, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)" )
    # end of exportDataToPython


    def exportDataToJSON( self, filepath=None ):
        """
        Writes the information tables to a .json file that can be easily loaded into a Java program.

        See http://en.wikipedia.org/wiki/JSON.
        """
        import json

        assert( self._XMLtree )
        self.importDataToPython()
        assert( self.__dataDicts )

        if not filepath: filepath = os.path.join( os.path.split(self.__XMLFilepath)[0], "DerivedFiles", self._filenameBase + "_Tables.json" )
        if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting to {}...").format( filepath ) )
        with open( filepath, 'wt' ) as myFile:
            #myFile.write( "# {}\n#\n".format( filepath ) ) # Not sure yet if these comment fields are allowed in JSON
            #myFile.write( "# This UTF-8 file was automatically generated by BibleBooksCodes.py V{} on {}\n#\n".format( ProgVersion, datetime.now() ) )
            #if self.titleString: myFile.write( "# {} data\n".format( self.titleString ) )
            #if self.ProgVersion: myFile.write( "#  Version: {}\n".format( self.ProgVersion ) )
            #if self.dateString: myFile.write( "#  Date: {}\n#\n".format( self.dateString ) )
            #myFile.write( "#   {} {} loaded from the original XML file.\n#\n\n".format( len(self._XMLtree), self._treeTag ) )
            json.dump( self.__dataDicts, myFile, indent=2 )
            #myFile.write( "\n\n# end of {}".format( os.path.basename(filepath) ) )
    # end of exportDataToJSON


    def exportDataToC( self, filepath=None ):
        """
        Writes the information tables to a .h file that can be included in c and c++ programs.
        """
        raise Exception( "C export not written yet" )
        def exportPythonDict( theFile, theDict, dictName, structName, fieldsComment ):
            """Exports theDict to theFile."""
            def convertEntry( entry ):
                """Convert special characters in an entry..."""
                result = ""
                for field in entry:
                    if result: result += ", " # Separate the fields
                    if field is None: result += '""'
                    elif isinstance( field, str): result += '"' + str(field).replace('"','\\"') + '"'
                    elif isinstance( field, int): result += str(field)
                    else: logging.error( _("Cannot convert unknown field type {!r} in entry {!r}").format( field, entry ) )
                return result

            theFile.write( "static struct {} {}[] = {\n  // Fields are {}\n".format( structName, dictName, fieldsComment ) )
            for entry in sorted(theDict.keys()):
                if isinstance( entry, str ):
                    theFile.write( "  {\"{}\", {}},\n".format( entry, convertEntry(theDict[entry]) ) )
                elif isinstance( entry, int ):
                    theFile.write( "  {{}, {}},\n".format( entry, convertEntry(theDict[entry]) ) )
                else:
                    logging.error( _("Can't handle this type of data yet: {}").format( entry ) )
            theFile.write( "}; // {}\n\n".format( dictName) )
        # end of exportPythonDict


        assert( self._XMLtree )
        self.importDataToPython()
        assert( self.__dataDicts )

        if not filepath: filepath = os.path.join( os.path.split(self.__XMLFilepath)[0], "DerivedFiles", self._filenameBase + "_Tables.h" )
        if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting to {}...").format( filepath ) )

        IDDict, RADict, SBLDict, OADict, PADict, PNDict = self.importDataToPython()
        ifdefName = self._filenameBase.upper() + "_Tables_h"
        with open( filepath, 'wt' ) as myFile:
            myFile.write( "// {}\n//\n".format( filepath ) )
            myFile.write( "// This UTF-8 file was automatically generated by BibleOrganizationalSystemsConverter.py V{} on {}\n//\n".format( ProgVersion, datetime.now() ) )
            if self.title: myFile.write( "// {}\n".format( self.title ) )
            if self.version: myFile.write( "//  Version: {}\n".format( self.version ) )
            if self.date: myFile.write( "//  Date: {}\n//\n".format( self.date ) )
            myFile.write( "//   {} {} loaded from the original XML file.\n//\n\n".format( len(self._XMLtree), self._treeTag ) )
            myFile.write( "#ifndef {}\n#define {}\n\n".format( ifdefName, ifdefName ) )
            exportPythonDict( myFile, IDDict, "IDDict", "{int id; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}", "id (sorted), referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)" )
            exportPythonDict( myFile, RADict, "RADict", "{char* refAbbrev; int id; char* SBLAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}", "referenceAbbreviation (sorted), SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)" )
            exportPythonDict( myFile, SBLDict, "SBLDict", "{char* SBLAbbrev; int id; char* refAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}", "SBLAbbreviation (sorted), ReferenceAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)" )
            exportPythonDict( myFile, OADict, "OADict", "{char* OSISAbbrev; int id; char* refAbbrev; char* SBLAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}", "OSISAbbreviation (sorted), ReferenceAbbreviation, SBLAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)" )
            exportPythonDict( myFile, PADict, "PADict", "{char* PTAbbrev; int id; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* PTNum; char* EngName;}", "ParatextAbbreviation (sorted), referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextNumberString, id, nameEnglish (comment only)" )
            exportPythonDict( myFile, PNDict, "PNDict", "{char* PTNum; int id; char* PTAbbrev; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* EngName;}", "ParatextNumberString (sorted), ParatextAbbreviation, referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, id, nameEnglish (comment only)" )
            myFile.write( "#endif // {}\n".format( ifdefName ) )
Exemple #4
0
class BibleOrganizationalSystemsConverter:
    """
    Class for handling and converting BibleOrganizationalSystems.
    """
    def __init__(self):
        """
        Constructor: expects the filepath of the source XML file.
        Loads (and crudely validates the XML file) into an element tree.
        """
        self._filenameBase = "BibleOrganizationalSystems"

        # These fields are used for parsing the XML
        self._treeTag = "BibleOrganizationalSystems"
        self._headerTag = "header"
        self._mainElementTag = "BibleOrganizationalSystem"

        # These fields are used for automatically checking/validating the XML
        self._compulsoryAttributes = ("type", )
        self._optionalAttributes = ()
        self._uniqueAttributes = ()
        self._compulsoryElements = (
            "referenceAbbreviation",
            "languageCode",
        )
        self._optionalElements = (
            "name",
            "completionDate",
            "publicationDate",
            "copyright",
            "versificationSystem",
            "punctuationSystem",
            "bookOrderSystem",
            "booksNamesSystem",
            "translator",
            "publisher",
            "derivedFrom",
            "usesText",
            "includesBooks",
            "url",
            "comment",
        )
        self._uniqueElements = ()
        self._allowedMultiple = (
            "name",
            "translator",
            "derivedFrom",
            "usesText",
            "url",
            "comment",
        )

        # These are fields that we will fill later
        self.title, self.version, self.date = None, None, None
        self.header, self._XMLtree = None, None
        self.__dataDicts = None

        # Get the data tables that we need for proper checking
        self._ISOLanguages = ISO_639_3_Languages().loadData()
        self._BibleBookOrderSystems = BibleBookOrderSystems().loadData()
        self._BiblePunctuationSystems = BiblePunctuationSystems().loadData()
        self._BibleVersificationSystems = BibleVersificationSystems().loadData(
        )
        self._BibleBooksNamesSystems = BibleBooksNamesSystems().loadData()

    # end of BibleOrganizationalSystemsConverter.__init__

    def __str__(self):
        """
        This method returns the string representation of a Bible book code.

        @return: the name of a Bible object formatted as a string
        @rtype: string
        """
        result = ""
        if self.title: result += ('\n' if result else '') + self.title
        if self.version:
            result += ('\n' if result else '') + "  Version: {}".format(
                self.version)
        if self.date:
            result += ('\n' if result else '') + "  Date: {}".format(self.date)
        result += ('\n' if result else '') + "  Number of entries = {}".format(
            len(self._XMLtree))
        return result

    # end of BibleOrganizationalSystemsConverter.__str__

    def __len__(self):
        """ Returns the number of items loaded. """
        return len(self._XMLtree)

    # end of BibleOrganizationalSystemsConverter.__len__

    def loadAndValidate(self, XMLFilepath=None):
        """
        Loads (and crudely validates the XML file) into an element tree.
            Allows the filepath of the source XML file to be specified, otherwise uses the default.
        """
        if self._XMLtree is None:  # We mustn't have already have loaded the data
            if XMLFilepath is None:
                XMLFilepath = os.path.join(
                    os.path.dirname(__file__), "DataFiles",
                    self._filenameBase + ".xml")  # Relative to module, not cwd

            self._load(XMLFilepath)
            if Globals.strictCheckingFlag:
                self._validate()
        return self

    # end of BibleOrganizationalSystemsConverter.loadAndValidate

    def _load(self, XMLFilepath):
        """
        Load the source XML file and remove the header from the tree.
        Also, extracts some useful elements from the header element.
        """
        assert (XMLFilepath)
        self.__XMLFilepath = XMLFilepath
        assert (self._XMLtree is None or len(self._XMLtree) == 0
                )  # Make sure we're not doing this twice

        if Globals.verbosityLevel > 2:
            print(
                _("Loading BibleOrganisationalSystems XML file from '{}'...").
                format(self.__XMLFilepath))
        self._XMLtree = ElementTree().parse(self.__XMLFilepath)
        assert (self._XMLtree)  # Fail here if we didn't load anything at all

        if self._XMLtree.tag == self._treeTag:
            header = self._XMLtree[0]
            if header.tag == self._headerTag:
                self.header = header
                self._XMLtree.remove(header)
                if len(header) > 1:
                    logging.info(_("Unexpected elements in header"))
                elif len(header) == 0:
                    logging.info(_("Missing work element in header"))
                else:
                    work = header[0]
                    if work.tag == "work":
                        self.version = work.find("version").text
                        self.date = work.find("date").text
                        self.title = work.find("title").text
                    else:
                        logging.warning(_("Missing work element in header"))
            else:
                logging.warning(
                    _("Missing header element (looking for '{}' tag)").format(
                        self._headerTag))
        else:
            logging.error(
                _("Expected to load '{}' but got '{}'").format(
                    self._treeTag, self._XMLtree.tag))

    # end of BibleOrganizationalSystemsConverter._load

    def _validate(self):
        """
        Check/validate the loaded data.
        """
        assert (self._XMLtree)

        uniqueDict = {}
        for elementName in self._uniqueElements:
            uniqueDict["Element_" + elementName] = []
        for attributeName in self._uniqueAttributes:
            uniqueDict["Attribute_" + attributeName] = []

        expectedID = 1
        for j, element in enumerate(self._XMLtree):
            if element.tag == self._mainElementTag:
                # Check compulsory attributes on this main element
                for attributeName in self._compulsoryAttributes:
                    attributeValue = element.get(attributeName)
                    if attributeValue is None:
                        logging.error(
                            _("Compulsory '{}' attribute is missing from {} element in record {}"
                              ).format(attributeName, element.tag, j))
                    if not attributeValue:
                        logging.warning(
                            _("Compulsory '{}' attribute is blank on {} element in record {}"
                              ).format(attributeName, element.tag, j))

                # Check optional attributes on this main element
                for attributeName in self._optionalAttributes:
                    attributeValue = element.get(attributeName)
                    if attributeValue is not None:
                        if not attributeValue:
                            logging.warning(
                                _("Optional '{}' attribute is blank on {} element in record {}"
                                  ).format(attributeName, element.tag, j))

                # Check for unexpected additional attributes on this main element
                for attributeName in element.keys():
                    attributeValue = element.get(attributeName)
                    if attributeName not in self._compulsoryAttributes and attributeName not in self._optionalAttributes:
                        logging.warning(
                            _("Additional '{}' attribute ('{}') found on {} element in record {}"
                              ).format(attributeName, attributeValue,
                                       element.tag, j))

                # Check the attributes that must contain unique information (in that particular field -- doesn't check across different attributes)
                for attributeName in self._uniqueAttributes:
                    attributeValue = element.get(attributeName)
                    if attributeValue is not None:
                        if attributeValue in uniqueDict["Attribute_" +
                                                        attributeName]:
                            logging.error(
                                _("Found '{}' data repeated in '{}' field on {} element in record {}"
                                  ).format(attributeValue, attributeName,
                                           element.tag, j))
                        uniqueDict["Attribute_" +
                                   attributeName].append(attributeValue)

                ID = element.find("referenceAbbreviation").text

                # Check compulsory elements
                for elementName in self._compulsoryElements:
                    if element.find(elementName) is None:
                        logging.error(
                            _("Compulsory '{}' element is missing in record with ID '{}' (record {})"
                              ).format(elementName, ID, j))
                    elif not element.find(elementName).text:
                        logging.warning(
                            _("Compulsory '{}' element is blank in record with ID '{}' (record {})"
                              ).format(elementName, ID, j))

                # Check optional elements
                for elementName in self._optionalElements:
                    if element.find(elementName) is not None:
                        if not element.find(elementName).text:
                            logging.warning(
                                _("Optional '{}' element is blank in record with ID '{}' (record {})"
                                  ).format(elementName, ID, j))

                # Check for unexpected additional elements
                for subelement in element:
                    if subelement.tag not in self._compulsoryElements and subelement.tag not in self._optionalElements:
                        logging.warning(
                            _("Additional '{}' element ('{}') found in record with ID '{}' (record {})"
                              ).format(subelement.tag, subelement.text, ID, j))

                # Check the elements that must contain unique information (in that particular element -- doesn't check across different elements)
                for elementName in self._uniqueElements:
                    if element.find(elementName) is not None:
                        text = element.find(elementName).text
                        if text in uniqueDict["Element_" + elementName]:
                            logging.error(
                                _("Found '{}' data repeated in '{}' element in record with ID '{}' (record {})"
                                  ).format(text, elementName, ID, j))
                        uniqueDict["Element_" + elementName].append(text)

                # Special checks of particular fields
                if element.find("includesBooks") is not None:
                    bookList = element.find("includesBooks").text.split()
                    for BBB in bookList:
                        if not Globals.BibleBooksCodes.isValidReferenceAbbreviation(
                                BBB):
                            logging.critical(
                                _("Unrecognized '{}' Bible book code found in 'includesBooks' in record with ID '{}' (record {})"
                                  ).format(BBB, ID, j))
                        if bookList.count(BBB) > 1:
                            logging.error(
                                _("Multiple '{}' Bible book codes found in 'includesBooks' in record with ID '{}' (record {})"
                                  ).format(BBB, ID, j))

            else:
                logging.warning(
                    _("Unexpected element: {} in record {}").format(
                        element.tag, j))

    # end of BibleOrganizationalSystemsConverter._validate

    def importDataToPython(self):
        """
        Loads (and pivots) the data (not including the header) into suitable Python containers to use in a Python program.
        (Of course, you can just use the elementTree in self._XMLtree if you prefer.)
        """
        assert (self._XMLtree)
        if self.__dataDicts:  # We've already done an import/restructuring -- no need to repeat it
            return self.__dataDicts

        # We'll create a number of dictionaries with different elements as the key
        dataDict, indexDict, combinedIndexDict = {}, {}, {}
        for element in self._XMLtree:
            bits = {}
            # Get the required information out of the tree for this element
            # Start with the compulsory elements and type attribute
            referenceAbbreviation = element.find("referenceAbbreviation").text
            bits["referenceAbbreviation"] = referenceAbbreviation
            myType = element.get("type")
            bits["type"] = myType
            if myType not in allowedTypes:
                logging.error(
                    _("Unrecognized '{}' type for '{}' (expected one of {})").
                    format(myType, referenceAbbreviation, allowedTypes))
            languageCode = element.find("languageCode").text
            if self._ISOLanguages and not self._ISOLanguages.isValidLanguageCode(
                    languageCode):  # Check that we have a valid language code
                logging.error(
                    "Unrecognized '{}' ISO-639-3 language code in '{}' organisational system"
                    .format(languageCode, referenceAbbreviation))
            bits["languageCode"] = languageCode

            # Now work on the optional elements
            for name in ("name", "publicationDate", "versificationSystem",
                         "punctuationSystem", "bookOrderSystem",
                         "booksNamesSystem", "derivedFrom", "usesText",
                         "includesBooks"):
                for nameData in element.findall(name):
                    if name in self._allowedMultiple:  # Put multiple entries into a list
                        if name not in bits: bits[name] = [nameData.text]
                        else: bits[name].append(nameData.text)
                    else:  # Not allowed multiples
                        if name in bits:
                            logging.error(
                                _("Unexpected multiple {} elements found in {} {}"
                                  ).format(name, referenceAbbreviation,
                                           myType))
                        if name == "includesBooks":  # special handling
                            bits["includesBooks"] = nameData.text.split()
                            for BBB in bits["includesBooks"]:
                                if not Globals.BibleBooksCodes.isValidReferenceAbbreviation(
                                        BBB):
                                    logging.error(
                                        _("Unrecognized '{}' Bible book code found in 'includesBooks' in {} {}"
                                          ).format(BBB, referenceAbbreviation,
                                                   myType))
                        else:
                            bits[name] = nameData.text  # normal handling

            extension = '_' + myType
            extendedRA = referenceAbbreviation if referenceAbbreviation.endswith(
                extension) else (referenceAbbreviation + extension)
            dataDict[extendedRA] = bits
            if referenceAbbreviation in indexDict:
                indexDict[referenceAbbreviation].append(extendedRA)
            else:
                indexDict[referenceAbbreviation] = [extendedRA]
            if referenceAbbreviation in combinedIndexDict:
                combinedIndexDict[referenceAbbreviation].append(extendedRA)
            else:
                combinedIndexDict[referenceAbbreviation] = [extendedRA]
            if extendedRA != referenceAbbreviation:
                #assert( extendedRA not in combinedIndexDict )
                if extendedRA in combinedIndexDict:
                    logging.error(
                        _("Found {} in combinedIndexDict").format(extendedRA))
                combinedIndexDict[extendedRA] = [extendedRA]
        assert (len(indexDict) <= len(dataDict))
        assert (len(combinedIndexDict) >= len(indexDict))

        if Globals.strictCheckingFlag:  # We'll do quite a bit more cross-checking now
            for extendedReferenceAbbreviation, data in dataDict.items():
                #print( extendedReferenceAbbreviation, data )
                systemType = data['type']
                if systemType == 'edition':
                    if 'derivedFrom' in data:
                        logging.error(
                            _("{} shouldn't use 'derivedFrom' '{}'").format(
                                extendedReferenceAbbreviation,
                                data['derivedFrom']))
                    if 'usesText' not in data:
                        logging.error(
                            _("{} doesn't specify 'usesText'").format(
                                extendedReferenceAbbreviation))
                    else:  # have a 'usesText' list
                        for textAbbrev in data['usesText']:
                            if textAbbrev not in indexDict:
                                logging.error(
                                    _("{} specifies unknown '{}' text in 'usesText' field"
                                      ).format(extendedReferenceAbbreviation,
                                               textAbbrev))
                            elif len(indexDict[textAbbrev]
                                     ) > 1:  # it could be ambiguous
                                found = 0
                                for thisType in (
                                        'revision', 'translation',
                                        'original'):  # but not 'edition'
                                    usesTextExtended = textAbbrev + '_' + thisType
                                    if usesTextExtended in dataDict:
                                        foundOne = usesTextExtended
                                        found += 1
                                assert (found > 0)
                                if found == 1:  # ah, it's not actually ambiguous
                                    if Globals.verbosityLevel > 2:
                                        print(
                                            _("Adjusted text used for {} from the ambiguous '{}' to the extended name '{}'"
                                              ).
                                            format(
                                                extendedReferenceAbbreviation,
                                                textAbbrev, foundOne))
                                    data['usesText'].remove(textAbbrev)
                                    data['usesText'].append(foundOne)
                                else:
                                    logging.warning(
                                        _("{} specifies ambiguous '{}' (could be {}) texts in 'usesText' field"
                                          ).format(
                                              extendedReferenceAbbreviation,
                                              textAbbrev,
                                              indexDict[textAbbrev]))
                elif systemType == 'revision':
                    if 'derivedFrom' not in data:
                        logging.error(
                            _("{} doesn't specify 'derivedFrom'").format(
                                extendedReferenceAbbreviation))
                    else:
                        for df in data['derivedFrom']:
                            if df not in indexDict:
                                logging.error(
                                    _("{} specifies unknown '{}' text in 'derivedFrom' field"
                                      ).format(extendedReferenceAbbreviation,
                                               df))
                            elif len(indexDict[df]) > 1:
                                logging.warning(
                                    _("{} specifies ambiguous '{}' (could be {}) texts in 'derivedFrom' field"
                                      ).format(extendedReferenceAbbreviation,
                                               df, indexDict[df]))
                elif systemType == 'translation':
                    if 'derivedFrom' not in data:
                        logging.warning(
                            _("{} doesn't specify 'derivedFrom'").format(
                                extendedReferenceAbbreviation))
                    else:
                        for df in data['derivedFrom']:
                            if df not in indexDict:
                                logging.error(
                                    _("{} specifies unknown '{}' text in 'derivedFrom' field"
                                      ).format(extendedReferenceAbbreviation,
                                               df))
                            elif len(indexDict[df]) > 1:
                                logging.warning(
                                    _("{} specifies ambiguous '{}' (could be {}) texts in 'derivedFrom' field"
                                      ).format(extendedReferenceAbbreviation,
                                               df, indexDict[df]))
                elif systemType == 'original':
                    if 'derivedFrom' in data:
                        logging.error(
                            _("{} shouldn't use 'derivedFrom' '{}'").format(
                                extendedReferenceAbbreviation,
                                data['derivedFrom']))
                if 'versificationSystem' in data and data[
                        'versificationSystem'] not in ('None', 'Unknown'):
                    if not self._BibleVersificationSystems.isValidVersificationSystemName(
                            data['versificationSystem']):
                        extra = "\n  Available systems are {}".format(
                            self._BibleVersificationSystems.
                            getAvailableVersificationSystemNames(
                            )) if Globals.verbosityLevel > 2 else ''
                        logging.error(
                            _("Unknown '{}' versification system name in {}{}"
                              ).format(data['versificationSystem'],
                                       extendedReferenceAbbreviation, extra))
                if 'punctuationSystem' in data and data[
                        'punctuationSystem'] not in ('None', 'Unknown'):
                    if not self._BiblePunctuationSystems.isValidPunctuationSystemName(
                            data['punctuationSystem']):
                        extra = "\n  Available systems are {}".format(
                            self._BiblePunctuationSystems.
                            getAvailablePunctuationSystemNames(
                            )) if Globals.verbosityLevel > 2 else ''
                        logging.error(
                            _("Unknown '{}' punctuation system name in {}{}").
                            format(data['punctuationSystem'],
                                   extendedReferenceAbbreviation, extra))

        self.__dataDicts = dataDict, indexDict, combinedIndexDict
        return self.__dataDicts

    # end of importDataToPython

    def pickle(self, filepath=None):
        """
        Writes the information tables to a .pickle file that can be easily loaded into a Python3 program.
        """
        import pickle

        assert (self._XMLtree)
        self.importDataToPython()
        assert (self.__dataDicts)

        if not filepath:
            folder = os.path.join(
                os.path.split(self.__XMLFilepath)[0], "DerivedFiles/")
            if not os.path.exists(folder): os.mkdir(folder)
            filepath = os.path.join(folder,
                                    self._filenameBase + "_Tables.pickle")
        if Globals.verbosityLevel > 1:
            print(_("Exporting to {}...").format(filepath))
        with open(filepath, 'wb') as myFile:
            pickle.dump(self.__dataDicts, myFile)

    # end of pickle

    def exportDataToPython(self, filepath=None):
        """
        Writes the information tables to a .py file that can be cut and pasted into a Python program.
        """
        def exportPythonDict(theFile, theDict, dictName, keyComment,
                             fieldsComment):
            """Exports theDict to theFile."""
            theFile.write(
                "{} = {{\n  # Key is {}\n  # Fields are: {}\n".format(
                    dictName, keyComment, fieldsComment))
            for dictKey in sorted(theDict.keys()):
                theFile.write('  {}: {},\n'.format(repr(dictKey),
                                                   theDict[dictKey]))
            theFile.write("}}\n# end of {}\n\n".format(dictName))

        # end of exportPythonDict

        assert (self._XMLtree)
        self.importDataToPython()
        assert (self.__dataDicts)

        if not filepath:
            filepath = os.path.join(
                os.path.split(self.__XMLFilepath)[0], "DerivedFiles",
                self._filenameBase + "_Tables.py")
        if Globals.verbosityLevel > 1:
            print(_("Exporting to {}...").format(filepath))

        dataDict, indexDict, combinedIndexDict = self.importDataToPython()
        with open(filepath, 'wt') as myFile:
            myFile.write("# {}\n#\n".format(filepath))
            myFile.write(
                "# This UTF-8 file was automatically generated by BibleOrganizationalSystemsConverter.py V{} on {}\n#\n"
                .format(ProgVersion, datetime.now()))
            if self.title: myFile.write("# {}\n".format(self.title))
            if self.version:
                myFile.write("#  Version: {}\n".format(self.version))
            if self.date: myFile.write("#  Date: {}\n#\n".format(self.date))
            myFile.write(
                "#   {} {} entries loaded from the original XML file.\n".
                format(len(self._XMLtree), self._treeTag))
            #myFile.write( "#   {} {} loaded from the original XML files.\n#\n\n".format( len(self.systems), self._treeTag ) )
            exportPythonDict(
                myFile, dataDict, "dataDict", "extendedReferenceAbbreviation",
                "referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, indexDict, "indexDict", "referenceAbbreviation",
                "id, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, combinedIndexDict, "combinedIndexDict",
                "referenceAbbreviation",
                "id, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)"
            )

    # end of exportDataToPython

    def exportDataToJSON(self, filepath=None):
        """
        Writes the information tables to a .json file that can be easily loaded into a Java program.

        See http://en.wikipedia.org/wiki/JSON.
        """
        import json

        assert (self._XMLtree)
        self.importDataToPython()
        assert (self.__dataDicts)

        if not filepath:
            filepath = os.path.join(
                os.path.split(self.__XMLFilepath)[0], "DerivedFiles",
                self._filenameBase + "_Tables.json")
        if Globals.verbosityLevel > 1:
            print(_("Exporting to {}...").format(filepath))
        with open(filepath, 'wt') as myFile:
            #myFile.write( "# {}\n#\n".format( filepath ) ) # Not sure yet if these comment fields are allowed in JSON
            #myFile.write( "# This UTF-8 file was automatically generated by BibleBooksCodes.py V{} on {}\n#\n".format( ProgVersion, datetime.now() ) )
            #if self.titleString: myFile.write( "# {} data\n".format( self.titleString ) )
            #if self.ProgVersion: myFile.write( "#  Version: {}\n".format( self.ProgVersion ) )
            #if self.dateString: myFile.write( "#  Date: {}\n#\n".format( self.dateString ) )
            #myFile.write( "#   {} {} loaded from the original XML file.\n#\n\n".format( len(self._XMLtree), self._treeTag ) )
            json.dump(self.__dataDicts, myFile, indent=2)
            #myFile.write( "\n\n# end of {}".format( os.path.basename(filepath) ) )

    # end of exportDataToJSON

    def exportDataToC(self, filepath=None):
        """
        Writes the information tables to a .h file that can be included in c and c++ programs.
        """
        raise Exception("C export not written yet")

        def exportPythonDict(theFile, theDict, dictName, structName,
                             fieldsComment):
            """Exports theDict to theFile."""
            def convertEntry(entry):
                """Convert special characters in an entry..."""
                result = ""
                for field in entry:
                    if result: result += ", "  # Separate the fields
                    if field is None: result += '""'
                    elif isinstance(field, str):
                        result += '"' + str(field).replace('"', '\\"') + '"'
                    elif isinstance(field, int):
                        result += str(field)
                    else:
                        logging.error(
                            _("Cannot convert unknown field type '{}' in entry '{}'"
                              ).format(field, entry))
                return result

            theFile.write(
                "static struct {} {}[] = {\n  // Fields are {}\n".format(
                    structName, dictName, fieldsComment))
            for entry in sorted(theDict.keys()):
                if isinstance(entry, str):
                    theFile.write("  {\"{}\", {}},\n".format(
                        entry, convertEntry(theDict[entry])))
                elif isinstance(entry, int):
                    theFile.write("  {{}, {}},\n".format(
                        entry, convertEntry(theDict[entry])))
                else:
                    logging.error(
                        _("Can't handle this type of data yet: {}").format(
                            entry))
            theFile.write("}; // {}\n\n".format(dictName))

        # end of exportPythonDict

        assert (self._XMLtree)
        self.importDataToPython()
        assert (self.__dataDicts)

        if not filepath:
            filepath = os.path.join(
                os.path.split(self.__XMLFilepath)[0], "DerivedFiles",
                self._filenameBase + "_Tables.h")
        if Globals.verbosityLevel > 1:
            print(_("Exporting to {}...").format(filepath))

        IDDict, RADict, SBLDict, OADict, PADict, PNDict = self.importDataToPython(
        )
        ifdefName = self._filenameBase.upper() + "_Tables_h"
        with open(filepath, 'wt') as myFile:
            myFile.write("// {}\n//\n".format(filepath))
            myFile.write(
                "// This UTF-8 file was automatically generated by BibleOrganizationalSystemsConverter.py V{} on {}\n//\n"
                .format(ProgVersion, datetime.now()))
            if self.title: myFile.write("// {}\n".format(self.title))
            if self.version:
                myFile.write("//  Version: {}\n".format(self.version))
            if self.date: myFile.write("//  Date: {}\n//\n".format(self.date))
            myFile.write(
                "//   {} {} loaded from the original XML file.\n//\n\n".format(
                    len(self._XMLtree), self._treeTag))
            myFile.write("#ifndef {}\n#define {}\n\n".format(
                ifdefName, ifdefName))
            exportPythonDict(
                myFile, IDDict, "IDDict",
                "{int id; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}",
                "id (sorted), referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, RADict, "RADict",
                "{char* refAbbrev; int id; char* SBLAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}",
                "referenceAbbreviation (sorted), SBLAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, SBLDict, "SBLDict",
                "{char* SBLAbbrev; int id; char* refAbbrev; char* OSISAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}",
                "SBLAbbreviation (sorted), ReferenceAbbreviation, OSISAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, OADict, "OADict",
                "{char* OSISAbbrev; int id; char* refAbbrev; char* SBLAbbrev; char* PTAbbrev; char* PTNum; char* EngName;}",
                "OSISAbbreviation (sorted), ReferenceAbbreviation, SBLAbbreviation, ParatextAbbreviation, ParatextNumberString, id, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, PADict, "PADict",
                "{char* PTAbbrev; int id; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* PTNum; char* EngName;}",
                "ParatextAbbreviation (sorted), referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, ParatextNumberString, id, nameEnglish (comment only)"
            )
            exportPythonDict(
                myFile, PNDict, "PNDict",
                "{char* PTNum; int id; char* PTAbbrev; char* refAbbrev; char* SBLAbbrev; char* OSISAbbrev; char* EngName;}",
                "ParatextNumberString (sorted), ParatextAbbreviation, referenceAbbreviation, SBLAbbreviation, OSISAbbreviation, id, nameEnglish (comment only)"
            )
            myFile.write("#endif // {}\n".format(ifdefName))