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