def set_text(self, text, update=False): """Set the text rendered in this text box. Uses C{gtk.TextBuffer.set_text()}. @type text: str|unicode|L{StringElem} @param text: The text to render in this text box.""" if not isinstance(text, StringElem): text = StringElem(text) if self.elem is None: self.elem = StringElem(u'') if text is not self.elem: # If text is self.elem, we are busy with a refresh and we should remember the selected element. self.selected_elem = None self.selected_elem_index = None # We have to edit the existing .elem for the sake of the undo controller if self.placeables_controller: self.elem.sub = [elem_parse(text, self.placeables_controller.get_parsers_for_textbox(self))] self.elem.prune() else: self.elem.sub = [text] self.update_tree() elif update: self.update_tree() self.emit("changed")
def test_rich_target(self): xlifffile = xliff.xlifffile() xliffunit = xlifffile.addsourceunit(u'') # Test 1 xliffunit.set_rich_target([StringElem([u'foo', X(id='bar'), u'baz'])], u'fr') target_dom_node = xliffunit.getlanguageNode(None, 1) x_placeable = target_dom_node[0] assert target_dom_node.text == 'foo' assert x_placeable.tag == u'x' assert x_placeable.attrib['id'] == 'bar' assert x_placeable.tail == 'baz' # Test 2 xliffunit.set_rich_target([StringElem([u'foo', u'baz', G(id='oof', sub=[G(id='zab', sub=[u'bar', u'rab'])])])], u'fr') target_dom_node = xliffunit.getlanguageNode(None, 1) g_placeable = target_dom_node[0] nested_g_placeable = g_placeable[0] assert target_dom_node.text == u'foobaz' assert g_placeable.tag == u'g' print('g_placeable.text: %s (%s)' % (g_placeable.text, type(g_placeable.text))) assert g_placeable.text is None assert g_placeable.attrib[u'id'] == u'oof' assert g_placeable.tail is None assert nested_g_placeable.tag == u'g' assert nested_g_placeable.text == u'barrab' assert nested_g_placeable.attrib[u'id'] == u'zab' assert nested_g_placeable.tail is None xliffunit.rich_target[0].print_tree(2) assert xliffunit.rich_target == [StringElem([u'foobaz', G(id='oof', sub=[G(id='zab', sub=[u'barrab'])])])]
def parse(tree, parse_funcs): """Parse placeables from the given string or sub-tree by using the parsing functions provided. The output of this function is B{heavily} dependent on the order of the parsing functions. This is because of the algorithm used. An over-simplification of the algorithm: the leaves in the C{StringElem} tree are expanded to the output of the first parsing function in C{parse_funcs}. The next level of recursion is then started on the new set of leaves with the used parsing function removed from C{parse_funcs}. @type tree: unicode|StringElem @param tree: The string or string element sub-tree to parse. @type parse_funcs: A list of parsing functions. It must take exactly one argument (a C{unicode} string to parse) and return a list of C{StringElem}s which, together, form the original string. If nothing could be parsed, it should return C{None}.""" if isinstance(tree, unicode): tree = StringElem(tree) if not parse_funcs: return tree parse_func = parse_funcs[0] for leaf in tree.flatten(): #FIXME: we might rather want to test for editability, but for now this # works better if not leaf.istranslatable: continue unileaf = unicode(leaf) if not unileaf: continue subleaves = parse_func(unileaf) if subleaves is not None: if len(subleaves) == 1 and type(leaf) is type( subleaves[0]) and leaf == subleaves[0]: pass elif isinstance(leaf, unicode): parent = tree.get_parent_elem(leaf) if parent is not None: if len(parent.sub) == 1: parent.sub = subleaves leaf = parent else: leafindex = parent.sub.index(leaf) parent.sub[leafindex] = StringElem(subleaves) leaf = parent.sub[leafindex] else: leaf.sub = subleaves parse(leaf, parse_funcs[1:]) if isinstance(leaf, StringElem): leaf.prune() return tree
def parse(cls, pstr): parts = [] matches = [] match_info = {} for matcher in cls.matchers: matches.extend(matcher.matches(pstr)) match_info.update(matcher.match_info) lastend = 0 def sort_matches(x, y): # This function will sort a list of matches according to the # match's starting position, putting the one with the longer # source text first, if two are the same. c = cmp(match_info[x.source]['pos'], match_info[y.source]['pos']) return c and c or cmp(len(y.source), len(x.source)) matches.sort(sort_matches) for match in matches: info = match_info[match.source] if info['pos'] < lastend: continue end = info['pos'] + len(match.source) if 'newtermlen' in info: end = info['pos'] + info['newtermlen'] if lastend < info['pos']: parts.append(StringElem(pstr[lastend:info['pos']])) term_string = pstr[info['pos']:end] term_placeable = cls([term_string]) parts.append(term_placeable) # Get translations for the placeable for m in matches: m_info = match_info[m.source] m_end = m_info['pos'] if 'newtermlen' in m_info: m_end += m_info['newtermlen'] else: m_end += len(m.source) if info['pos'] == m_info['pos'] and end == m_end: term_placeable.translations.append(m.target) # remove duplicates: term_placeable.translations = list(set( term_placeable.translations)) lastend = end if lastend != len(pstr) and parts: parts.append(StringElem(pstr[lastend:])) return parts or None
def test_xml_space(): source = etree.fromstring( '<source xml:space="default"> a <x id="foo[1]/bar[1]/baz[1]"/> </source>' ) elem = lisa.xml_to_strelem(source) print(elem.sub) assert elem.sub == [ StringElem('a '), X(id='foo[1]/bar[1]/baz[1]'), StringElem(' ') ]
def test_chunk_list(): left = StringElem([ 'a', G(id='foo[2]/bar[2]/baz[2]', sub=['b', X(id='foo[1]/bar[1]/baz[1]'), 'c']), 'é' ]) right = StringElem([ 'a', G(id='foo[2]/bar[2]/baz[2]', sub=['b', X(id='foo[1]/bar[1]/baz[1]'), 'c']), 'é' ]) assert left == right
def test_xml_to_strelem(): source = etree.fromstring(u'<source>a<x id="foo[1]/bar[1]/baz[1]"/></source>') elem = lisa.xml_to_strelem(source) assert elem.sub == [ StringElem(u'a'), X(id=u'foo[1]/bar[1]/baz[1]') ] source = etree.fromstring(u'<source>a<x id="foo[1]/bar[1]/baz[1]"/>é</source>') elem = lisa.xml_to_strelem(source) assert elem.sub == [ StringElem(u'a'), X(id=u'foo[1]/bar[1]/baz[1]'), StringElem(u'é') ] source = etree.fromstring(u'<source>a<g id="foo[2]/bar[2]/baz[2]">b<x id="foo[1]/bar[1]/baz[1]"/>c</g>é</source>') elem = lisa.xml_to_strelem(source) assert elem.sub == [ StringElem(u'a'), G(id=u'foo[2]/bar[2]/baz[2]', sub=[StringElem(u'b'), X(id=u'foo[1]/bar[1]/baz[1]'), StringElem(u'c')]), StringElem(u'é') ]
def parse(cls, pstr): parts = [] matches = [] match_info = {} for matcher in cls.matchers: matches.extend(matcher.matches(pstr)) match_info.update(matcher.match_info) lastend = 0 # This function will sort a list of matches according to the # match's starting position, putting the one with the longer # source text first, if two are the same. matches.sort(key=lambda x: len(x.source), reverse=True) matches.sort(key=lambda x: match_info[x.source]["pos"]) for match in matches: info = match_info[match.source] if info["pos"] < lastend: continue end = info["pos"] + len(match.source) if "newtermlen" in info: end = info["pos"] + info["newtermlen"] if lastend < info["pos"]: parts.append(StringElem(pstr[lastend:info["pos"]])) term_string = pstr[info["pos"]:end] term_placeable = cls([term_string]) parts.append(term_placeable) # Get translations for the placeable for m in matches: m_info = match_info[m.source] m_end = m_info["pos"] if "newtermlen" in m_info: m_end += m_info["newtermlen"] else: m_end += len(m.source) if info["pos"] == m_info["pos"] and end == m_end: term_placeable.translations.append(m.target) # remove duplicates: term_placeable.translations = list(set( term_placeable.translations)) lastend = end if lastend != len(pstr) and parts: parts.append(StringElem(pstr[lastend:])) return parts or None
def test_chunk_list(): left = StringElem([ "a", G(id="foo[2]/bar[2]/baz[2]", sub=["b", X(id="foo[1]/bar[1]/baz[1]"), "c"]), "é", ]) right = StringElem([ "a", G(id="foo[2]/bar[2]/baz[2]", sub=["b", X(id="foo[1]/bar[1]/baz[1]"), "c"]), "é", ]) assert left == right
def test_xml_to_strelem(): source = etree.fromstring("<source>a</source>") elem = lisa.xml_to_strelem(source) assert elem == StringElem("a") source = etree.fromstring( '<source>a<x id="foo[1]/bar[1]/baz[1]"/></source>') elem = lisa.xml_to_strelem(source) assert elem.sub == [StringElem("a"), X(id="foo[1]/bar[1]/baz[1]")] source = etree.fromstring( '<source>a<x id="foo[1]/bar[1]/baz[1]"/>é</source>') elem = lisa.xml_to_strelem(source) assert elem.sub == [ StringElem("a"), X(id="foo[1]/bar[1]/baz[1]"), StringElem("é") ] source = etree.fromstring( '<source>a<g id="foo[2]/bar[2]/baz[2]">b<x id="foo[1]/bar[1]/baz[1]"/>c</g>é</source>' ) elem = lisa.xml_to_strelem(source) assert elem.sub == [ StringElem("a"), G( id="foo[2]/bar[2]/baz[2]", sub=[ StringElem("b"), X(id="foo[1]/bar[1]/baz[1]"), StringElem("c") ], ), StringElem("é"), ]
def test_rich_target(self): xlifffile = xliff.xlifffile() xliffunit = xlifffile.addsourceunit("") # Test 1 xliffunit.set_rich_target( [StringElem(["foo", X(id="bar"), "baz"])], "fr") target_dom_node = xliffunit.getlanguageNode(None, 1) x_placeable = target_dom_node[0] assert target_dom_node.text == "foo" assert x_placeable.tag == "x" assert x_placeable.attrib["id"] == "bar" assert x_placeable.tail == "baz" # Test 2 xliffunit.set_rich_target( [ StringElem([ "foo", "baz", G(id="oof", sub=[G(id="zab", sub=["bar", "rab"])]) ]) ], "fr", ) target_dom_node = xliffunit.getlanguageNode(None, 1) g_placeable = target_dom_node[0] nested_g_placeable = g_placeable[0] assert target_dom_node.text == "foobaz" assert g_placeable.tag == "g" print("g_placeable.text: {} ({})".format(g_placeable.text, type(g_placeable.text))) assert g_placeable.text is None assert g_placeable.attrib["id"] == "oof" assert g_placeable.tail is None assert nested_g_placeable.tag == "g" assert nested_g_placeable.text == "barrab" assert nested_g_placeable.attrib["id"] == "zab" assert nested_g_placeable.tail is None xliffunit.rich_target[0].print_tree(2) assert xliffunit.rich_target == [ StringElem( ["foobaz", G(id="oof", sub=[G(id="zab", sub=["barrab"])])]) ]
def xml_to_strelem(dom_node, xml_space="preserve"): if dom_node is None: return StringElem() if isinstance(dom_node, basestring): dom_node = etree.fromstring(dom_node) normalize_xml_space(dom_node, xml_space, remove_start=True) result = StringElem() if dom_node.text: result.sub.append(StringElem(unicode(dom_node.text))) for child_dom_node in dom_node: result.sub.append(make_placeable(child_dom_node, xml_space)) if child_dom_node.tail: result.sub.append(StringElem(unicode(child_dom_node.tail))) result.prune() return result
def test_insert(self): # Test inserting at the beginning elem = self.elem.copy() elem.insert(0, 'xxx') assert str(elem.sub[0]) == 'xxx' + str(self.elem.sub[0]) # Test inserting at the end elem = self.elem.copy() elem.insert(len(elem), 'xxx') assert elem.flatten()[-1] == StringElem('xxx') assert str(elem).endswith('&brandLong;</a>xxx') elem = self.elem.copy() elem.insert(len(elem), ">>>", preferred_parent=elem.sub[-1]) assert str(elem.flatten()[-1]) == '</a>>>>' assert str(elem).endswith('&brandLong;</a>>>>') # Test inserting in the middle of an existing string elem = self.elem.copy() elem.insert(2, 'xxx') assert str(elem.sub[0]) == 'Ģëxxxt ' # Test inserting between elements elem = self.elem.copy() elem.insert(56, 'xxx') assert str(elem)[56:59] == 'xxx'
def test_delete_range_case3(self): # Case 3: Within a single element # elem = self.elem.copy() deleted, parent, offset = elem.delete_range(1, 2) assert deleted == StringElem(u'ë') assert parent is elem.sub[0] assert offset == 1
def test_insert(self): # Test inserting at the beginning elem = self.elem.copy() elem.insert(0, "xxx") assert str(elem.sub[0]) == "xxx" + str(self.elem.sub[0]) # Test inserting at the end elem = self.elem.copy() elem.insert(len(elem), "xxx") assert elem.flatten()[-1] == StringElem("xxx") assert str(elem).endswith("&brandLong;</a>xxx") elem = self.elem.copy() elem.insert(len(elem), ">>>", preferred_parent=elem.sub[-1]) assert str(elem.flatten()[-1]) == "</a>>>>" assert str(elem).endswith("&brandLong;</a>>>>") # Test inserting in the middle of an existing string elem = self.elem.copy() elem.insert(2, "xxx") assert str(elem.sub[0]) == "Ģëxxxt " # Test inserting between elements elem = self.elem.copy() elem.insert(56, "xxx") assert str(elem)[56:59] == "xxx"
def _add_translatable_to_store(store, parent_translatable, translatable, id_maker): """Construct a new translation unit, set its source and location information and add it to 'store'. """ unit = store.UnitClass(u'') unit.rich_source = [StringElem(_to_placeables(parent_translatable, translatable, id_maker))] unit.addlocation(translatable.xpath) store.addunit(unit)
def apply_parsers(self, elems, parsers=None): """Apply all selected placeable parsers to the list of string elements given. @param elems: The list of C{StringElem}s to apply the parsers to.""" if not isinstance(elems, list) and isinstance(elems, StringElem): elems = [elems] if parsers is None: parsers = self.parsers for elem in elems: elem = elem parsed = parse_placeables(elem, parsers) if isinstance(elem, (str, unicode)) and parsed != StringElem(elem): parent = elem.get_parent_elem(elem) if parent is not None: parent.sub[parent.sub.index(elem)] = StringElem(parsed) return elems
def test_rich_source(self): xlifffile = xliff.xlifffile() xliffunit = xlifffile.addsourceunit(u'') # Test 1 xliffunit.rich_source = [StringElem([u'foo', X(id='bar'), u'baz'])] source_dom_node = xliffunit.getlanguageNode(None, 0) x_placeable = source_dom_node[0] assert source_dom_node.text == 'foo' assert x_placeable.tag == u'x' assert x_placeable.attrib['id'] == 'bar' assert x_placeable.tail == 'baz' xliffunit.rich_source[0].print_tree(2) print(xliffunit.rich_source) assert xliffunit.rich_source == [StringElem([StringElem(u'foo'), X(id='bar'), StringElem(u'baz')])] # Test 2 xliffunit.rich_source = [StringElem([u'foo', u'baz', G(id='oof', sub=[G(id='zab', sub=[u'bar', u'rab'])])])] source_dom_node = xliffunit.getlanguageNode(None, 0) g_placeable = source_dom_node[0] nested_g_placeable = g_placeable[0] assert source_dom_node.text == u'foobaz' assert g_placeable.tag == u'g' assert g_placeable.text is None assert g_placeable.attrib[u'id'] == u'oof' assert g_placeable.tail is None assert nested_g_placeable.tag == u'g' assert nested_g_placeable.text == u'barrab' assert nested_g_placeable.attrib[u'id'] == u'zab' assert nested_g_placeable.tail is None rich_source = xliffunit.rich_source rich_source[0].print_tree(2) assert rich_source == [StringElem([u'foobaz', G(id='oof', sub=[G(id='zab', sub=[u'barrab'])])])]
def xml_to_strelem(dom_node, xml_space="preserve"): if dom_node is None: return StringElem() if isinstance(dom_node, six.string_types): dom_node = etree.fromstring(dom_node) normalize_xml_space(dom_node, xml_space, remove_start=True) result = StringElem() sub = result.sub # just an optimisation for child_dom_node in dom_node: if child_dom_node.tag is etree.Comment: continue sub.append(make_placeable(child_dom_node, xml_space)) if child_dom_node.tail: sub.append(StringElem(six.text_type(child_dom_node.tail))) # This is just a strange way of inserting the first text and avoiding a # call to .prune() which is very expensive. We assume the tree is optimal. node_text = dom_node.text if sub and node_text: sub.insert(0, StringElem(six.text_type(node_text))) elif node_text: sub.append(six.text_type(node_text)) return result
def rewrite_unicode(self, string): """Convert to Unicode characters that look like the source string""" if not isinstance(string, StringElem): string = StringElem(string) def transpose(char): loc = ord(char) - 65 if loc < 0 or loc > 56: return char return self.REWRITE_UNICODE_MAP[loc] def transformer(s): return ''.join([transpose(c) for c in s]) self.apply_to_translatables(string, transformer) return string
def rewrite_flipped(self, string): """Convert the string to look flipped upside down.""" if not isinstance(string, StringElem): string = StringElem(string) def transpose(char): loc = ord(char) - 33 if loc < 0 or loc > 89: return char return self.REWRITE_FLIPPED_MAP[loc] def transformer(s): return u"\u202e" + u''.join([transpose(c) for c in s]) # To reverse instead of using the RTL override: #return u''.join(reversed([transpose(c) for c in s])) self.apply_to_translatables(string, transformer) return string
def test_rich_source(): xlifffile = xliff.xlifffile() xliffunit = xlifffile.addsourceunit("") # Test 1 xliffunit.rich_source = [StringElem(["foo", X(id="bar"), "baz"])] source_dom_node = xliffunit.getlanguageNode(None, 0) x_placeable = source_dom_node[0] assert source_dom_node.text == "foo" assert x_placeable.tag == "x" assert x_placeable.attrib["id"] == "bar" assert x_placeable.tail == "baz" xliffunit.rich_source[0].print_tree(2) print(xliffunit.rich_source) assert xliffunit.rich_source == [ StringElem([StringElem("foo"), X(id="bar"), StringElem("baz")]) ] # Test 2 xliffunit.rich_source = [ StringElem([ "foo", "baz", G(id="oof", sub=[G(id="zab", sub=["bar", "rab"])]) ]) ] source_dom_node = xliffunit.getlanguageNode(None, 0) g_placeable = source_dom_node[0] nested_g_placeable = g_placeable[0] assert source_dom_node.text == "foobaz" assert g_placeable.tag == "g" assert g_placeable.text is None assert g_placeable.attrib["id"] == "oof" assert g_placeable.tail is None assert nested_g_placeable.tag == "g" assert nested_g_placeable.text == "barrab" assert nested_g_placeable.attrib["id"] == "zab" assert nested_g_placeable.tail is None rich_source = xliffunit.rich_source rich_source[0].print_tree(2) assert rich_source == [ StringElem( ["foobaz", G(id="oof", sub=[G(id="zab", sub=["barrab"])])]) ]
def _rewrite_prepend_append(self, string, prepend, append=None): if append is None: append = prepend if not isinstance(string, StringElem): string = StringElem(string) string.sub.insert(0, prepend) if six.text_type(string).endswith(u'\n'): # Try and remove the last character from the tree try: lastnode = string.flatten()[-1] if isinstance(lastnode.sub[-1], six.text_type): lastnode.sub[-1] = lastnode.sub[-1].rstrip(u'\n') except IndexError: pass string.sub.append(append + u'\n') else: string.sub.append(append) return string
def add_translatable_to_store(parent_translatable, translatable): """Construct a new translation unit, set its source and location information and add it to 'store'. """ xliff_unit = xliffunit(u'') placeables = _to_placeables(parent_translatable, translatable, id_maker) xliff_unit.rich_source = [StringElem(placeables)] # Get the plain text for the unit source. The output is enclosed within # XLIFF source tags we don't want, so strip them. unit_source = etree.tostring(xliff_unit.source_dom) unit_source = unit_source[unit_source.find(">", 1) + 1:] unit_source = unit_source[:unit_source.rfind("<", 1)] # Create the PO unit and add it to the PO store. po_unit = store.UnitClass(unit_source) po_unit.addlocation(translatable.xpath) po_unit.addlocation(filename) store.addunit(po_unit)
def rewrite_unicode(self, string): """Convert to Unicode characters that look like the source string""" if not isinstance(string, StringElem): string = StringElem(string) def transpose(char): loc = ord(char) - 65 if loc < 0 or loc > 56: return char return self.REWRITE_UNICODE_MAP[loc] def transformer(s): if self.preserveplaceholders: return self.transform_characters_preserving_placeholders( s, transpose) else: return "".join(transpose(c) for c in s) self.apply_to_translatables(string, transformer) return string
def test_insert(self): # Test inserting at the beginning elem = self.elem.copy() elem.insert(0, u'xxx') assert unicode(elem.sub[0]) == u'xxx' + unicode(self.elem.sub[0]) # Test inserting at the end elem = self.elem.copy() elem.insert(len(elem) + 1, u'xxx') assert elem.flatten()[-1] == StringElem(u'xxx') # Test inserting in the middle of an existing string elem = self.elem.copy() elem.insert(2, u'xxx') assert unicode(elem.sub[0]) == u'Ģëxxxt ' # Test inserting between elements elem = self.elem.copy() elem.insert(56, u'xxx') assert unicode(elem)[56:59] == u'xxx'
def rewrite_chef(self, string): """Rewrite using Mock Swedish as made famous by Monty Python""" if not isinstance(string, StringElem): string = StringElem(string) # From Dive into Python which itself got it elsewhere # http://www.renderx.com/demos/examples/diveintopython.pdf subs = ((r'a([nu])', r'u\1'), (r'A([nu])', r'U\1'), (r'a\B', r'e'), (r'A\B', r'E'), (r'en\b', r'ee'), (r'\Bew', r'oo'), (r'\Be\b', r'e-a'), (r'\be', r'i'), (r'\bE', r'I'), (r'\Bf', r'ff'), (r'\Bir', r'ur'), (r'(\w*?)i(\w*?)$', r'\1ee\2'), (r'\bow', r'oo'), (r'\bo', r'oo'), (r'\bO', r'Oo'), (r'the', r'zee'), (r'The', r'Zee'), (r'th\b', r't'), (r'\Btion', r'shun'), (r'\Bu', r'oo'), (r'\BU', r'Oo'), (r'v', r'f'), (r'V', r'F'), (r'w', r'w'), (r'W', r'W'), (r'([a-z])[.]', r'\1. Bork Bork Bork!')) for a, b in subs: self.apply_to_translatables(string, lambda s: re.sub(a, b, s)) return string
def update_tree(self): if not self.placeables_controller: return if self.elem is None: self.elem = StringElem(u'') self.add_default_gui_info(self.elem) self.buffer.handler_block_by_func(self._on_delete_range) self.buffer.handler_block_by_func(self._on_insert_text) self.elem.gui_info.render() self.show_suggestion() self.buffer.handler_unblock_by_func(self._on_delete_range) self.buffer.handler_unblock_by_func(self._on_insert_text) tagtable = self.buffer.get_tag_table() def remtag(tag, data): tagtable.remove(tag) # FIXME: The following line caused the program to segfault, so it's removed (for now). #tagtable.foreach(remtag) # At this point we have a tree of string elements with GUI info. self.apply_gui_info(self.elem)
def rewrite_flipped(self, string): """Convert the string to look flipped upside down.""" if not isinstance(string, StringElem): string = StringElem(string) def transpose(char): loc = ord(char) - 33 if loc < 0 or loc > 89: return char return self.REWRITE_FLIPPED_MAP[loc] def transformer(s): if self.preserveplaceholders: return "\u202e" + self.transform_characters_preserving_placeholders( s, transpose) else: return "\u202e" + "".join([transpose(c) for c in s]) # To reverse instead of using the RTL override: # return ''.join(reversed([transpose(c) for c in s])) self.apply_to_translatables(string, transformer) return string
def test_set_strelem_to_xml(): source = etree.Element("source") lisa.strelem_to_xml(source, StringElem("a")) assert etree.tostring(source, encoding="UTF-8") == b"<source>a</source>" source = etree.Element("source") lisa.strelem_to_xml(source, StringElem(["a", "é"])) assert etree.tostring(source, encoding="UTF-8") == b"<source>a\xc3\xa9</source>" source = etree.Element("source") lisa.strelem_to_xml(source, StringElem(X(id="foo[1]/bar[1]/baz[1]"))) assert (etree.tostring( source, encoding="UTF-8") == b'<source><x id="foo[1]/bar[1]/baz[1]"/></source>' ) source = etree.Element("source") lisa.strelem_to_xml(source, StringElem(["a", X(id="foo[1]/bar[1]/baz[1]")])) assert (etree.tostring(source, encoding="UTF-8") == b'<source>a<x id="foo[1]/bar[1]/baz[1]"/></source>') source = etree.Element("source") lisa.strelem_to_xml(source, StringElem(["a", X(id="foo[1]/bar[1]/baz[1]"), "é"])) assert (etree.tostring(source, encoding="UTF-8") == b'<source>a<x id="foo[1]/bar[1]/baz[1]"/>\xc3\xa9</source>') source = etree.Element("source") lisa.strelem_to_xml( source, StringElem([ "a", G( id="foo[2]/bar[2]/baz[2]", sub=["b", X(id="foo[1]/bar[1]/baz[1]"), "c"], ), "é", ]), ) assert ( etree.tostring(source, encoding="UTF-8") == b'<source>a<g id="foo[2]/bar[2]/baz[2]">b<x id="foo[1]/bar[1]/baz[1]"/>c</g>\xc3\xa9</source>' )