home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / bin / xml2po < prev    next >
Encoding:
Text File  |  2007-04-09  |  28.0 KB  |  884 lines

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