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