Embed SVG images as ``<svg>`` instead of data-URI.
[docutils.git] / docutils / test / test_nodes.py
blobab09ca0de8402e2c6432e07758db40ec0ff56075
1 #! /usr/bin/env python3
2 # $Id$
3 # Author: David Goodger <goodger@python.org>
4 # Copyright: This module has been placed in the public domain.
6 """
7 Test module for nodes.py.
8 """
10 from pathlib import Path
11 import sys
12 import unittest
14 if __name__ == '__main__':
15 # prepend the "docutils root" to the Python library path
16 # so we import the local `docutils` package.
17 sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
19 from docutils import nodes, utils
21 debug = False
24 class TextTests(unittest.TestCase):
26 def setUp(self):
27 self.text = nodes.Text('Line 1.\nLine 2.')
28 self.unicode_text = nodes.Text('Möhren')
29 self.longtext = nodes.Text('Mary had a little lamb whose '
30 'fleece was white as snow and '
31 'everwhere that Mary went the '
32 'lamb was sure to go.')
34 def test_repr(self):
35 self.assertEqual(repr(self.text), r"<#text: 'Line 1.\nLine 2.'>")
36 self.assertEqual(self.text.shortrepr(),
37 r"<#text: 'Line 1.\nLine 2.'>")
38 self.assertEqual(repr(self.unicode_text), "<#text: 'Möhren'>")
40 def test_str(self):
41 self.assertEqual(str(self.text), 'Line 1.\nLine 2.')
43 def test_unicode(self):
44 self.assertEqual(str(self.unicode_text), 'Möhren')
45 self.assertEqual(str(self.unicode_text), 'M\xf6hren')
47 def test_astext(self):
48 self.assertTrue(isinstance(self.text.astext(), str))
49 self.assertEqual(self.text.astext(), 'Line 1.\nLine 2.')
50 self.assertEqual(self.unicode_text.astext(), 'Möhren')
52 def test_pformat(self):
53 self.assertTrue(isinstance(self.text.pformat(), str))
54 self.assertEqual(self.text.pformat(), 'Line 1.\nLine 2.\n')
56 def test_strip(self):
57 text = nodes.Text(' was noch ')
58 stripped = text.lstrip().rstrip()
59 stripped2 = text.lstrip(' wahn').rstrip(' wahn')
60 self.assertEqual(stripped, 'was noch')
61 self.assertEqual(stripped2, 's noc')
63 def test_asciirestriction(self):
64 # no bytes at all allowed
65 self.assertRaises(TypeError, nodes.Text, b'hol')
67 def test_longrepr(self):
68 self.assertEqual(repr(self.longtext), r"<#text: 'Mary had a "
69 r"little lamb whose fleece was white as snow "
70 r"and everwh ...'>")
71 self.assertEqual(self.longtext.shortrepr(),
72 r"<#text: 'Mary had a lit ...'>")
74 def test_comparison(self):
75 # Text nodes are compared by value
76 self.assertEqual(self.text, 'Line 1.\nLine 2.')
77 self.assertEqual(self.text, nodes.Text('Line 1.\nLine 2.'))
79 def test_Text_rawsource_deprection_warning(self):
80 with self.assertWarnsRegex(DeprecationWarning,
81 '"rawsource" is ignored'):
82 nodes.Text('content', rawsource='content')
85 class ElementTests(unittest.TestCase):
87 def test_empty(self):
88 element = nodes.Element()
89 self.assertEqual(repr(element), '<Element: >')
90 self.assertEqual(str(element), '<Element/>')
91 dom = element.asdom()
92 self.assertEqual(dom.toxml(), '<Element/>')
93 dom.unlink()
94 element['attr'] = '1'
95 self.assertEqual(repr(element), '<Element: >')
96 self.assertEqual(str(element), '<Element attr="1"/>')
97 dom = element.asdom()
98 self.assertEqual(dom.toxml(), '<Element attr="1"/>')
99 dom.unlink()
100 self.assertEqual(element.pformat(), '<Element attr="1">\n')
101 del element['attr']
102 element['mark'] = '\u2022'
103 self.assertEqual(repr(element), '<Element: >')
104 self.assertEqual(str(element), '<Element mark="\u2022"/>')
105 dom = element.asdom()
106 self.assertEqual(dom.toxml(), '<Element mark="\u2022"/>')
107 dom.unlink()
108 element['names'] = ['nobody', 'имя', 'näs']
109 self.assertEqual(repr(element), '<Element "nobody; имя; näs": >')
110 self.assertTrue(isinstance(repr(element), str))
112 def test_withtext(self):
113 element = nodes.Element('text\nmore', nodes.Text('text\nmore'))
114 uelement = nodes.Element('grün', nodes.Text('grün'))
115 self.assertEqual(repr(element), r"<Element: <#text: 'text\nmore'>>")
116 self.assertEqual(repr(uelement), "<Element: <#text: 'grün'>>")
117 self.assertTrue(isinstance(repr(uelement), str))
118 self.assertEqual(str(element), '<Element>text\nmore</Element>')
119 self.assertEqual(str(uelement), '<Element>gr\xfcn</Element>')
120 dom = element.asdom()
121 self.assertEqual(dom.toxml(), '<Element>text\nmore</Element>')
122 dom.unlink()
123 element['attr'] = '1'
124 self.assertEqual(repr(element), r"<Element: <#text: 'text\nmore'>>")
125 self.assertEqual(str(element),
126 '<Element attr="1">text\nmore</Element>')
127 dom = element.asdom()
128 self.assertEqual(dom.toxml(),
129 '<Element attr="1">text\nmore</Element>')
130 dom.unlink()
131 self.assertEqual(element.pformat(),
132 '<Element attr="1">\n text\n more\n')
134 def test_index(self):
135 # Element.index() behaves like list.index() on the element's children
136 e = nodes.Element()
137 e += nodes.Element()
138 e += nodes.Text('sample')
139 e += nodes.Element()
140 e += nodes.Text('other sample')
141 e += nodes.Text('sample')
142 # return element's index for the first four children:
143 for i in range(4):
144 self.assertEqual(e.index(e[i]), i)
145 # Caution: mismatches are possible for Text nodes
146 # as they are compared by value (like `str` instances)
147 self.assertEqual(e.index(e[4]), 1)
148 self.assertEqual(e.index(e[4], start=2), 4)
150 def test_previous_sibling(self):
151 e = nodes.Element()
152 c1 = nodes.Element()
153 c2 = nodes.Element()
154 e += [c1, c2]
155 # print(c1 == c2)
156 self.assertEqual(e.previous_sibling(), None)
157 self.assertEqual(c1.previous_sibling(), None)
158 self.assertEqual(c2.previous_sibling(), c1)
160 def test_clear(self):
161 element = nodes.Element()
162 element += nodes.Element()
163 self.assertTrue(len(element))
164 element.clear()
165 self.assertTrue(not len(element))
167 def test_normal_attributes(self):
168 element = nodes.Element()
169 self.assertTrue('foo' not in element)
170 self.assertRaises(KeyError, element.__getitem__, 'foo')
171 element['foo'] = 'sometext'
172 self.assertEqual(element['foo'], 'sometext')
173 del element['foo']
174 self.assertRaises(KeyError, element.__getitem__, 'foo')
176 def test_default_attributes(self):
177 element = nodes.Element()
178 self.assertEqual(element['ids'], [])
179 self.assertEqual(element.non_default_attributes(), {})
180 self.assertTrue(not element.is_not_default('ids'))
181 self.assertTrue(element['ids'] is not nodes.Element()['ids'])
182 element['ids'].append('someid')
183 self.assertEqual(element['ids'], ['someid'])
184 self.assertEqual(element.non_default_attributes(),
185 {'ids': ['someid']})
186 self.assertTrue(element.is_not_default('ids'))
188 def test_update_basic_atts(self):
189 element1 = nodes.Element(ids=['foo', 'bar'], test=['test1'])
190 element2 = nodes.Element(ids=['baz', 'qux'], test=['test2'])
191 element1.update_basic_atts(element2)
192 # 'ids' are appended because 'ids' is a basic attribute.
193 self.assertEqual(element1['ids'], ['foo', 'bar', 'baz', 'qux'])
194 # 'test' is not overwritten because it is not a basic attribute.
195 self.assertEqual(element1['test'], ['test1'])
197 def test_update_all_atts(self):
198 # Note: Also tests is_not_list_attribute and is_not_known_attribute
199 # and various helpers
200 # Test for full attribute replacement
201 element1 = nodes.Element(ids=['foo', 'bar'], parent_only='parent',
202 all_nodes='mom')
203 element2 = nodes.Element(ids=['baz', 'qux'], child_only='child',
204 all_nodes='dad', source='source')
206 # Test for when same fields are replaced as well as source...
207 element1.update_all_atts_consistantly(element2, True, True)
208 # 'ids' are appended because 'ids' is a basic attribute.
209 self.assertEqual(element1['ids'], ['foo', 'bar', 'baz', 'qux'])
210 # 'parent_only' should remain unaffected.
211 self.assertEqual(element1['parent_only'], 'parent')
212 # 'all_nodes' is overwritten due to the second parameter == True.
213 self.assertEqual(element1['all_nodes'], 'dad')
214 # 'child_only' should have been added.
215 self.assertEqual(element1['child_only'], 'child')
216 # 'source' is also overwritten due to the third parameter == True.
217 self.assertEqual(element1['source'], 'source')
219 # Test for when same fields are replaced but not source...
220 element1 = nodes.Element(ids=['foo', 'bar'], parent_only='parent',
221 all_nodes='mom')
222 element1.update_all_atts_consistantly(element2)
223 # 'ids' are appended because 'ids' is a basic attribute.
224 self.assertEqual(element1['ids'], ['foo', 'bar', 'baz', 'qux'])
225 # 'parent_only' should remain unaffected.
226 self.assertEqual(element1['parent_only'], 'parent')
227 # 'all_nodes' is overwritten due to the second parameter default True.
228 self.assertEqual(element1['all_nodes'], 'dad')
229 # 'child_only' should have been added.
230 self.assertEqual(element1['child_only'], 'child')
231 # 'source' remains unset due to the third parameter default of False.
232 self.assertEqual(element1.get('source'), None)
234 # Test for when fields are NOT replaced but source is...
235 element1 = nodes.Element(ids=['foo', 'bar'], parent_only='parent',
236 all_nodes='mom')
237 element1.update_all_atts_consistantly(element2, False, True)
238 # 'ids' are appended because 'ids' is a basic attribute.
239 self.assertEqual(element1['ids'], ['foo', 'bar', 'baz', 'qux'])
240 # 'parent_only' should remain unaffected.
241 self.assertEqual(element1['parent_only'], 'parent')
242 # 'all_nodes' is preserved due to the second parameter == False.
243 self.assertEqual(element1['all_nodes'], 'mom')
244 # 'child_only' should have been added.
245 self.assertEqual(element1['child_only'], 'child')
246 # 'source' is added due to the third parameter == True.
247 self.assertEqual(element1['source'], 'source')
248 element1 = nodes.Element(source='destination')
249 element1.update_all_atts_consistantly(element2, False, True)
250 # 'source' remains unchanged due to the second parameter == False.
251 self.assertEqual(element1['source'], 'destination')
253 # Test for when same fields are replaced but not source...
254 element1 = nodes.Element(ids=['foo', 'bar'], parent_only='parent',
255 all_nodes='mom')
256 element1.update_all_atts_consistantly(element2, False)
257 # 'ids' are appended because 'ids' is a basic attribute.
258 self.assertEqual(element1['ids'], ['foo', 'bar', 'baz', 'qux'])
259 # 'parent_only' should remain unaffected.
260 self.assertEqual(element1['parent_only'], 'parent')
261 # 'all_nodes' is preserved due to the second parameter == False.
262 self.assertEqual(element1['all_nodes'], 'mom')
263 # 'child_only' should have been added.
264 self.assertEqual(element1['child_only'], 'child')
265 # 'source' remains unset due to the third parameter default of False.
266 self.assertEqual(element1.get('source'), None)
268 # Test for List attribute merging
269 # Attribute Concatination
270 element1 = nodes.Element(ss='a', sl='1', ls=['I'], ll=['A'])
271 element2 = nodes.Element(ss='b', sl=['2'], ls='II', ll=['B'])
272 element1.update_all_atts_concatenating(element2)
273 # 'ss' is replaced because non-list
274 self.assertEqual(element1['ss'], 'b')
275 # 'sl' is replaced because they are both not lists
276 self.assertEqual(element1['sl'], ['2'])
277 # 'ls' is replaced because they are both not lists
278 self.assertEqual(element1['ls'], 'II')
279 # 'll' is extended because they are both lists
280 self.assertEqual(element1['ll'], ['A', 'B'])
282 # Attribute Coercion
283 element1 = nodes.Element(ss='a', sl='1', ls=['I'], ll=['A'])
284 element2 = nodes.Element(ss='b', sl=['2'], ls='II', ll=['B'])
285 element1.update_all_atts_coercion(element2)
286 # 'ss' is replaced because non-list
287 self.assertEqual(element1['ss'], 'b')
288 # 'sl' is converted to a list and appended because element2 has a list
289 self.assertEqual(element1['sl'], ['1', '2'])
290 # 'ls' has element2's value appended to the list
291 self.assertEqual(element1['ls'], ['I', 'II'])
292 # 'll' is extended because they are both lists
293 self.assertEqual(element1['ll'], ['A', 'B'])
295 # Attribute Conversion
296 element1 = nodes.Element(ss='a', sl='1', ls=['I'], ll=['A'])
297 element2 = nodes.Element(ss='b', sl=['2'], ls='II', ll=['B'])
298 element1.update_all_atts_convert(element2)
299 # 'ss' is converted to a list with the values from each element
300 self.assertEqual(element1['ss'], ['a', 'b'])
301 # 'sl' is converted to a list and appended
302 self.assertEqual(element1['sl'], ['1', '2'])
303 # 'ls' has element2's value appended to the list
304 self.assertEqual(element1['ls'], ['I', 'II'])
305 # 'll' is extended
306 self.assertEqual(element1['ll'], ['A', 'B'])
308 def test_replace_self(self):
309 parent = nodes.Element(ids=['parent'])
310 child1 = nodes.Element(ids=['child1'])
311 grandchild = nodes.Element(ids=['grandchild'])
312 child1 += grandchild
313 child2 = nodes.Element(ids=['child2'])
314 twins = [nodes.Element(ids=['twin%s' % i]) for i in (1, 2)]
315 child2 += twins
316 child3 = nodes.Element(ids=['child3'])
317 child4 = nodes.Element(ids=['child4'])
318 parent += [child1, child2, child3, child4]
319 self.assertEqual(parent.pformat(), """\
320 <Element ids="parent">
321 <Element ids="child1">
322 <Element ids="grandchild">
323 <Element ids="child2">
324 <Element ids="twin1">
325 <Element ids="twin2">
326 <Element ids="child3">
327 <Element ids="child4">
328 """)
329 # Replace child1 with the grandchild.
330 child1.replace_self(child1[0])
331 self.assertEqual(parent[0], grandchild)
332 # Assert that 'ids' have been updated.
333 self.assertEqual(grandchild['ids'], ['grandchild', 'child1'])
334 # Replace child2 with its children.
335 child2.replace_self(child2[:])
336 self.assertEqual(parent[1:3], twins)
337 # Assert that 'ids' have been propagated to first child.
338 self.assertEqual(twins[0]['ids'], ['twin1', 'child2'])
339 self.assertEqual(twins[1]['ids'], ['twin2'])
340 # Replace child3 with new child.
341 newchild = nodes.Element(ids=['newchild'])
342 child3.replace_self(newchild)
343 self.assertEqual(parent[3], newchild)
344 self.assertEqual(newchild['ids'], ['newchild', 'child3'])
345 # Crazy but possible case: Substitute child4 for itself.
346 child4.replace_self(child4)
347 # Make sure the 'child4' ID hasn't been duplicated.
348 self.assertEqual(child4['ids'], ['child4'])
349 self.assertEqual(len(parent), 5)
351 def test_unicode(self):
352 node = nodes.Element('Möhren', nodes.Text('Möhren'))
353 self.assertEqual(str(node), '<Element>Möhren</Element>')
355 def test_set_class_deprecation_warning(self):
356 node = nodes.Element('test node')
357 with self.assertWarns(DeprecationWarning):
358 node.set_class('parrot')
361 class MiscTests(unittest.TestCase):
363 def test_node_class_names(self):
364 node_class_names = []
365 for x in dir(nodes):
366 c = getattr(nodes, x)
367 if (isinstance(c, type)
368 and issubclass(c, nodes.Node)
369 and len(c.__bases__) > 1):
370 node_class_names.append(x)
371 node_class_names.sort()
372 nodes.node_class_names.sort()
373 self.assertEqual(node_class_names, nodes.node_class_names)
375 ids = [('a', 'a'), ('A', 'a'), ('', ''), ('a b \n c', 'a-b-c'),
376 ('a.b.c', 'a-b-c'), (' - a - b - c - ', 'a-b-c'), (' - ', ''),
377 ('\u2020\u2066', ''), ('a \xa7 b \u2020 c', 'a-b-c'),
378 ('1', ''), ('1abc', 'abc'),
380 ids_unicode_all = [
381 ('\u00f8 o with stroke', 'o-o-with-stroke'),
382 ('\u0111 d with stroke', 'd-d-with-stroke'),
383 ('\u0127 h with stroke', 'h-h-with-stroke'),
384 ('\u0131 dotless i', 'i-dotless-i'),
385 ('\u0142 l with stroke', 'l-l-with-stroke'),
386 ('\u0167 t with stroke', 't-t-with-stroke'),
387 # From Latin Extended-B
388 ('\u0180 b with stroke', 'b-b-with-stroke'),
389 ('\u0183 b with topbar', 'b-b-with-topbar'),
390 ('\u0188 c with hook', 'c-c-with-hook'),
391 ('\u018c d with topbar', 'd-d-with-topbar'),
392 ('\u0192 f with hook', 'f-f-with-hook'),
393 ('\u0199 k with hook', 'k-k-with-hook'),
394 ('\u019a l with bar', 'l-l-with-bar'),
395 ('\u019e n with long right leg', 'n-n-with-long-right-leg'),
396 ('\u01a5 p with hook', 'p-p-with-hook'),
397 ('\u01ab t with palatal hook', 't-t-with-palatal-hook'),
398 ('\u01ad t with hook', 't-t-with-hook'),
399 ('\u01b4 y with hook', 'y-y-with-hook'),
400 ('\u01b6 z with stroke', 'z-z-with-stroke'),
401 ('\u01e5 g with stroke', 'g-g-with-stroke'),
402 ('\u0225 z with hook', 'z-z-with-hook'),
403 ('\u0234 l with curl', 'l-l-with-curl'),
404 ('\u0235 n with curl', 'n-n-with-curl'),
405 ('\u0236 t with curl', 't-t-with-curl'),
406 ('\u0237 dotless j', 'j-dotless-j'),
407 ('\u023c c with stroke', 'c-c-with-stroke'),
408 ('\u023f s with swash tail', 's-s-with-swash-tail'),
409 ('\u0240 z with swash tail', 'z-z-with-swash-tail'),
410 ('\u0247 e with stroke', 'e-e-with-stroke'),
411 ('\u0249 j with stroke', 'j-j-with-stroke'),
412 ('\u024b q with hook tail', 'q-q-with-hook-tail'),
413 ('\u024d r with stroke', 'r-r-with-stroke'),
414 ('\u024f y with stroke', 'y-y-with-stroke'),
415 # From Latin-1 Supplements
416 ('\u00e0: a with grave', 'a-a-with-grave'),
417 ('\u00e1 a with acute', 'a-a-with-acute'),
418 ('\u00e2 a with circumflex', 'a-a-with-circumflex'),
419 ('\u00e3 a with tilde', 'a-a-with-tilde'),
420 ('\u00e4 a with diaeresis', 'a-a-with-diaeresis'),
421 ('\u00e5 a with ring above', 'a-a-with-ring-above'),
422 ('\u00e7 c with cedilla', 'c-c-with-cedilla'),
423 ('\u00e8 e with grave', 'e-e-with-grave'),
424 ('\u00e9 e with acute', 'e-e-with-acute'),
425 ('\u00ea e with circumflex', 'e-e-with-circumflex'),
426 ('\u00eb e with diaeresis', 'e-e-with-diaeresis'),
427 ('\u00ec i with grave', 'i-i-with-grave'),
428 ('\u00ed i with acute', 'i-i-with-acute'),
429 ('\u00ee i with circumflex', 'i-i-with-circumflex'),
430 ('\u00ef i with diaeresis', 'i-i-with-diaeresis'),
431 ('\u00f1 n with tilde', 'n-n-with-tilde'),
432 ('\u00f2 o with grave', 'o-o-with-grave'),
433 ('\u00f3 o with acute', 'o-o-with-acute'),
434 ('\u00f4 o with circumflex', 'o-o-with-circumflex'),
435 ('\u00f5 o with tilde', 'o-o-with-tilde'),
436 ('\u00f6 o with diaeresis', 'o-o-with-diaeresis'),
437 ('\u00f9 u with grave', 'u-u-with-grave'),
438 ('\u00fa u with acute', 'u-u-with-acute'),
439 ('\u00fb u with circumflex', 'u-u-with-circumflex'),
440 ('\u00fc u with diaeresis', 'u-u-with-diaeresis'),
441 ('\u00fd y with acute', 'y-y-with-acute'),
442 ('\u00ff y with diaeresis', 'y-y-with-diaeresis'),
443 # From Latin Extended-A
444 ('\u0101 a with macron', 'a-a-with-macron'),
445 ('\u0103 a with breve', 'a-a-with-breve'),
446 ('\u0105 a with ogonek', 'a-a-with-ogonek'),
447 ('\u0107 c with acute', 'c-c-with-acute'),
448 ('\u0109 c with circumflex', 'c-c-with-circumflex'),
449 ('\u010b c with dot above', 'c-c-with-dot-above'),
450 ('\u010d c with caron', 'c-c-with-caron'),
451 ('\u010f d with caron', 'd-d-with-caron'),
452 ('\u0113 e with macron', 'e-e-with-macron'),
453 ('\u0115 e with breve', 'e-e-with-breve'),
454 ('\u0117 e with dot above', 'e-e-with-dot-above'),
455 ('\u0119 e with ogonek', 'e-e-with-ogonek'),
456 ('\u011b e with caron', 'e-e-with-caron'),
457 ('\u011d g with circumflex', 'g-g-with-circumflex'),
458 ('\u011f g with breve', 'g-g-with-breve'),
459 ('\u0121 g with dot above', 'g-g-with-dot-above'),
460 ('\u0123 g with cedilla', 'g-g-with-cedilla'),
461 ('\u0125 h with circumflex', 'h-h-with-circumflex'),
462 ('\u0129 i with tilde', 'i-i-with-tilde'),
463 ('\u012b i with macron', 'i-i-with-macron'),
464 ('\u012d i with breve', 'i-i-with-breve'),
465 ('\u012f i with ogonek', 'i-i-with-ogonek'),
466 ('\u0133 ligature ij', 'ij-ligature-ij'),
467 ('\u0135 j with circumflex', 'j-j-with-circumflex'),
468 ('\u0137 k with cedilla', 'k-k-with-cedilla'),
469 ('\u013a l with acute', 'l-l-with-acute'),
470 ('\u013c l with cedilla', 'l-l-with-cedilla'),
471 ('\u013e l with caron', 'l-l-with-caron'),
472 ('\u0140 l with middle dot', 'l-l-with-middle-dot'),
473 ('\u0144 n with acute', 'n-n-with-acute'),
474 ('\u0146 n with cedilla', 'n-n-with-cedilla'),
475 ('\u0148 n with caron', 'n-n-with-caron'),
476 ('\u014d o with macron', 'o-o-with-macron'),
477 ('\u014f o with breve', 'o-o-with-breve'),
478 ('\u0151 o with double acute', 'o-o-with-double-acute'),
479 ('\u0155 r with acute', 'r-r-with-acute'),
480 ('\u0157 r with cedilla', 'r-r-with-cedilla'),
481 ('\u0159 r with caron', 'r-r-with-caron'),
482 ('\u015b s with acute', 's-s-with-acute'),
483 ('\u015d s with circumflex', 's-s-with-circumflex'),
484 ('\u015f s with cedilla', 's-s-with-cedilla'),
485 ('\u0161 s with caron', 's-s-with-caron'),
486 ('\u0163 t with cedilla', 't-t-with-cedilla'),
487 ('\u0165 t with caron', 't-t-with-caron'),
488 ('\u0169 u with tilde', 'u-u-with-tilde'),
489 ('\u016b u with macron', 'u-u-with-macron'),
490 ('\u016d u with breve', 'u-u-with-breve'),
491 ('\u016f u with ring above', 'u-u-with-ring-above'),
492 ('\u0171 u with double acute', 'u-u-with-double-acute'),
493 ('\u0173 u with ogonek', 'u-u-with-ogonek'),
494 ('\u0175 w with circumflex', 'w-w-with-circumflex'),
495 ('\u0177 y with circumflex', 'y-y-with-circumflex'),
496 ('\u017a z with acute', 'z-z-with-acute'),
497 ('\u017c z with dot above', 'z-z-with-dot-above'),
498 ('\u017e z with caron', 'z-z-with-caron'),
499 # From Latin Extended-B
500 ('\u01a1 o with horn', 'o-o-with-horn'),
501 ('\u01b0 u with horn', 'u-u-with-horn'),
502 ('\u01c6 dz with caron', 'dz-dz-with-caron'),
503 ('\u01c9 lj', 'lj-lj'),
504 ('\u01cc nj', 'nj-nj'),
505 ('\u01ce a with caron', 'a-a-with-caron'),
506 ('\u01d0 i with caron', 'i-i-with-caron'),
507 ('\u01d2 o with caron', 'o-o-with-caron'),
508 ('\u01d4 u with caron', 'u-u-with-caron'),
509 ('\u01e7 g with caron', 'g-g-with-caron'),
510 ('\u01e9 k with caron', 'k-k-with-caron'),
511 ('\u01eb o with ogonek', 'o-o-with-ogonek'),
512 ('\u01ed o with ogonek and macron', 'o-o-with-ogonek-and-macron'),
513 ('\u01f0 j with caron', 'j-j-with-caron'),
514 ('\u01f3 dz', 'dz-dz'),
515 ('\u01f5 g with acute', 'g-g-with-acute'),
516 ('\u01f9 n with grave', 'n-n-with-grave'),
517 ('\u0201 a with double grave', 'a-a-with-double-grave'),
518 ('\u0203 a with inverted breve', 'a-a-with-inverted-breve'),
519 ('\u0205 e with double grave', 'e-e-with-double-grave'),
520 ('\u0207 e with inverted breve', 'e-e-with-inverted-breve'),
521 ('\u0209 i with double grave', 'i-i-with-double-grave'),
522 ('\u020b i with inverted breve', 'i-i-with-inverted-breve'),
523 ('\u020d o with double grave', 'o-o-with-double-grave'),
524 ('\u020f o with inverted breve', 'o-o-with-inverted-breve'),
525 ('\u0211 r with double grave', 'r-r-with-double-grave'),
526 ('\u0213 r with inverted breve', 'r-r-with-inverted-breve'),
527 ('\u0215 u with double grave', 'u-u-with-double-grave'),
528 ('\u0217 u with inverted breve', 'u-u-with-inverted-breve'),
529 ('\u0219 s with comma below', 's-s-with-comma-below'),
530 ('\u021b t with comma below', 't-t-with-comma-below'),
531 ('\u021f h with caron', 'h-h-with-caron'),
532 ('\u0227 a with dot above', 'a-a-with-dot-above'),
533 ('\u0229 e with cedilla', 'e-e-with-cedilla'),
534 ('\u022f o with dot above', 'o-o-with-dot-above'),
535 ('\u0233 y with macron', 'y-y-with-macron'),
536 # digraphs From Latin-1 Supplements
537 ('\u00df: ligature sz', 'sz-ligature-sz'),
538 ('\u00e6 ae', 'ae-ae'),
539 ('\u0153 ligature oe', 'oe-ligature-oe'),
540 ('\u0238 db digraph', 'db-db-digraph'),
541 ('\u0239 qp digraph', 'qp-qp-digraph'),
544 def test_make_id(self):
545 failures = []
546 tests = self.ids + self.ids_unicode_all
547 for input, expect in tests:
548 output = nodes.make_id(input)
549 if expect != output:
550 failures.append("'%s' != '%s'" % (expect, output))
551 if failures:
552 self.fail(f'{len(failures)} failures in {len(self.ids)} ids\n'
553 + "\n".join(failures))
555 def test_findall(self):
556 e = nodes.Element()
557 e += nodes.Element()
558 e[0] += nodes.Element()
559 e[0] += nodes.TextElement()
560 e[0][1] += nodes.Text('some text')
561 e += nodes.Element()
562 e += nodes.Element()
563 self.assertEqual(list(e.findall()),
564 [e, e[0], e[0][0], e[0][1], e[0][1][0], e[1], e[2]])
565 self.assertEqual(list(e.findall(include_self=False)),
566 [e[0], e[0][0], e[0][1], e[0][1][0], e[1], e[2]])
567 self.assertEqual(list(e.findall(descend=False)),
568 [e])
569 self.assertEqual(list(e[0].findall(descend=False, ascend=True)),
570 [e[0], e[1], e[2]])
571 self.assertEqual(list(e[0][0].findall(descend=False, ascend=True)),
572 [e[0][0], e[0][1], e[1], e[2]])
573 self.assertEqual(list(e[0][0].findall(descend=False, siblings=True)),
574 [e[0][0], e[0][1]])
575 self.testlist = e[0:2]
576 self.assertEqual(list(e.findall(condition=self.not_in_testlist)),
577 [e, e[0][0], e[0][1], e[0][1][0], e[2]])
578 # Return siblings despite siblings=False because ascend is true.
579 self.assertEqual(list(e[1].findall(ascend=True, siblings=False)),
580 [e[1], e[2]])
581 self.assertEqual(list(e[0].findall()),
582 [e[0], e[0][0], e[0][1], e[0][1][0]])
583 self.testlist = [e[0][0], e[0][1]]
584 self.assertEqual(list(e[0].findall(condition=self.not_in_testlist)),
585 [e[0], e[0][1][0]])
586 self.testlist.append(e[0][1][0])
587 self.assertEqual(list(e[0].findall(condition=self.not_in_testlist)),
588 [e[0]])
589 self.assertEqual(list(e.findall(nodes.TextElement)), [e[0][1]])
591 def test_findall_duplicate_texts(self):
592 e = nodes.Element()
593 e += nodes.TextElement()
594 e[0] += nodes.Text('one')
595 e[0] += nodes.Text('two')
596 e[0] += nodes.Text('three')
597 e[0] += nodes.Text('two')
598 e[0] += nodes.Text('five')
599 full_list = list(e[0][0].findall(siblings=True))
600 self.assertEqual(len(full_list), 5)
601 for i in range(5):
602 self.assertIs(full_list[i], e[0][i])
604 partial_list = list(e[0][3].findall(siblings=True))
605 self.assertEqual(len(partial_list), 2)
606 self.assertIs(partial_list[0], e[0][3])
607 self.assertIs(partial_list[1], e[0][4])
609 def test_next_node(self):
610 e = nodes.Element()
611 e += nodes.Element()
612 e[0] += nodes.Element()
613 e[0] += nodes.TextElement()
614 e[0][1] += nodes.Text('some text')
615 e += nodes.Element()
616 e += nodes.Element()
617 self.testlist = [e[0], e[0][1], e[1]]
618 compare = [(e, e[0][0]),
619 (e[0], e[0][0]),
620 (e[0][0], e[0][1][0]),
621 (e[0][1], e[0][1][0]),
622 (e[0][1][0], e[2]),
623 (e[1], e[2]),
624 (e[2], None)]
625 for node, next_node in compare:
626 self.assertEqual(node.next_node(self.not_in_testlist, ascend=True),
627 next_node)
628 self.assertEqual(e[0][0].next_node(ascend=True), e[0][1])
629 self.assertEqual(e[2].next_node(), None)
631 def not_in_testlist(self, x):
632 return x not in self.testlist
634 def test_copy(self):
635 grandchild = nodes.Text('grandchild text')
636 child = nodes.emphasis('childtext', grandchild, att='child')
637 e = nodes.Element('raw text', child, att='e')
638 # Shallow copy:
639 e_copy = e.copy()
640 self.assertTrue(e is not e_copy)
641 # Internal attributes (like `rawsource`) are also copied.
642 self.assertEqual(e.rawsource, 'raw text')
643 self.assertEqual(e_copy.rawsource, e.rawsource)
644 self.assertEqual(e_copy['att'], 'e')
645 self.assertEqual(e_copy.document, e.document)
646 self.assertEqual(e_copy.source, e.source)
647 self.assertEqual(e_copy.line, e.line)
648 # Children are not copied.
649 self.assertEqual(len(e_copy), 0)
650 # Deep copy:
651 e_deepcopy = e.deepcopy()
652 self.assertEqual(e_deepcopy.rawsource, e.rawsource)
653 self.assertEqual(e_deepcopy['att'], 'e')
654 # Children are copied recursively.
655 self.assertEqual(e_deepcopy[0][0], grandchild)
656 self.assertTrue(e_deepcopy[0][0] is not grandchild)
657 self.assertEqual(e_deepcopy[0]['att'], 'child')
659 def test_system_message_copy(self):
660 e = nodes.system_message('mytext', att='e', rawsource='raw text')
661 # Shallow copy:
662 e_copy = e.copy()
663 self.assertTrue(e is not e_copy)
664 # Internal attributes (like `rawsource`) are also copied.
665 self.assertEqual(e.rawsource, 'raw text')
666 self.assertEqual(e_copy.rawsource, e.rawsource)
667 self.assertEqual(e_copy['att'], 'e')
670 class TreeCopyVisitorTests(unittest.TestCase):
672 def setUp(self):
673 document = utils.new_document('test data')
674 document += nodes.paragraph('', 'Paragraph 1.')
675 blist = nodes.bullet_list()
676 for i in range(1, 6):
677 item = nodes.list_item()
678 for j in range(1, 4):
679 item += nodes.paragraph('', 'Item %s, paragraph %s.' % (i, j))
680 blist += item
681 document += blist
682 self.document = document
684 def compare_trees(self, one, two):
685 self.assertEqual(one.__class__, two.__class__)
686 self.assertNotEqual(id(one), id(two))
687 self.assertEqual(len(one.children), len(two.children))
688 for i in range(len(one.children)):
689 self.compare_trees(one.children[i], two.children[i])
691 def test_copy_whole(self):
692 visitor = nodes.TreeCopyVisitor(self.document)
693 self.document.walkabout(visitor)
694 newtree = visitor.get_tree_copy()
695 self.assertEqual(self.document.pformat(), newtree.pformat())
696 self.compare_trees(self.document, newtree)
699 class SetIdTests(unittest.TestCase):
701 def setUp(self):
702 self.document = utils.new_document('test')
703 self.elements = [nodes.Element(names=['test']),
704 nodes.section(), # Name empty
705 nodes.section(names=['Test']), # duplicate id
706 nodes.footnote(names=['2019-10-30']), # id empty
709 def test_set_id_default(self):
710 # Default prefixes.
711 for element in self.elements:
712 self.document.set_id(element)
713 ids = [element['ids'] for element in self.elements]
714 self.assertEqual(ids, [['test'], ['section-1'],
715 ['test-1'], ['footnote-1']])
717 def test_set_id_custom(self):
718 # Custom prefixes.
720 # Change settings.
721 self.document.settings.id_prefix = 'P-'
722 self.document.settings.auto_id_prefix = 'auto'
724 for element in self.elements:
725 self.document.set_id(element)
726 ids = [element['ids'] for element in self.elements]
727 self.assertEqual(ids, [['P-test'],
728 ['P-auto1'],
729 ['P-auto2'],
730 ['P-2019-10-30']])
732 def test_set_id_descriptive_auto_id(self):
733 # Use name or tag-name for auto-id.
735 # Change setting.
736 self.document.settings.auto_id_prefix = '%'
738 for element in self.elements:
739 self.document.set_id(element)
740 ids = [element['ids'] for element in self.elements]
741 self.assertEqual(ids, [['test'],
742 ['section-1'],
743 ['test-1'],
744 ['footnote-1']])
746 def test_set_id_custom_descriptive_auto_id(self):
747 # Custom prefixes and name or tag-name for auto-id.
749 # Change settings.
750 self.document.settings.id_prefix = 'P:'
751 self.document.settings.auto_id_prefix = 'a-%'
753 for element in self.elements:
754 self.document.set_id(element)
755 ids = [element['ids'] for element in self.elements]
756 self.assertEqual(ids, [['P:test'],
757 ['P:a-section-1'],
758 ['P:test-1'],
759 ['P:2019-10-30']])
762 class NodeVisitorTests(unittest.TestCase):
763 def setUp(self):
764 self.document = utils.new_document('test')
765 self.element = nodes.Element()
766 self.visitor = nodes.NodeVisitor(self.document)
768 def test_dispatch_visit_unknown(self):
769 # raise exception if no visit/depart methods are defined for node class
770 with self.assertRaises(NotImplementedError):
771 self.visitor.dispatch_visit(self.element)
773 def test_dispatch_visit_optional(self):
774 # silently skip nodes of a calss in tuple nodes.NodeVisitor.optional
775 rv = self.visitor.dispatch_visit(nodes.meta())
776 self.assertIsNone(rv)
779 class MiscFunctionTests(unittest.TestCase):
781 names = [('a', 'a'), ('A', 'a'), ('A a A', 'a a a'),
782 ('A a A a', 'a a a a'),
783 (' AaA\n\r\naAa\tAaA\t\t', 'aaa aaa aaa')]
785 def test_normalize_name(self):
786 for input, output in self.names:
787 normed = nodes.fully_normalize_name(input)
788 self.assertEqual(normed, output)
791 if __name__ == '__main__':
792 unittest.main()