Repository: translate/translate

Issue:
Qt ts files are not pretty printed the same as Linguist output
_Version: trunk_

If you edit the .ts files in Virtaal they are not completely pretty printed.  All .ts files seem to be using pretty print.  This makes our changes result in large diffs.

e.g. Output from Linguist

``` xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="af">
<context>
    <name>API</name>
    <message>
        <source>Echo Test Service</source>
        <translation type="unfinished">aaaaa</translation>
    </message>
    <message numerus="yes">
        <source>%n contact(s)</source>
        <translation type="unfinished">
            <numerusform>bb</numerusform>
            <numerusform>c</numerusform>
        </translation>
    </message>
</context>
</TS>
```

Same output from Virtaal

``` xml
<!DOCTYPE TS>
<TS version="1.1" language="af">
  <context>
    <name>API</name>
    <message>
      <source>Echo Test Service</source><translation type="unfinished">aaaa</translation></message>
    <message numerus="yes">
      <source>%n contact(s)</source><translation type="unfinished"><numerusform>bb</numerusform><numerusform>c</numerusform></translation></message>
  </context>
</TS>
```



Discussion:
This is hard/impossible to fix nicely. The issue is that spacing in XML is significant in general, even though it might be meaningless in some specific places. The XML stack can't know which ones matter or which ones don't. So we instruct it to pretty print stuff that we create, and it does something reasonable, but for anything that it reads in, it assumes that all spacing is relevant, and preserves them - which is the right thing to do in general, maybe just not for us in this case.

The only way I can think of to fix this is to add "\n\t\t" in the .tail of the source, numerusform and translation. That just seems very messy and unnecessary. Another possibility for improvement is to try and minimise the destruction and recreation of tags, which would probably help in some cases (see the comment in ts2::settarget and the call to .clear() which is the culprit here)

This improved a bit since the bug report (can't find the commit) and I just improved it a small little bit extra with revision 15488.  It doesn't look like the linguist output, but a diffing program that ignores whitespace might give a mostly meaningful comparison. It is definitely better than reported here initially.  I guess we can leave this open, although I don't see much more work in this direction any time soon.


Resolving patch:
diff --git a/translate/storage/ts2.py b/translate/storage/ts2.py
--- a/translate/storage/ts2.py
+++ b/translate/storage/ts2.py
@@ -481,5 +481,11 @@ def nplural(self):
 
     def __str__(self):
         """Converts to a string containing the file's XML."""
-        return etree.tostring(self.document, pretty_print=True,
+        root = self.document.getroot()
+        doctype = self.document.docinfo.doctype
+        # Iterate over empty tags without children and force empty text
+        # This will prevent self-closing tags in pretty_print mode
+        for e in root.xpath("//*[not(./node()) and not(text())]"):
+            e.text = ""
+        return etree.tostring(root, doctype=doctype, pretty_print=True,
                               xml_declaration=True, encoding='utf-8')


Test patch:
diff --git a/translate/storage/test_ts2.py b/translate/storage/test_ts2.py
--- a/translate/storage/test_ts2.py
+++ b/translate/storage/test_ts2.py
@@ -32,6 +32,35 @@
 from translate.storage.placeables.lisa import xml_to_strelem
 
 
+TS_NUMERUS = """<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+    <name>Dialog2</name>
+    <message numerus="yes">
+        <source>%n files</source>
+        <translation type="unfinished">
+            <numerusform></numerusform>
+        </translation>
+    </message>
+    <message id="this_is_some_id" numerus="yes">
+        <source>%n cars</source>
+        <translation type="unfinished">
+            <numerusform></numerusform>
+        </translation>
+    </message>
+    <message>
+        <source>Age: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message id="this_is_another_id">
+        <source>func3</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
+"""
+
 xliffparsers = []
 for attrname in dir(xliff):
     attr = getattr(xliff, attrname)
@@ -216,35 +245,12 @@ def test_merge_with_fuzzies(self):
 
     def test_getid(self):
         """test that getid works well"""
-        tsstr = """<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1">
-<context>
-    <name>Dialog2</name>
-    <message numerus="yes">
-        <source>%n files</source>
-        <translation type="unfinished">
-            <numerusform></numerusform>
-        </translation>
-    </message>
-    <message id="this_is_some_id" numerus="yes">
-        <source>%n cars</source>
-        <translation type="unfinished">
-            <numerusform></numerusform>
-        </translation>
-    </message>
-    <message>
-        <source>Age: %1</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message id="this_is_another_id">
-        <source>func3</source>
-        <translation type="unfinished"></translation>
-    </message>
-</context>
-</TS>"""
-
-        tsfile = ts.tsfile.parsestring(tsstr)
+        tsfile = ts.tsfile.parsestring(TS_NUMERUS)
         assert tsfile.units[0].getid() == "Dialog2%n files"
         assert tsfile.units[1].getid() == "Dialog2\nthis_is_some_id%n cars"
         assert tsfile.units[3].getid() == "Dialog2\nthis_is_another_idfunc3"
+
+    def test_backnforth(self):
+        """test that ts files are read and output properly"""
+        tsfile = ts.tsfile.parsestring(TS_NUMERUS)
+        assert str(tsfile) == TS_NUMERUS