forked from Kitware/ParaView
-
Notifications
You must be signed in to change notification settings - Fork 0
/
XML_translations_header_generator.py
176 lines (156 loc) · 6.39 KB
/
XML_translations_header_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# This script takes as input a Proxy XML file
# and return a cpp header file that Qt Linguist
# tools can itself transform into a Qt translation
# source file.
# For example, this XML file:
"""
<ServerManagerConfiguration>
<ProxyGroup name="example">
<SourceProxy label="Image Data To AMR"
name="ImageDataToAMR">
</SourceProxy>
</ProxyGroup>
</ServerManagerConfiguration>
"""
# would result in the following header:
"""
#include <QtGlobal>
#include <iostream>
#include <string>
static const char *messages[] = {
//: Real source: example.xml:2 - example.xml
QT_TRANSLATE_NOOP("ServerManagerXML", R"(example)"),
//: Real source: example.xml:3 - example
QT_TRANSLATE_NOOP("ServerManagerXML", R"(Image Data To AMR)")
};
"""
import sys
# Forbid the use of C modules so the parser overload can get the correct line numbers
sys.modules['_elementtree'] = None
import xml.etree.ElementTree as ET
import argparse
from collections.abc import Iterable
import os
def translationUnit(file: str, line: int, context: str, content: str) -> str:
if not content:
return ""
content = ' '.join(content.replace('"', '\\"').split())
# line is not used for now as it creates git merge conflict when modifying the translation and ParaView at the same time
res = f"\t//: Real source: {file} - {context}\n"
res += f"\tQT_TRANSLATE_NOOP(\"ServerManagerXML\", R\"({content})\"),\n\n"
return res
def _insertSpace(string: str, index: int) -> str:
return string[:index] + ' ' + string[index:]
# This function MUST have the same behavior as
# vtkSMObject::CreatePrettyLabel()
def createPrettyLabel(label: str) -> str:
"""
Create a proper label from a name.
"""
if label == "":
return ""
label = " " + label + " "
for i in range(0, len(label)):
if label[i - 1].isspace() or label[i + 1].isspace():
continue
if label[i].isupper() and label[i + 1].islower():
label = _insertSpace(label, i)
if label[i].islower() and label[i + 1].isupper():
label = _insertSpace(label, i + 1)
return label[1:-2]
def recursiveStringCrawl(file: str, context: str, group: str, node) -> list:
"""
Recursive function searching for node labels to translate.
Return a list of its translatable attributes (and from its children)
to translate as Qt header format.
"""
res = []
# Identify proxy group
if node.tag == "ProxyGroup":
group = node.attrib["name"]
else:
if "label" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["label"]))
elif "menu_label" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["menu_label"]))
elif "Documentation" in node.tag:
if node.text:
res.append(translationUnit(file, node.line, context, node.text))
if "long_help" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["long_help"]))
if "short_help" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["short_help"]))
elif "Text" in node.tag:
if node.text:
res.append(translationUnit(file, node.line, context, node.text))
if "title" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["title"]))
elif "Property" in node.tag and "name" in node.attrib:
res.append(translationUnit(file, node.line, context, createPrettyLabel(node.attrib["name"])))
elif node.tag.endswith("Proxy") and not group.startswith("internal_") and "name" in node.attrib:
res.append(translationUnit(file, node.line, context, createPrettyLabel(node.attrib["name"])))
elif "ShowInMenu" in node.tag and "category" in node.attrib:
res.append(translationUnit(file, node.line, context, node.attrib["category"]))
if node.tag.endswith("Proxy") and "name" in node.attrib:
context = node.attrib["name"]
for child in node:
res += recursiveStringCrawl(file, context, group, child)
return res
class LineParser(ET.XMLParser):
"""
Overload default XML parser to give nodes a line attribute.
"""
def _start(self, *args, **kwargs):
el = super(self.__class__, self)._start(*args, **kwargs)
el.line = self.parser.CurrentLineNumber
return el
def removePrefix(string: str, prefix: str):
if string.startswith(prefix):
return string[len(prefix):]
return string
def fileParsing(fileIn: str, sourceDir: str) -> str:
"""
Starts recursiveStringCrawl at xml file root.
Returns the string of all labels to translate as
Qt header format.
"""
xmlTree = ET.parse(fileIn, parser=LineParser())
stringsList = recursiveStringCrawl(removePrefix(fileIn, sourceDir), removePrefix(fileIn, sourceDir), "root", xmlTree.getroot())
res = "".join(stringsList)
return res
def filesManager(filesIn: list, fileOut: str, sourceDir: str):
"""
Create Qt header first lines then call
fileParsing for each file and appends them
to the result file.
"""
if not isinstance(filesIn, Iterable):
raise ValueError("Not an array")
res = "#include <QtGlobal>\n#include <iostream>\n#include <string>\n\n"
res += "static const char *messages[] = {\n"
for file in filesIn:
res += fileParsing(file, sourceDir)
# Removing a comma and newline after last element of the "message" list.
res = res[:-2]
res += "\n};\n"
with open(fileOut, 'w', encoding='utf-8') as f:
f.write(res)
def __main__():
"""
Asks for an ouput file and for one or more input files.
"""
parser = argparse.ArgumentParser("Generates C++ headers with XML content to be translated")
parser.add_argument('-o', '--out', help='Result header filename', nargs="?")
parser.add_argument('-s', '--source', help='Client source directory path', nargs=1)
parser.add_argument('inFiles', metavar='N', nargs="+")
args = parser.parse_args()
for i in args.inFiles:
if not os.path.exists(i):
raise ValueError(f"Invalid input file '{i}'")
if args.out is not None and args.source is not None:
filesManager(args.inFiles, args.out, args.source[0])
else:
for i in args.inFiles:
filesManager([i], os.path.splitext(i)[0] + ".h")
if __name__ == "__main__":
__main__()