1#!/usr/bin/env python
2# Copyright (c) 2004 Danilo Segan <danilo@kvota.net>.
3#
4# This file is part of xml2po.
5#
6# xml2po is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# xml2po is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with xml2po; if not, write to the Free Software Foundation, Inc.,
18# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19#
20
21# xml2po -- translate XML documents
22VERSION = "0.5.2"
23
24# Versioning system (I use this for a long time, so lets explain it to
25# those Linux-versioning-scheme addicts):
26#   1.0.* are unstable, development versions
27#   1.1 will be first stable release (release 1), and 1.1.* bugfix releases
28#   2.0.* will be unstable-feature-development stage (milestone 1)
29#   2.1.* unstable development betas (milestone 2)
30#   2.2 second stable release (release 2), and 2.2.* bugfix releases
31#   ...
32#
33import sys
34import libxml2
35import gettext
36import os
37import re
38
39class MessageOutput:
40    def __init__(self, with_translations = 0):
41        self.messages = []
42        self.comments = {}
43        self.linenos = {}
44        self.nowrap = {}
45        if with_translations:
46            self.translations = []
47        self.do_translations = with_translations
48        self.output_msgstr = 0 # this is msgid mode for outputMessage; 1 is for msgstr mode
49
50    def translationsFollow(self):
51        """Indicate that what follows are translations."""
52        self.output_msgstr = 1
53
54    def setFilename(self, filename):
55        self.filename = filename
56
57    def outputMessage(self, text, lineno = 0, comment = None, spacepreserve = 0, tag = None):
58        """Adds a string to the list of messages."""
59        if (text.strip() != ''):
60            t = escapePoString(normalizeString(text, not spacepreserve))
61            if self.output_msgstr:
62                self.translations.append(t)
63                return
64
65            if self.do_translations or (not t in self.messages):
66                self.messages.append(t)
67                if spacepreserve:
68                    self.nowrap[t] = 1
69                if t in self.linenos.keys():
70                    self.linenos[t].append((self.filename, tag, lineno))
71                else:
72                    self.linenos[t] = [ (self.filename, tag, lineno) ]
73                if (not self.do_translations) and comment and not t in self.comments:
74                    self.comments[t] = comment
75            else:
76                if t in self.linenos.keys():
77                    self.linenos[t].append((self.filename, tag, lineno))
78                else:
79                    self.linenos[t] = [ (self.filename, tag, lineno) ]
80                if comment and not t in self.comments:
81                    self.comments[t] = comment
82
83    def outputHeader(self, out):
84        import time
85        out.write("""msgid ""
86msgstr ""
87"Project-Id-Version: PACKAGE VERSION\\n"
88"POT-Creation-Date: %s\\n"
89"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
90"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
91"Language-Team: LANGUAGE <LL@li.org>\\n"
92"MIME-Version: 1.0\\n"
93"Content-Type: text/plain; charset=UTF-8\\n"
94"Content-Transfer-Encoding: 8bit\\n"
95
96""" % (time.strftime("%Y-%m-%d %H:%M%z")))
97
98    def outputAll(self, out):
99        self.outputHeader(out)
100
101        for k in self.messages:
102            if k in self.comments:
103                out.write("#. %s\n" % (self.comments[k].replace("\n","\n#. ")))
104            references = ""
105            for reference in self.linenos[k]:
106                references += "%s:%d(%s) " % (reference[0], reference[2], reference[1])
107            out.write("#: %s\n" % (references))
108            if k in self.nowrap and self.nowrap[k]:
109                out.write("#, no-wrap\n")
110            out.write("msgid \"%s\"\n" % (k))
111            translation = ""
112            if self.do_translations:
113                if len(self.translations)>0:
114                    translation = self.translations.pop(0)
115            out.write("msgstr \"%s\"\n\n" % (translation))
116
117
118def normalizeNode(node):
119    if not node:
120        return
121    elif isSpacePreserveNode(node):
122        return
123    elif node.isText():
124        if node.isBlankNode():
125            node.setContent('')
126        else:
127            node.setContent(re.sub('\s+',' ', node.content))
128
129    elif node.children and node.type == 'element':
130        child = node.children
131        while child:
132            normalizeNode(child)
133            child = child.next
134
135def normalizeString(text, ignorewhitespace = 1):
136    """Normalizes string to be used as key for gettext lookup.
137
138    Removes all unnecessary whitespace."""
139    if not ignorewhitespace:
140        return text
141    try:
142        # Lets add document DTD so entities are resolved
143        dtd = doc.intSubset()
144        tmp = dtd.serialize('utf-8')
145        tmp = tmp + '<norm>%s</norm>' % text
146    except:
147        tmp = '<norm>%s</norm>' % text
148
149    try:
150        ctxt = libxml2.createDocParserCtxt(tmp)
151        if expand_entities:
152            ctxt.replaceEntities(1)
153        ctxt.parseDocument()
154        tree = ctxt.doc()
155        newnode = tree.getRootElement()
156    except:
157        print >> sys.stderr, """Error while normalizing string as XML:\n"%s"\n""" % (text)
158        return text
159
160    normalizeNode(newnode)
161
162    result = ''
163    child = newnode.children
164    while child:
165        result += child.serialize('utf-8')
166        child = child.next
167
168    result = re.sub('^ ','', result)
169    result = re.sub(' $','', result)
170
171    return result
172
173def stringForEntity(node):
174    """Replaces entities in the node."""
175    text = node.serialize('utf-8')
176    try:
177        # Lets add document DTD so entities are resolved
178        dtd = node.doc.intSubset()
179        tmp = dtd.serialize('utf-8') + '<norm>%s</norm>' % text
180        next = 1
181    except:
182        tmp = '<norm>%s</norm>' % text
183        next = 0
184
185    ctxt = libxml2.createDocParserCtxt(tmp)
186    if expand_entities:
187        ctxt.replaceEntities(1)
188    ctxt.parseDocument()
189    tree = ctxt.doc()
190    if next:
191        newnode = tree.children.next
192    else:
193        newnode = tree.children
194
195    result = ''
196    child = newnode.children
197    while child:
198        result += child.serialize('utf-8')
199        child = child.next
200
201    return result
202
203
204def escapePoString(text):
205    return text.replace('\\','\\\\').replace('"', "\\\"").replace("\n","\\n").replace("\t","\\t")
206
207def unEscapePoString(text):
208    return text.replace('\\"', '"').replace('\\\\','\\')
209
210def getTranslation(text, spacepreserve = 0):
211    """Returns a translation via gettext for specified snippet.
212
213    text should be a string to look for, spacepreserve set to 1
214    when spaces should be preserved.
215    """
216    text = normalizeString(text, not spacepreserve)
217    if (text.strip() == ''):
218        return text
219    if gt:
220        return gt.ugettext(text.decode('utf-8'))
221    return text
222
223def startTagForNode(node):
224    if not node:
225        return 0
226
227    result = node.name
228    params = ''
229    if node.properties:
230        for p in node.properties:
231            if p.type == 'attribute':
232                # FIXME: This part sucks
233                params += p.serialize('utf-8')
234    return result+params
235
236def endTagForNode(node):
237    if not node:
238        return 0
239
240    result = node.name
241    return result
242
243def isFinalNode(node):
244    if automatic:
245        auto = autoNodeIsFinal(node)
246        # Check if any of the parents is also autoNodeIsFinal,
247        # and if it is, don't consider this node a final one
248        parent = node.parent
249        while parent and auto:
250            auto = not autoNodeIsFinal(parent)
251            parent = parent.parent
252        return auto
253    #node.type =='text' or not node.children or
254    if node.type == 'element' and node.name in ultimate_tags:
255        return 1
256    elif node.children:
257        final_children = 1
258        child = node.children
259        while child and final_children:
260            if not isFinalNode(child):
261                final_children = 0
262            child = child.next
263        if final_children:
264            return 1
265    return 0
266
267def isSwallowNode(node):
268    if not node.name:
269        return 0
270    if node.name in swallow_tags:
271        return 1
272    if node.parent and node.parent.name and ((node.parent.name + '/' + node.name) in swallow_tags):
273        return 1
274    return 0
275
276def ignoreNode(node):
277    if automatic:
278        if node.type in ('dtd', 'comment', 'cdata'):
279            return 1
280        else:
281            return 0
282    else:
283        if isFinalNode(node):
284            return 0
285        if isSwallowNode(node):
286            return 0
287        if node.name in ignored_tags or node.type in ('dtd', 'comment', 'cdata'):
288            return 1
289        return 0
290
291def isSpacePreserveNode(node):
292    pres = node.getSpacePreserve()
293    if pres == 1:
294        return 1
295    else:
296        if CurrentXmlMode and (node.name in CurrentXmlMode.getSpacePreserveTags()):
297            return 1
298        else:
299            return 0
300
301def getCommentForNode(node):
302    """Walk through previous siblings until a comment is found, or other element.
303
304    Only whitespace is allowed between comment and current node."""
305    prev = node.prev
306    while prev and prev.type == 'text' and prev.content.strip() == '':
307        prev = prev.prev
308    if prev and prev.type == 'comment':
309        return prev.content.strip()
310    else:
311        return None
312
313
314def replaceNodeContentsWithText(node,text):
315    """Replaces all subnodes of a node with contents of text treated as XML."""
316    if node.children:
317        starttag = node.name #startTagForNode(node)
318        endtag = endTagForNode(node)
319        try:
320            # Lets add document DTD so entities are resolved
321            dtd = doc.intSubset()
322            tmp = ''
323            if expand_entities: # FIXME: we get a "Segmentation fault" in libxml2.parseMemory() when we include DTD otherwise
324                tmp = dtd.serialize('utf-8')
325            tmp = tmp + '<%s>%s</%s>' % (starttag, text, endtag)
326        except:
327            tmp = '<%s>%s</%s>' % (starttag, text, endtag)
328
329        try:
330            ctxt = libxml2.createDocParserCtxt(tmp.encode('utf-8'))
331            ctxt.replaceEntities(0)
332            ctxt.parseDocument()
333            newnode = ctxt.doc()
334        except:
335            print >> sys.stderr, """Error while parsing translation as XML:\n"%s"\n""" % (text.encode('utf-8'))
336            return
337
338        newelem = newnode.getRootElement()
339        if newelem and newelem.children:
340            free = node.children
341            while free:
342                next = free.next
343                free.unlinkNode()
344                free = next
345
346            node.addChildList(newelem.children)
347        else:
348            # In practice, this happens with tags such as "<para>    </para>" (only whitespace in between)
349            pass
350    else:
351        node.setContent(text)
352
353def autoNodeIsFinal(node):
354    """Returns 1 if node is text node, contains non-whitespace text nodes or entities."""
355    final = 0
356    if node.isText() and node.content.strip()!='':
357        return 1
358    child = node.children
359    while child:
360        if child.type in ['text'] and  child.content.strip()!='':
361            final = 1
362            break
363        child = child.next
364
365    return final
366
367
368def worthOutputting(node):
369    """Returns 1 if node is "worth outputting", otherwise 0.
370
371    Node is "worth outputting", if none of the parents
372    isFinalNode, and it contains non-blank text and entities.
373    """
374    worth = 1
375    parent = node.parent
376    final = isFinalNode(node) and node.name not in ignored_tags
377    while not final and parent:
378        if isFinalNode(parent):
379            final = 1 # reset if we've got to one final tag
380        if final and (parent.name not in ignored_tags) and worthOutputting(parent):
381            worth = 0
382            break
383        parent = parent.parent
384    #if node.name == 'funclink' and worth:
385        #print "wah?" + str(isFinalNode(node)) + " " + str(node.name not in ignored_tags) + " " + str(worth)
386    if not worth:
387        return 0
388
389    return autoNodeIsFinal(node)
390
391def processElementTag(node, replacements, restart = 0):
392    """Process node with node.type == 'element'."""
393    if node.type == 'element':
394        outtxt = ''
395        if restart:
396            myrepl = []
397        else:
398            myrepl = replacements
399
400        submsgs = []
401
402        child = node.children
403        while child:
404            if (isFinalNode(child)) or (child.type == 'element' and worthOutputting(child)):
405                myrepl.append(processElementTag(child, myrepl, 1))
406                outtxt += '<placeholder-%d/>' % (len(myrepl))
407            else:
408                if child.type == 'element':
409                    (starttag, content, endtag, translation) = processElementTag(child, myrepl, 0)
410                    outtxt += '<%s>%s</%s>' % (starttag, content, endtag)
411                else:
412                    outtxt += doSerialize(child)
413
414            child = child.next
415
416        if mode == 'merge':
417            if isSwallowNode(node):
418                translation = outtxt.decode('utf-8')
419            else:
420                translation = getTranslation(outtxt, isSpacePreserveNode(node))
421        else:
422            translation = outtxt
423        starttag = startTagForNode(node)
424        endtag = endTagForNode(node)
425
426        if restart or worthOutputting(node):
427            i = 0
428            while i < len(myrepl):
429                replacement = '<%s>%s</%s>' % (myrepl[i][0], myrepl[i][3], myrepl[i][2])
430                i += 1
431                translation = translation.replace('<placeholder-%d/>' % (i), replacement)
432
433            if worthOutputting(node):
434                if mode == 'merge':
435                    replaceNodeContentsWithText(node, translation)
436                elif not isSwallowNode(node):
437                    msg.outputMessage(outtxt, node.lineNo(), getCommentForNode(node), isSpacePreserveNode(node), tag = node.name)
438
439        return (starttag, outtxt, endtag, translation)
440    else:
441        raise Exception("You must pass node with node.type=='element'.")
442
443
444def isExternalGeneralParsedEntity(node):
445    if (node and node.type=='entity_ref'):
446        try:
447            # it would be nice if debugDumpNode could use StringIO, but it apparently cannot
448            tmp = file(".xml2po-entitychecking","w+")
449            node.debugDumpNode(tmp,0)
450            tmp.seek(0)
451            tmpstr = tmp.read()
452            tmp.close()
453            os.remove(".xml2po-entitychecking")
454        except:
455            # We fail silently, and replace all entities if we cannot
456            # write .xml2po-entitychecking
457            # !!! This is not very nice thing to do, but I don't know if
458            #     raising an exception is any better
459            return 0
460        if tmpstr.find('EXTERNAL_GENERAL_PARSED_ENTITY') != -1:
461            return 1
462        else:
463            return 0
464    else:
465        return 0
466
467def doSerialize(node):
468    """Serializes a node and its children, emitting PO messages along the way.
469
470    node is the node to serialize, first indicates whether surrounding
471    tags should be emitted as well.
472    """
473
474    if ignoreNode(node):
475        return ''
476    elif not node.children:
477        return node.serialize("utf-8")
478    elif node.type == 'entity_ref':
479        if isExternalGeneralParsedEntity(node):
480            return node.serialize('utf-8')
481        else:
482            return stringForEntity(node) #content #content #serialize("utf-8")
483    elif node.type == 'entity_decl':
484        return node.serialize('utf-8') #'<%s>%s</%s>' % (startTagForNode(node), node.content, node.name)
485    elif node.type == 'text':
486        return node.serialize('utf-8')
487    elif node.type == 'element':
488        repl = []
489        (starttag, content, endtag, translation) = processElementTag(node, repl, 1)
490        return '<%s>%s</%s>' % (starttag, content, endtag)
491    else:
492        child = node.children
493        outtxt = ''
494        while child:
495            outtxt += doSerialize(child)
496            child = child.next
497        return outtxt
498
499
500def read_finaltags(filelist):
501    if CurrentXmlMode:
502        return CurrentXmlMode.getFinalTags()
503    else:
504        defaults = ['para', 'title', 'releaseinfo', 'revnumber',
505                    'date', 'itemizedlist', 'orderedlist',
506                    'variablelist', 'varlistentry', 'term' ]
507        return defaults
508
509def read_ignoredtags(filelist):
510    if CurrentXmlMode:
511        return CurrentXmlMode.getIgnoredTags()
512    else:
513        defaults = ['itemizedlist', 'orderedlist', 'variablelist',
514                    'varlistentry' ]
515        return defaults
516
517def read_swallowtags():
518    if CurrentXmlMode and CurrentXmlMode.getSwallowTags:
519        return CurrentXmlMode.getSwallowTags()
520    else:
521        defaults = [ ]
522        return defaults
523
524def tryToUpdate(allargs, lang):
525    # Remove "-u" and "--update-translation"
526    command = allargs[0]
527    args = allargs[1:]
528    opts, args = getopt.getopt(args, 'avhmket:o:p:u:',
529                               ['automatic-tags','version', 'help', 'keep-entities', 'extract-all-entities', 'merge', 'translation=',
530                                'output=', 'po-file=', 'update-translation=' ])
531    for opt, arg in opts:
532        if opt in ('-a', '--automatic-tags'):
533            command += " -a"
534        elif opt in ('-k', '--keep-entities'):
535            command += " -k"
536        elif opt in ('-e', '--extract-all-entities'):
537            command += " -e"
538        elif opt in ('-m', '--mode'):
539            command += " -m %s" % arg
540        elif opt in ('-o', '--output'):
541            sys.stderr.write("Error: Option '-o' is not yet supported when updating translations directly.\n")
542            sys.exit(8)
543        elif opt in ('-v', '--version'):
544            print VERSION
545            sys.exit(0)
546        elif opt in ('-h', '--help'):
547            sys.stderr.write("Error: If you want help, please use `%s --help' without '-u' option.\n" % (allargs[0]))
548            sys.exit(9)
549        elif opt in ('-u', '--update-translation'):
550            pass
551        else:
552            sys.stderr.write("Error: Option `%s' is not supported with option `-u'.\n" % (opt))
553            sys.exit(9)
554
555    while args:
556        command += " " + args.pop()
557
558    file = lang
559
560    sys.stderr.write("Merging translations for %s: " % (lang))
561    result = os.system("%s | msgmerge -o .tmp.%s.po %s -" % (command, lang, file))
562    if result:
563        sys.exit(10)
564    else:
565        result = os.system("mv .tmp.%s.po %s" % (lang, file))
566        if result:
567            sys.stderr.write("Error: cannot rename file.\n")
568            sys.exit(11)
569        else:
570            os.system("msgfmt -cv -o %s %s" % (NULL_STRING, file))
571            sys.exit(0)
572
573def load_mode(modename):
574    #import imp
575    #found = imp.find_module(modename, submodes_path)
576    #module = imp.load_module(modename, found[0], found[1], found[2])
577    sys.path.append(submodes_path)
578    module = __import__(modename)
579    modeModule = '%sXmlMode' % modename
580    return getattr(module, modeModule)
581
582def xml_error_handler(arg, ctxt):
583    pass
584
585#libxml2.registerErrorHandler(xml_error_handler, None)
586
587
588# Main program start
589if __name__ != '__main__': raise NotImplementedError
590
591# Parameters
592submodes_path = "/opt/gnome/share/xml2po"
593default_mode = 'docbook'
594
595filename = ''
596origxml = ''
597mofile = ''
598ultimate = [ ]
599ignored = [ ]
600filenames = [ ]
601translationlanguage = ''
602
603mode = 'pot' # 'pot' or 'merge'
604automatic = 0
605expand_entities = 1
606expand_all_entities = 0
607
608output  = '-' # this means to stdout
609
610NULL_STRING = '/dev/null'
611if not os.path.exists('/dev/null'): NULL_STRING = 'NUL'
612
613import getopt, fileinput
614
615def usage (with_help = False):
616        print >> sys.stderr, "Usage:  %s [OPTIONS] [XMLFILE]..." % (sys.argv[0])
617	if (with_help):
618        	print >> sys.stderr, """
619OPTIONS may be some of:
620    -a    --automatic-tags     Automatically decides if tags are to be considered
621                                 "final" or not
622    -k    --keep-entities      Don't expand entities
623    -e    --expand-all-entities  Expand ALL entities (including SYSTEM ones)
624    -m    --mode=TYPE          Treat tags as type TYPE (default: docbook)
625    -o    --output=FILE        Print resulting text (XML or POT) to FILE
626    -p    --po-file=FILE       Specify PO file containing translation, and merge
627                                 Overwrites temporary file .xml2po.mo.
628    -r    --reuse=FILE         Specify translated XML file with the same structure
629    -t    --translation=FILE   Specify MO file containing translation, and merge
630    -u    --update-translation=LANG.po   Updates a PO file using msgmerge program
631    -l    --language=LANG      Set language of the translation to LANG
632    -v    --version            Output version of the xml2po program
633
634    -h    --help               Output this message
635
636EXAMPLES:
637    To create a POTemplate book.pot from input files chapter1.xml and
638    chapter2.xml, run the following:
639        %s -o book.pot chapter1.xml chapter2.xml
640
641    After translating book.pot into de.po, merge the translations back,
642    using -p option for each XML file:
643        %s -p de.po chapter1.xml > chapter1.de.xml
644        %s -p de.po chapter2.xml > chapter2.de.xml
645""" % (sys.argv[0], sys.argv[0], sys.argv[0])
646        sys.exit(0)
647
648if len(sys.argv) < 2: usage()
649
650args = sys.argv[1:]
651try: opts, args = getopt.getopt(args, 'avhkem:t:o:p:u:r:l:',
652                           ['automatic-tags','version', 'help', 'keep-entities', 'expand-all-entities', 'mode=', 'translation=',
653                            'output=', 'po-file=', 'update-translation=', 'reuse=', 'language=' ])
654except getopt.GetoptError: usage(True)
655
656for opt, arg in opts:
657    if opt in ('-m', '--mode'):
658        default_mode = arg
659    if opt in ('-a', '--automatic-tags'):
660        automatic = 1
661    elif opt in ('-k', '--keep-entities'):
662        expand_entities = 0
663    elif opt in ('-e', '--expand-all-entities'):
664        expand_all_entities = 1
665    elif opt in ('-l', '--language'):
666        translationlanguage = arg
667    elif opt in ('-t', '--translation'):
668        mofile = arg
669        mode = 'merge'
670        if translationlanguage == '': translationlanguage = os.path.split(os.path.splitext(mofile)[0])[1]
671    elif opt in ('-r', '--reuse'):
672        origxml = arg
673    elif opt in ('-u', '--update-translation'):
674        tryToUpdate(sys.argv, arg)
675    elif opt in ('-p', '--po-file'):
676        mofile = ".xml2po.mo"
677        pofile = arg
678        if translationlanguage == '': translationlanguage = os.path.split(os.path.splitext(pofile)[0])[1]
679        os.system("msgfmt -o %s %s >%s" % (mofile, pofile, NULL_STRING)) and sys.exit(7)
680        mode = 'merge'
681    elif opt in ('-o', '--output'):
682        output = arg
683    elif opt in ('-v', '--version'):
684        print VERSION
685        sys.exit(0)
686    elif opt in ('-h', '--help'):
687    	usage(True)
688
689# Treat remaining arguments as XML files
690while args:
691    filenames.append(args.pop())
692
693if len(filenames) > 1 and mode=='merge':
694    print  >> sys.stderr, "Error: You can merge translations with only one XML file at a time."
695    sys.exit(2)
696
697try:
698    CurrentXmlMode = load_mode(default_mode)()
699except:
700    CurrentXmlMode = None
701    print >> sys.stderr, "Warning: cannot load module '%s', using automatic detection (-a)." % (default_mode)
702    automatic = 1
703
704if mode=='merge' and mofile=='':
705    print >> sys.stderr, "Error: You must specify MO file when merging translations."
706    sys.exit(3)
707
708if mode=='merge':
709    openedfile = open(mofile, "rb")
710    if openedfile:
711        gt = gettext.GNUTranslations(openedfile)
712
713ultimate_tags = read_finaltags(ultimate)
714ignored_tags = read_ignoredtags(ignored)
715swallow_tags = read_swallowtags()
716
717# I'm not particularly happy about making any of these global,
718# but I don't want to bother too much with it right now
719semitrans = {}
720PlaceHolder = 0
721if origxml == '':
722    msg = MessageOutput()
723else:
724    filenames.append(origxml)
725    msg = MessageOutput(1)
726
727for filename in filenames:
728    try:
729        if filename == origxml:
730            msg.translationsFollow()
731        ctxt = libxml2.createFileParserCtxt(filename)
732        ctxt.lineNumbers(1)
733        if expand_all_entities:
734            ctxt.replaceEntities(1)
735        ctxt.parseDocument()
736        doc = ctxt.doc()
737        if doc.name != filename:
738            print >> sys.stderr, "Error: I tried to open '%s' but got '%s' -- how did that happen?" % (filename, doc.name)
739            sys.exit(4)
740    except:
741        print >> sys.stderr, "Error: cannot open file '%s'." % (filename)
742        sys.exit(1)
743
744    msg.setFilename(filename)
745    if CurrentXmlMode and origxml=='':
746        CurrentXmlMode.preProcessXml(doc,msg)
747    doSerialize(doc)
748
749if output == '-':
750    out = sys.stdout
751else:
752    try:
753        out = file(output, 'w')
754    except:
755        print >> sys.stderr, "Error: cannot open file %s for writing." % (output)
756        sys.exit(5)
757
758if mode != 'merge':
759    if CurrentXmlMode:
760        tcmsg = CurrentXmlMode.getStringForTranslators()
761        tccom = CurrentXmlMode.getCommentForTranslators()
762        if tcmsg:
763            msg.outputMessage(tcmsg, 0, tccom)
764
765    msg.outputAll(out)
766else:
767    if CurrentXmlMode:
768        tcmsg = CurrentXmlMode.getStringForTranslators()
769        if tcmsg:
770            outtxt = getTranslation(tcmsg)
771        else:
772            outtxt = ''
773        CurrentXmlMode.postProcessXmlTranslation(doc, translationlanguage, outtxt)
774    out.write(doc.serialize('utf-8', 1))
775