i18n: Robustify db-seed-i18n.py parsing
[working/Evergreen.git] / build / i18n / scripts / fieldmapper.py
1 #!/usr/bin/env python
2 # fieldmapper.py
3 """
4 This class enables translation of Evergreen's fieldmapper IDL XML.
5
6 Requires polib from http://polib.googlecode.com
7 """
8 # Copyright 2007 Dan Scott <dscott@laurentian.ca>
9 #
10 # This program is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU General Public License
12 # as published by the Free Software Foundation; either version 2
13 # of the License, or (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19
20 import basel10n
21 import codecs
22 import optparse
23 import polib
24 import sys
25 import xml.sax
26 import xml.sax.handler
27 import xml.sax.saxutils
28
29 class IDL(basel10n.BaseL10N):
30     """
31     This class provides methods for extracting translatable strings from
32     Evergreen's fieldmapper IDL reporter:label attributes, generating a
33     translatable POT file, reading translated PO files, and generating
34     an updated fm_IDL.xml file with the additional language strings.
35     """
36
37     def __init__(self):
38         self.pot = None
39         basel10n.BaseL10N.__init__(self)
40         self.idl = ''
41         self.definitions = []
42
43     def get_strings(self, source):
44         """
45         Extracts translatable strings from the reporter:label attributes
46         in Evergreen's fieldmapper IDL file.
47         """
48         self.pothead()
49
50         locator = xml.sax.xmlreader.Locator()
51         parser = xml.sax.make_parser()
52         handler = IDLHandler()
53         handler.setDocumentLocator(locator)
54         parser.setContentHandler(handler)
55         parser.parse(source)
56
57         for entity in handler.entities:
58             poe = polib.POEntry()
59             poe.occurrences = handler.entities[entity]
60             poe.msgid = entity
61             self.pot.append(poe)
62         self.idl = handler.entityized
63
64     def create_entity(self):
65         """
66         Creates an entity definition file based on a translated PO file.
67         """
68         entity = '<!ENTITY %s %s>'
69         for entry in self.pot:
70             for name in entry.occurrences:
71                 entdef = xml.sax.saxutils.quoteattr(entry.msgstr)
72                 if entry.msgstr == '':
73                     # No translation available; use the en-US definition
74                     entdef = xml.sax.saxutils.quoteattr(entry.msgid)
75                 self.definitions.append(entity % (name[0], entdef))
76
77 class IDLHandler(xml.sax.handler.ContentHandler):
78     """
79     Parses a fieldmapper IDL file to get at reporter:label and name attributes.
80     Generates a list of entity definitions and their values, as well as an
81     entity-ized version of the fieldmapper IDL.
82     """
83
84     def __init__(self):
85         xml.sax.handler.ContentHandler.__init__(self)
86         self.entities = dict()
87         self.classid = None
88         self.entityized = u"""<?xml version="1.0" encoding="utf-8"?>
89 <!DOCTYPE fieldmapper [
90     <!--#include virtual="/opac/locale/${locale}/fm_IDL.dtd"--> 
91 ]>
92 """
93         self.locator = None
94
95     def setDocumentLocator(self, locator):
96         """
97         Override setDocumentLocator so we can track line numbers
98         """
99         self.locator = locator
100
101     def startElement(self, name, attributes):
102         """
103         Return the reporter:label or name attribute value for each class
104         field, or link element.
105         """
106         entity = None
107         lineno = self.locator.getLineNumber()
108         if name == 'class':
109             self.classid = attributes['id']
110         if attributes.has_key('reporter:label'):
111             if name == 'class':
112                 entity = "%s.%s.label" % (name, self.classid)
113             elif name == 'field':
114                 entity = "%s.%s.%s.label" % (name, self.classid, \
115                     attributes['name'])
116             elif name == 'link':
117                 entity = "%s.%s.%s.label" % (name, self.classid, \
118                     attributes['field'])
119             label = attributes['reporter:label']
120             if not self.entities.has_key(label):
121                 self.entities[label] = [(str(entity), lineno)]
122             else:
123                 self.entities[label].append((str(entity), lineno))
124
125         # Now we'll render an entity-ized version of this element
126         element = "<%s" % (name)
127         for att in attributes.keys():
128             # Replace reporter:label attribute values with entities
129             if att == 'reporter:label':
130                 element = element + " %s='&%s;'" % (att, entity) 
131             else:
132                 element = element + " %s='%s'" % (att, attributes[att])
133
134         # field and link elements are empty elements
135         if name == 'field' or name == 'link':
136             element = element + " />"
137         else:
138             element = element + ">"
139         self.entityized = self.entityized + element
140
141     def characters(self, content):
142         """
143         Shove character data into the entityized IDL file
144         """
145         self.entityized = self.entityized + xml.sax.saxutils.escape(content)
146
147     def endElement(self, name):
148         """
149         field and link elements are empty elements
150         """
151         if name == 'field' or name == 'link':
152             pass
153         else:
154             self.entityized = self.entityized + "</%s>" % (name)
155
156 def main():
157     """
158     Determine what action to take
159     """
160     opts = optparse.OptionParser()
161     opts.add_option('-p', '--pot', action='store', \
162         help='Create a POT file from the specified fieldmapper IDL file', \
163         metavar='FILE')
164     opts.add_option('-c', '--convert', action='store', \
165         help='Create a fieldmapper FILE that uses entities instead of text ' \
166         'strings for field labels and names', metavar='FILE')
167     opts.add_option('-e', '--entity', action='store', \
168         help='Create an entity definition from a translated PO FILE', \
169         metavar='FILE')
170     opts.add_option('-o', '--output', dest='outfile', \
171         help='Write output to FILE (defaults to STDOUT)', metavar='FILE')
172     (options, args) = opts.parse_args()
173
174     pot = IDL()
175     # Generate a new POT file from the fieldmapper IDL
176     if options.pot:
177         pot.get_strings(options.pot)
178         if options.outfile:
179             pot.savepot(options.outfile)
180         else:
181             sys.stdout.write(pot.pot.__str__())
182     # Generate an entity file from a PO file
183     elif options.entity:
184         pot.loadpo(options.entity)
185         pot.create_entity()
186         if options.outfile:
187             outfile = codecs.open(options.outfile, encoding='utf-8', mode='w')
188             for entity in pot.definitions: 
189                 outfile.write(entity + "\n")
190         else:
191             for entity in pot.definitions:
192                 print(entity)
193     # Generate an entity-ized fieldmapper IDL file
194     elif options.convert:
195         pot.get_strings(options.convert)
196         if options.outfile:
197             outfile = codecs.open(options.outfile, encoding='utf-8', mode='w')
198             outfile.write(pot.idl)
199         else:
200             sys.stdout.write(pot.idl)
201     # No options were recognized - print help and bail
202     else:
203         opts.print_help()
204
205 if __name__ == '__main__':
206     main()