home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / bin / xml2po < prev    next >
Encoding:
Text File  |  2006-08-07  |  26.4 KB  |  828 lines

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