i18n: Robustify db-seed-i18n.py parsing
[working/Evergreen.git] / build / i18n / scripts / ils_events.py
1 #!/usr/bin/env python
2 # ils_events.py
3 """
4 This class enables translation of Evergreen's ils_events XML file.
5
6 Requires polib from http://polib.googlecode.com
7
8 Source event definitions are structured as follows:
9 <ils_events>
10     <event code='1' textcode='UNKNOWN'>
11         <desc xml:lang="en-US">Placeholder event.  Used for development only</desc>
12      </event>
13 </ils_events>
14
15 This generates an updated file with the following structure:
16 <ils_events>
17     <event code='1' textcode='UNKNOWN'>
18         <desc xml:lang="en-US">Placeholder event.  Used for development only</desc>
19         <desc xml:lang="fr-CA">Exemple - seulement developpement</desc>
20     </event>
21 </ils_events>
22 """
23 # Copyright 2007 Dan Scott <dscott@laurentian.ca>
24 #
25 # This program is free software; you can redistribute it and/or
26 # modify it under the terms of the GNU General Public License
27 # as published by the Free Software Foundation; either version 2
28 # of the License, or (at your option) any later version.
29 #
30 # This program is distributed in the hope that it will be useful,
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33 # GNU General Public License for more details.
34
35 import basel10n
36 import codecs
37 import optparse
38 import polib
39 import re
40 import sys
41 import xml.sax
42 import xml.sax.handler
43
44 class ILSEvents(basel10n.BaseL10N):
45     """
46     This class provides methods for extracting translatable strings from
47     Evergreen's ils_events XML file, generating a translatable POT file,
48     reading translated PO files, and generating an updated ils_events.xml
49     file with the additional language strings.
50     """
51
52     def __init__(self):
53         self.pot = None
54         basel10n.BaseL10N.__init__(self)
55         self.definitions = []
56         self.locale = None
57
58     def get_strings(self, source):
59         """
60         Extracts translatable strings from the //desc[@lang='en-US'] attributes
61         in Evergreen's ils_events.xml file.
62         """
63         self.pothead()
64
65         locator = xml.sax.xmlreader.Locator()
66         parser = xml.sax.make_parser()
67         handler = ILSEventHandler()
68         handler.setDocumentLocator(locator)
69         parser.setContentHandler(handler)
70         parser.parse(source)
71
72         for entry in handler.events:
73             poe = polib.POEntry()
74             poe.occurrences = handler.events[entry]
75             poe.msgid = entry
76             self.pot.append(poe)
77
78     def create_events(self):
79         """
80         Creates an ILS events XML file based on a translated PO file.
81
82         Each PO entry has one or more file comment with the following structure:
83
84         #: numcode.textcode:lineno
85         """
86
87         event = """    <event code='%d' textcode='%s'>
88         <desc xml:lang='%s'>%s</desc>\n    </event>"""
89
90         # We should generate this in a real XML way, rather than faking it
91         # But we'll fake it for now
92         for entry in self.pot:
93             for name in entry.occurrences:
94                 # regex name here
95                 pat = re.compile(r'(\d+)\.(\w+)').match(name[0])
96                 numcode = pat.group(1)
97                 textcode = pat.group(2)
98
99                 if entry.msgstr == '':
100                     # No translation available; use the en-US definition
101                     self.definitions.append(event % (int(numcode), textcode, self.locale, entry.msgid))
102                 else:
103                     self.definitions.append(event % (int(numcode), textcode, self.locale, entry.msgstr))
104
105 class ILSEventHandler(xml.sax.handler.ContentHandler):
106     """
107     Parses an ils_events.xml file to get at event[@code] attributes and
108     the contained desc[@lang='en-US'] elements.
109
110     Generates a list of events and their English descriptions.
111     """
112
113     def __init__(self):
114         xml.sax.handler.ContentHandler.__init__(self)
115         self.events = dict()
116         self.desc = u''
117         self.en_us_flag = False
118         self.numcode = None
119         self.textcode = None
120         self.locator = None
121
122     def setDocumentLocator(self, locator):
123         """
124         Override setDocumentLocator so we can track line numbers
125         """
126         self.locator = locator
127
128     def startElement(self, name, attributes):
129         """
130         Grab the event code attribute value for each class
131         or field element.
132         """
133         if name == 'event':
134             self.numcode = attributes['code']
135             self.textcode = attributes['textcode']
136         if name == 'desc' and attributes['xml:lang'] == 'en-US':
137             self.en_us_flag = True
138
139     def characters(self, content):
140         """
141         Build the ILS event description
142         """
143         if self.en_us_flag is True and content is not None:
144             self.desc = self.desc.strip() + ' ' + content.strip()
145
146     def endElement(self, name):
147         """
148         Generate the event with the closed description
149         """
150         self.desc = self.desc.strip()
151         if name == 'desc' and self.en_us_flag is True:
152             lineno = self.locator.getLineNumber()
153             event = "%d.%s" % (int(self.numcode), self.textcode)
154             if self.events.has_key(self.desc):
155                 self.events[self.desc].append([str(event), lineno])
156             else:
157                 self.events[self.desc] = [[str(event), lineno]]
158
159             # Reset event values
160             self.desc = u''
161             self.en_us_flag = False
162             self.numcode = None
163             self.textcode = None
164
165 def main():
166     """
167     Determine what action to take
168     """
169     opts = optparse.OptionParser()
170     opts.add_option('-p', '--pot', action='store', \
171         help='Create a POT file from the specified ils_events.xml file', \
172         metavar='FILE')
173     opts.add_option('-c', '--create', action='store', \
174         help='Create an ils_events.xml file from a translated PO FILE', \
175         metavar='FILE')
176     opts.add_option('-l', '--locale', action='store', \
177         help='Locale of the ils_events.xml file that will be generated', \
178         metavar='FILE')
179     opts.add_option('-o', '--output', dest='outfile', \
180         help='Write output to FILE (defaults to STDOUT)', metavar='FILE')
181     (options, args) = opts.parse_args()
182
183     pot = ILSEvents()
184
185     # Generate a new POT file from the ils_events.xml file
186     if options.pot:
187         pot.get_strings(options.pot)
188         if options.outfile:
189             pot.savepot(options.outfile)
190         else:
191             sys.stdout.write(pot.pot.__str__())
192
193     # Generate an ils_events.xml file from a PO file
194     elif options.create:
195         if options.locale:
196             pot.locale = options.locale
197         else:
198             opts.error('Must specify an output locale to create an XML file')
199
200         head = """<?xml version="1.0" encoding="utf-8"?>
201 <ils_events>
202         """
203         
204         tail = "</ils_events>"
205
206         pot.loadpo(options.create)
207         pot.create_events()
208         if options.outfile:
209             outfile = codecs.open(options.outfile, encoding='utf-8', mode='w')
210             outfile.write(head)
211             for event in pot.definitions: 
212                 outfile.write(event + "\n")
213             outfile.write(tail)
214         else:
215             print(head)
216             for event in pot.definitions:
217                 print(event)
218             print(tail)
219
220     # No options were recognized - print help and bail
221     else:
222         opts.print_help()
223
224 if __name__ == '__main__':
225     main()