1 #! /usr/bin/env python3
3 # Author: David Goodger <goodger@python.org>
4 # Copyright: This module has been placed in the public domain.
7 Test module for nodes.py.
10 from pathlib
import Path
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
24 class TextTests(unittest
.TestCase
):
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.')
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'>")
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')
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 "
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
):
88 element
= nodes
.Element()
89 self
.assertEqual(repr(element
), '<Element: >')
90 self
.assertEqual(str(element
), '<Element/>')
92 self
.assertEqual(dom
.toxml(), '<Element/>')
95 self
.assertEqual(repr(element
), '<Element: >')
96 self
.assertEqual(str(element
), '<Element attr="1"/>')
98 self
.assertEqual(dom
.toxml(), '<Element attr="1"/>')
100 self
.assertEqual(element
.pformat(), '<Element attr="1">\n')
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"/>')
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>')
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>')
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
138 e
+= nodes
.Text('sample')
140 e
+= nodes
.Text('other sample')
141 e
+= nodes
.Text('sample')
142 # return element's index for the first four children:
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
):
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
))
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')
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(),
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',
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',
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',
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',
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'])
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'])
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'])
313 child2
= nodes
.Element(ids
=['child2'])
314 twins
= [nodes
.Element(ids
=['twin%s' % i
]) for i
in (1, 2)]
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">
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
= []
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'),
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
):
546 tests
= self
.ids
+ self
.ids_unicode_all
547 for input, expect
in tests
:
548 output
= nodes
.make_id(input)
550 failures
.append("'%s' != '%s'" % (expect
, output
))
552 self
.fail(f
'{len(failures)} failures in {len(self.ids)} ids\n'
553 + "\n".join(failures
))
555 def test_findall(self
):
558 e
[0] += nodes
.Element()
559 e
[0] += nodes
.TextElement()
560 e
[0][1] += nodes
.Text('some text')
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)),
569 self
.assertEqual(list(e
[0].findall(descend
=False, ascend
=True)),
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)),
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)),
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
)),
586 self
.testlist
.append(e
[0][1][0])
587 self
.assertEqual(list(e
[0].findall(condition
=self
.not_in_testlist
)),
589 self
.assertEqual(list(e
.findall(nodes
.TextElement
)), [e
[0][1]])
591 def test_findall_duplicate_texts(self
):
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)
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
):
612 e
[0] += nodes
.Element()
613 e
[0] += nodes
.TextElement()
614 e
[0][1] += nodes
.Text('some text')
617 self
.testlist
= [e
[0], e
[0][1], e
[1]]
618 compare
= [(e
, e
[0][0]),
620 (e
[0][0], e
[0][1][0]),
621 (e
[0][1], e
[0][1][0]),
625 for node
, next_node
in compare
:
626 self
.assertEqual(node
.next_node(self
.not_in_testlist
, ascend
=True),
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
635 grandchild
= nodes
.Text('grandchild text')
636 child
= nodes
.emphasis('childtext', grandchild
, att
='child')
637 e
= nodes
.Element('raw text', child
, att
='e')
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)
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')
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
):
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
))
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
):
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
):
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
):
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'],
732 def test_set_id_descriptive_auto_id(self
):
733 # Use name or tag-name for auto-id.
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'],
746 def test_set_id_custom_descriptive_auto_id(self
):
747 # Custom prefixes and name or tag-name for auto-id.
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'],
762 class NodeVisitorTests(unittest
.TestCase
):
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__':