1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 from lxml import etree
22
23 from translate.storage import base
24 from translate.misc.typecheck import accepts, Self, IsCallable, IsOneOf, Any, Class
25 from translate.misc.typecheck.typeclasses import Number
26 from translate.misc.contextlib import contextmanager, nested
27 from translate.misc.context import with_
28 from translate.storage.xml_extract import xpath_breadcrumb
29 from translate.storage.xml_extract import misc
30 from translate.storage.placeables import xliff, StringElem
34 return IsOneOf(t, type(None))
35
36 TranslatableClass = Class('Translatable')
40 """A node corresponds to a translatable element. A node may
41 have children, which correspond to placeables."""
42
43 @accepts(Self(), unicode, unicode, etree._Element, [IsOneOf(TranslatableClass, unicode)])
44 - def __init__(self, placeable_name, xpath, dom_node, source):
45 self.placeable_name = placeable_name
46 self.source = source
47 self.xpath = xpath
48 self.is_inline = False
49 self.dom_node = dom_node
50
53
54 placeables = property(_get_placeables)
55
60
63 """Maintain constants and variables used during the walking of a
64 DOM tree (via the function apply)."""
65
66 - def __init__(self, no_translate_content_elements, inline_elements={}, nsmap={}):
67 self.no_translate_content_elements = no_translate_content_elements
68 self.inline_elements = inline_elements
69 self.is_inline = False
70 self.xpath_breadcrumb = xpath_breadcrumb.XPathBreadcrumb()
71 self.placeable_name = u"<top-level>"
72 self.nsmap = nsmap
73
74
75 @accepts(etree._Element, ParseState)
77 """Run find_translatable_dom_nodes on the current dom_node"""
78 placeable = find_translatable_dom_nodes(dom_node, state)
79
80
81
82 if len(placeable) == 0:
83 return Translatable(u"placeable", state.xpath_breadcrumb.xpath, dom_node, [])
84
85
86 elif len(placeable) == 1:
87 return placeable[0]
88 else:
89 raise Exception("BUG: find_translatable_dom_nodes should never return more than a single translatable")
90
91
92 @accepts(etree._Element, ParseState)
94 """Return a list of placeables and list with
95 alternating string-placeable objects. The former is
96 useful for directly working with placeables and the latter
97 is what will be used to build the final translatable string."""
98
99 source = []
100 for child in dom_node:
101 source.extend([_process_placeable(child, state), unicode(child.tail or u"")])
102 return source
103
104
105 @accepts(etree._Element, ParseState)
111
112
113 @accepts(etree._Element, ParseState)
115 _namespace, tag = misc.parse_tag(dom_node.tag)
116 children = [find_translatable_dom_nodes(child, state) for child in dom_node]
117
118 children = [child for child_list in children for child in child_list]
119 if len(children) > 1:
120 intermediate_translatable = Translatable(tag, state.xpath_breadcrumb.xpath, dom_node, children)
121 return [intermediate_translatable]
122 else:
123 return children
124
131
132
133 @accepts(etree._Element, ParseState)
149
150 @contextmanager
151 def placeable_set():
152 old_placeable_name = state.placeable_name
153 state.placeable_name = tag
154 yield state.placeable_name
155 state.placeable_name = old_placeable_name
156
157 @contextmanager
158 def inline_set():
159 old_inline = state.is_inline
160 if (namespace, tag) in state.inline_elements:
161 state.is_inline = True
162 else:
163 state.is_inline = False
164 yield state.is_inline
165 state.is_inline = old_inline
166
167 def with_block(xpath_breadcrumb, placeable_name, is_inline):
168 if (namespace, tag) not in state.no_translate_content_elements:
169 return _process_translatable(dom_node, state)
170 else:
171 return _process_children(dom_node, state)
172 return with_(nested(xpath_set(), placeable_set(), inline_set()), with_block)
173
176
178 self._max_id = 0
179 self._obj_id_map = {}
180
182 if not self.has_id(obj):
183 self._obj_id_map[obj] = self._max_id
184 self._max_id += 1
185 return self._obj_id_map[obj]
186
188 return obj in self._obj_id_map
189
193 result = []
194 for chunk in translatable.source:
195 if isinstance(chunk, unicode):
196 result.append(chunk)
197 else:
198 id = unicode(id_maker.get_id(chunk))
199 if chunk.is_inline:
200 result.append(xliff.G(sub=_to_placeables(parent_translatable, chunk, id_maker), id=id))
201 else:
202 result.append(xliff.X(id=id, xid=chunk.xpath))
203 return result
204
205
206 @accepts(base.TranslationStore, Nullable(Translatable), Translatable, IdMaker)
208 """Construct a new translation unit, set its source and location
209 information and add it to 'store'.
210 """
211 unit = store.UnitClass(u'')
212 unit.rich_source = [StringElem(_to_placeables(parent_translatable, translatable, id_maker))]
213 unit.addlocation(translatable.xpath)
214 store.addunit(unit)
215
216
217 @accepts(Translatable)
218 -def _contains_translatable_text(translatable):
219 """Checks whether translatable contains any chunks of text which contain
220 more than whitespace.
221
222 If not, then there's nothing to translate."""
223 for chunk in translatable.source:
224 if isinstance(chunk, unicode):
225 if chunk.strip() != u"":
226 return True
227 return False
228
229
230 @accepts(base.TranslationStore)
232 """Return a function which, when called with a Translatable will add
233 a unit to 'store'. The placeables will represented as strings according
234 to 'placeable_quoter'."""
235 id_maker = IdMaker()
236
237 def add_to_store(parent_translatable, translatable, rid):
238 _add_translatable_to_store(store, parent_translatable, translatable, id_maker)
239
240 return add_to_store
241
245 for translatable in translatables:
246 if _contains_translatable_text(translatable) and not translatable.is_inline:
247 rid = rid + 1
248 new_parent_translatable = translatable
249 f(parent_translatable, translatable, rid)
250 else:
251 new_parent_translatable = parent_translatable
252
253 _walk_translatable_tree(translatable.placeables, f, new_parent_translatable, rid)
254
258
259
260 @accepts(lambda obj: hasattr(obj, "read"), base.TranslationStore, ParseState, Nullable(IsCallable()))
261 -def build_store(odf_file, store, parse_state, store_adder=None):
270