1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import lxml.etree as etree
24
25 from translate.storage import base
26
27 from translate.misc.typecheck import accepts, IsCallable
28 from translate.misc.typecheck.typeclasses import Number
29 from translate.storage.xml_extract import misc
30 from translate.storage.xml_extract import extract
31 from translate.storage.xml_extract import unit_tree
32 from translate.storage.xml_name import XmlNamer
36 """Return a dictionary indexed by child tag names, where each tag is associated with an array
37 of all the child nodes with matching the tag name, in the order in which they appear as children
38 of dom_node.
39
40 >>> xml = '<a><b></b><c></c><b></b><d/></a>'
41 >>> element = etree.fromstring(xml)
42 >>> get_tag_arrays(element)
43 {'b': [<Element a at 84df144>, <Element a at 84df148>], 'c': [<Element a at 84df120>], 'd': [<Element a at 84df152>]}
44 """
45 child_dict = {}
46 for child in dom_node:
47 if child.tag not in child_dict:
48 child_dict[child.tag] = []
49 child_dict[child.tag].append(child)
50 return child_dict
51
54 tag_array = _get_tag_arrays(dom_node)
55 for unit_child_index, unit_child in unit_node.children.iteritems():
56 tag, index = unit_child_index
57 try:
58 dom_child = tag_array[XmlNamer(dom_node).name(tag)][index]
59 apply_translations(dom_child, unit_child, do_translate)
60
61
62 except KeyError:
63 pass
64
65
66 except IndexError:
67 pass
68
69 if unit_node.unit != None:
70
71
72 do_translate(dom_node, unit_node.unit)
73
76 return misc.reduce_tree(f, dom_node, dom_node, lambda dom_node: dom_node, *state)
77
78 @accepts(etree._Element, etree._Element)
79 -def find_dom_root(parent_dom_node, dom_node):
80 """@see: L{find_placeable_dom_tree_roots}"""
81 if dom_node is None or parent_dom_node is None:
82 return None
83 if dom_node.getparent() == parent_dom_node:
84 return dom_node
85 elif dom_node.getparent() is None:
86 return None
87 else:
88 return find_dom_root(parent_dom_node, dom_node.getparent())
89
90 @accepts(extract.Translatable)
92 """For an inline placeable, find the root DOM node for the placeable in its
93 parent.
94
95 Consider the diagram. In this pseudo-ODF example, there is an inline span
96 element. However, the span is contained in other tags (which we never process).
97 When splicing the template DOM tree (that is, the DOM which comes from
98 the XML document we're using to generate a translated XML document), we'll
99 need to move DOM sub-trees around and we need the roots of these sub-trees::
100
101 <p> This is text \/ <- Paragraph containing an inline placeable
102 <blah> <- Inline placeable's root (which we want to find)
103 ... <- Any number of intermediate DOM nodes
104 <span> bold text <- The inline placeable's Translatable
105 holds a reference to this DOM node
106 """
107
108 def set_dom_root_for_unit_node(parent_unit_node, unit_node, dom_tree_roots):
109 dom_tree_roots[unit_node] = find_dom_root(parent_unit_node.dom_node, unit_node.dom_node)
110 return dom_tree_roots
111 return extract.reduce_unit_tree(set_dom_root_for_unit_node, unit_node, {})
112
113 @accepts(extract.Translatable, etree._Element)
115 """Creating a mapping from the DOM nodes in source_dom_node which correspond to
116 placeables, with DOM nodes in the XML document template (this information is obtained
117 from unit_node). We are interested in DOM nodes in the XML document template which
118 are the roots of placeables. See the diagram below, as well as
119 L{find_placeable_dom_tree_roots}.
120
121 XLIFF Source (below)::
122 <source>This is text <g> bold text</g> and a footnote<x/></source>
123 / \________
124 / \
125 <p>This is text<blah>...<span> bold text</span>...</blah> and <note>...</note></p>
126 Input XML document used as a template (above)
127
128 In the above diagram, the XLIFF source DOM node <g> is associated with the XML
129 document DOM node <blah>, whereas the XLIFF source DOM node <x> is associated with
130 the XML document DOM node <note>.
131 """
132 dom_tree_roots = find_placeable_dom_tree_roots(unit_node)
133 source_dom_to_doc_dom = {}
134
135 def loop(unit_node, source_dom_node):
136 for child_unit_node, child_source_dom in zip(unit_node.placeables, source_dom_node):
137 source_dom_to_doc_dom[child_source_dom] = dom_tree_roots[child_unit_node]
138 loop(child_unit_node, child_source_dom)
139
140 loop(unit_node, source_dom_node)
141 return source_dom_to_doc_dom
142
145 """Associate placeables in source_dom_node and target_dom_node which
146 have the same 'id' attributes.
147
148 We're using XLIFF placeables. The XLIFF standard requires that
149 placeables have unique ids. The id of a placeable is never modified,
150 which means that even if placeables are moved around in a translation,
151 we can easily associate placeables from the source text with placeables
152 in the target text.
153
154 This function does exactly that.
155 """
156
157 def map_id_to_dom_node(parent_node, node, id_to_dom_node):
158
159
160 if u'id' in node.attrib:
161 id_to_dom_node[node.attrib[u'id']] = node
162 return id_to_dom_node
163
164
165 id_to_dom_node = reduce_dom_tree(map_id_to_dom_node, target_dom_node, {})
166
167 def map_target_dom_to_source_dom_aux(parent_node, node, target_dom_to_source_dom):
168
169 if u'id' in node.attrib and node.attrib[u'id'] in id_to_dom_node:
170 target_dom_to_source_dom[id_to_dom_node[node.attrib[u'id']]] = node
171 return target_dom_to_source_dom
172
173
174
175
176
177
178 return reduce_dom_tree(map_target_dom_to_source_dom_aux, source_dom_node, {})
179
184
185 @accepts(etree._Element, {etree._Element: etree._Element})
186 -def _get_translated_node(target_node, target_dom_to_doc_dom):
187 """Convenience function to get node corresponding to 'target_node'
188 and to assign the tail text of 'target_node' to this node."""
189 dom_node = target_dom_to_doc_dom[target_node]
190 dom_node.tail = target_node.tail
191 return dom_node
192
193 @accepts(etree._Element, etree._Element, {etree._Element: etree._Element})
194 -def _build_translated_dom(dom_node, target_node, target_dom_to_doc_dom):
195 """Use the "shape" of 'target_node' (which is a DOM tree) to insert nodes
196 into the DOM tree rooted at 'dom_node'.
197
198 The mapping 'target_dom_to_doc_dom' is used to map nodes from 'target_node'
199 to nodes which much be inserted into dom_node.
200 """
201 dom_node.text = target_node.text
202
203
204
205
206
207
208 dom_node.extend(_get_translated_node(child, target_dom_to_doc_dom) for child in target_node
209 if target_dom_to_doc_dom[child] is not None)
210
211
212 for dom_child, target_child in zip(dom_node, target_node):
213 _build_translated_dom(dom_child, target_child, target_dom_to_doc_dom)
214
215 @accepts(IsCallable())
216 -def replace_dom_text(make_parse_state):
217 """Return a function::
218
219 action: etree_Element x base.TranslationUnit -> None
220
221 which takes a dom_node and a translation unit. The dom_node is rearranged
222 according to rearrangement of placeables in unit.target (relative to their
223 positions in unit.source).
224 """
225
226 @accepts(etree._Element, base.TranslationUnit)
227 def action(dom_node, unit):
228 """Use the unit's target (or source in the case where there is no translation)
229 to update the text in the dom_node and at the tails of its children."""
230 source_dom = unit.source_dom
231 if unit.target_dom is not None:
232 target_dom = unit.target_dom
233 else:
234 target_dom = unit.source_dom
235
236
237 unit_node = extract.find_translatable_dom_nodes(dom_node, make_parse_state())[0]
238 target_dom_to_doc_dom = _build_target_dom_to_doc_dom(unit_node, source_dom, target_dom)
239
240 dom_node[:] = []
241 _build_translated_dom(dom_node, target_dom, target_dom_to_doc_dom)
242
243 return action
244