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 NodeTests(unittest
.TestCase
):
26 def not_in_testlist(self
, x
):
27 # function to use in `condition` argument in findall() and next_node()
28 return x
not in self
.testlist
30 def test_findall(self
):
31 # `findall()` is defined in class Node,
32 # we test with a tree of Element instances (simpler setup)
35 e
[0] += nodes
.Element()
36 e
[0] += nodes
.TextElement()
37 e
[0][1] += nodes
.Text('some text')
40 self
.assertEqual(list(e
.findall()),
41 [e
, e
[0], e
[0][0], e
[0][1], e
[0][1][0], e
[1], e
[2]])
42 self
.assertEqual(list(e
.findall(include_self
=False)),
43 [e
[0], e
[0][0], e
[0][1], e
[0][1][0], e
[1], e
[2]])
44 self
.assertEqual(list(e
.findall(descend
=False)),
46 self
.assertEqual(list(e
[0].findall(descend
=False, ascend
=True)),
48 self
.assertEqual(list(e
[0][0].findall(descend
=False, ascend
=True)),
49 [e
[0][0], e
[0][1], e
[1], e
[2]])
50 self
.assertEqual(list(e
[0][0].findall(descend
=False, siblings
=True)),
52 self
.testlist
= e
[0:2]
53 self
.assertEqual(list(e
.findall(condition
=self
.not_in_testlist
)),
54 [e
, e
[0][0], e
[0][1], e
[0][1][0], e
[2]])
55 # Return siblings despite siblings=False because ascend is true.
56 self
.assertEqual(list(e
[1].findall(ascend
=True, siblings
=False)),
58 self
.assertEqual(list(e
[0].findall()),
59 [e
[0], e
[0][0], e
[0][1], e
[0][1][0]])
60 self
.testlist
= [e
[0][0], e
[0][1]]
61 self
.assertEqual(list(e
[0].findall(condition
=self
.not_in_testlist
)),
63 self
.testlist
.append(e
[0][1][0])
64 self
.assertEqual(list(e
[0].findall(condition
=self
.not_in_testlist
)),
66 self
.assertEqual(list(e
.findall(nodes
.TextElement
)), [e
[0][1]])
68 def test_findall_duplicate_texts(self
):
70 e
+= nodes
.TextElement()
71 e
[0] += nodes
.Text('one') # e[0][0]
72 e
[0] += nodes
.Text('two') # e[0][1]
73 e
[0] += nodes
.Text('three') # e[0][2]
74 e
[0] += nodes
.Text('two') # e[0][3] same value as e[0][1]
75 e
[0] += nodes
.Text('five') # e[0][4]
76 full_list
= list(e
[0][0].findall(siblings
=True))
77 self
.assertEqual(len(full_list
), 5)
79 self
.assertIs(full_list
[i
], e
[0][i
])
81 partial_list
= list(e
[0][3].findall(siblings
=True))
82 self
.assertEqual(len(partial_list
), 2)
83 self
.assertIs(partial_list
[0], e
[0][3])
84 self
.assertIs(partial_list
[1], e
[0][4])
86 def test_next_node(self
):
89 e
[0] += nodes
.Element()
90 e
[0] += nodes
.TextElement()
91 e
[0][1] += nodes
.Text('some text')
94 self
.testlist
= [e
[0], e
[0][1], e
[1]]
95 compare
= [(e
, e
[0][0]),
97 (e
[0][0], e
[0][1][0]),
98 (e
[0][1], e
[0][1][0]),
102 for node
, next_node
in compare
:
103 self
.assertEqual(node
.next_node(self
.not_in_testlist
, ascend
=True),
105 self
.assertEqual(e
[0][0].next_node(ascend
=True), e
[0][1])
106 self
.assertEqual(e
[2].next_node(), None)
109 class TextTests(unittest
.TestCase
):
111 text
= nodes
.Text('Line 1.\n\x00rad två.')
112 longtext
= nodes
.Text('Mary had a little lamb '
113 'whose fleece was white as snow '
114 'and everwhere that Mary went '
115 'the lamb was sure to go.')
117 def test_value_type_check(self
):
118 # data must by `str` instance, no `bytes` allowed
119 with self
.assertRaises(TypeError):
122 def test_Text_rawsource_deprection_warning(self
):
123 with self
.assertWarnsRegex(DeprecationWarning,
124 '"rawsource" is ignored'):
125 nodes
.Text('content', rawsource
='content')
128 self
.assertEqual(str(self
.text
), 'Line 1.\n\x00rad två.')
131 self
.assertEqual(repr(self
.text
), r
"<#text: 'Line 1.\n\x00rad två.'>")
132 self
.assertEqual(self
.text
.shortrepr(),
133 r
"<#text: 'Line 1.\n\x00rad två.'>")
135 def test_repr_long_text(self
):
136 self
.assertEqual(repr(self
.longtext
), r
"<#text: 'Mary had a "
137 r
"little lamb whose fleece was white as snow "
139 self
.assertEqual(self
.longtext
.shortrepr(),
140 r
"<#text: 'Mary had a lit ...'>")
142 def test_astext(self
):
143 self
.assertEqual(self
.text
.astext(), 'Line 1.\nrad två.')
145 def test_pformat(self
):
146 self
.assertTrue(isinstance(self
.text
.pformat(), str))
147 self
.assertEqual(self
.text
.pformat(), 'Line 1.\nrad två.\n')
149 def test_strip(self
):
150 text
= nodes
.Text(' was noch ')
151 stripped
= text
.lstrip().rstrip()
152 stripped2
= text
.lstrip(' wahn').rstrip(' wahn')
153 self
.assertEqual(stripped
, 'was noch')
154 self
.assertEqual(stripped2
, 's noc')
156 def test_comparison(self
):
157 # Text nodes are compared by value
158 self
.assertEqual(self
.text
, nodes
.Text('Line 1.\n\x00rad två.'))
161 class ElementTests(unittest
.TestCase
):
163 def test_empty(self
):
164 element
= nodes
.Element()
165 self
.assertEqual(repr(element
), '<Element: >')
166 self
.assertEqual(str(element
), '<Element/>')
167 dom
= element
.asdom()
168 self
.assertEqual(dom
.toxml(), '<Element/>')
170 element
['attr'] = '1'
171 self
.assertEqual(repr(element
), '<Element: >')
172 self
.assertEqual(str(element
), '<Element attr="1"/>')
173 dom
= element
.asdom()
174 self
.assertEqual(dom
.toxml(), '<Element attr="1"/>')
176 self
.assertEqual(element
.pformat(), '<Element attr="1">\n')
178 element
['mark'] = '\u2022'
179 self
.assertEqual(repr(element
), '<Element: >')
180 self
.assertEqual(str(element
), '<Element mark="\u2022"/>')
181 dom
= element
.asdom()
182 self
.assertEqual(dom
.toxml(), '<Element mark="\u2022"/>')
184 element
['names'] = ['nobody', 'имя', 'näs']
185 self
.assertEqual(repr(element
), '<Element "nobody; имя; näs": >')
186 self
.assertTrue(isinstance(repr(element
), str))
188 def test_withtext(self
):
189 element
= nodes
.Element('text\nmore', nodes
.Text('text\nmore'))
190 uelement
= nodes
.Element('grün', nodes
.Text('grün'))
191 self
.assertEqual(repr(element
), r
"<Element: <#text: 'text\nmore'>>")
192 self
.assertEqual(repr(uelement
), "<Element: <#text: 'grün'>>")
193 self
.assertTrue(isinstance(repr(uelement
), str))
194 self
.assertEqual(str(element
), '<Element>text\nmore</Element>')
195 self
.assertEqual(str(uelement
), '<Element>gr\xfcn</Element>')
196 dom
= element
.asdom()
197 self
.assertEqual(dom
.toxml(), '<Element>text\nmore</Element>')
199 element
['attr'] = '1'
200 self
.assertEqual(repr(element
), r
"<Element: <#text: 'text\nmore'>>")
201 self
.assertEqual(str(element
),
202 '<Element attr="1">text\nmore</Element>')
203 dom
= element
.asdom()
204 self
.assertEqual(dom
.toxml(),
205 '<Element attr="1">text\nmore</Element>')
207 self
.assertEqual(element
.pformat(),
208 '<Element attr="1">\n text\n more\n')
210 def test_index(self
):
211 # Element.index() behaves like list.index() on the element's children
214 e
+= nodes
.Text('sample')
216 e
+= nodes
.Text('other sample')
217 e
+= nodes
.Text('sample')
218 # return element's index for the first four children:
220 self
.assertEqual(e
.index(e
[i
]), i
)
221 # Caution: mismatches are possible for Text nodes
222 # as they are compared by value (like `str` instances)
223 self
.assertEqual(e
.index(e
[4]), 1)
224 self
.assertEqual(e
.index(e
[4], start
=2), 4)
226 def test_previous_sibling(self
):
232 self
.assertEqual(e
.previous_sibling(), None)
233 self
.assertEqual(c1
.previous_sibling(), None)
234 self
.assertEqual(c2
.previous_sibling(), c1
)
236 def test_clear(self
):
237 element
= nodes
.Element()
238 element
+= nodes
.Element()
239 self
.assertTrue(len(element
))
241 self
.assertTrue(not len(element
))
243 def test_normal_attributes(self
):
244 element
= nodes
.Element()
245 self
.assertTrue('foo' not in element
)
246 self
.assertRaises(KeyError, element
.__getitem
__, 'foo')
247 element
['foo'] = 'sometext'
248 self
.assertEqual(element
['foo'], 'sometext')
250 self
.assertRaises(KeyError, element
.__getitem
__, 'foo')
252 def test_default_attributes(self
):
253 element
= nodes
.Element()
254 self
.assertEqual(element
['ids'], [])
255 self
.assertEqual(element
.non_default_attributes(), {})
256 self
.assertTrue(not element
.is_not_default('ids'))
257 self
.assertTrue(element
['ids'] is not nodes
.Element()['ids'])
258 element
['ids'].append('someid')
259 self
.assertEqual(element
['ids'], ['someid'])
260 self
.assertEqual(element
.non_default_attributes(),
262 self
.assertTrue(element
.is_not_default('ids'))
264 def test_update_basic_atts(self
):
265 element1
= nodes
.Element(ids
=['foo', 'bar'], test
=['test1'])
266 element2
= nodes
.Element(ids
=['baz', 'qux'], test
=['test2'])
267 element1
.update_basic_atts(element2
)
268 # 'ids' are appended because 'ids' is a basic attribute.
269 self
.assertEqual(element1
['ids'], ['foo', 'bar', 'baz', 'qux'])
270 # 'test' is not overwritten because it is not a basic attribute.
271 self
.assertEqual(element1
['test'], ['test1'])
273 def test_update_all_atts(self
):
274 # Note: Also tests is_not_list_attribute and is_not_known_attribute
275 # and various helpers
276 # Test for full attribute replacement
277 element1
= nodes
.Element(ids
=['foo', 'bar'], parent_only
='parent',
279 element2
= nodes
.Element(ids
=['baz', 'qux'], child_only
='child',
280 all_nodes
='dad', source
='source')
282 # Test for when same fields are replaced as well as source...
283 element1
.update_all_atts_consistantly(element2
, True, True)
284 # 'ids' are appended because 'ids' is a basic attribute.
285 self
.assertEqual(element1
['ids'], ['foo', 'bar', 'baz', 'qux'])
286 # 'parent_only' should remain unaffected.
287 self
.assertEqual(element1
['parent_only'], 'parent')
288 # 'all_nodes' is overwritten due to the second parameter == True.
289 self
.assertEqual(element1
['all_nodes'], 'dad')
290 # 'child_only' should have been added.
291 self
.assertEqual(element1
['child_only'], 'child')
292 # 'source' is also overwritten due to the third parameter == True.
293 self
.assertEqual(element1
['source'], 'source')
295 # Test for when same fields are replaced but not source...
296 element1
= nodes
.Element(ids
=['foo', 'bar'], parent_only
='parent',
298 element1
.update_all_atts_consistantly(element2
)
299 # 'ids' are appended because 'ids' is a basic attribute.
300 self
.assertEqual(element1
['ids'], ['foo', 'bar', 'baz', 'qux'])
301 # 'parent_only' should remain unaffected.
302 self
.assertEqual(element1
['parent_only'], 'parent')
303 # 'all_nodes' is overwritten due to the second parameter default True.
304 self
.assertEqual(element1
['all_nodes'], 'dad')
305 # 'child_only' should have been added.
306 self
.assertEqual(element1
['child_only'], 'child')
307 # 'source' remains unset due to the third parameter default of False.
308 self
.assertEqual(element1
.get('source'), None)
310 # Test for when fields are NOT replaced but source is...
311 element1
= nodes
.Element(ids
=['foo', 'bar'], parent_only
='parent',
313 element1
.update_all_atts_consistantly(element2
, False, True)
314 # 'ids' are appended because 'ids' is a basic attribute.
315 self
.assertEqual(element1
['ids'], ['foo', 'bar', 'baz', 'qux'])
316 # 'parent_only' should remain unaffected.
317 self
.assertEqual(element1
['parent_only'], 'parent')
318 # 'all_nodes' is preserved due to the second parameter == False.
319 self
.assertEqual(element1
['all_nodes'], 'mom')
320 # 'child_only' should have been added.
321 self
.assertEqual(element1
['child_only'], 'child')
322 # 'source' is added due to the third parameter == True.
323 self
.assertEqual(element1
['source'], 'source')
324 element1
= nodes
.Element(source
='destination')
325 element1
.update_all_atts_consistantly(element2
, False, True)
326 # 'source' remains unchanged due to the second parameter == False.
327 self
.assertEqual(element1
['source'], 'destination')
329 # Test for when same fields are replaced but not source...
330 element1
= nodes
.Element(ids
=['foo', 'bar'], parent_only
='parent',
332 element1
.update_all_atts_consistantly(element2
, False)
333 # 'ids' are appended because 'ids' is a basic attribute.
334 self
.assertEqual(element1
['ids'], ['foo', 'bar', 'baz', 'qux'])
335 # 'parent_only' should remain unaffected.
336 self
.assertEqual(element1
['parent_only'], 'parent')
337 # 'all_nodes' is preserved due to the second parameter == False.
338 self
.assertEqual(element1
['all_nodes'], 'mom')
339 # 'child_only' should have been added.
340 self
.assertEqual(element1
['child_only'], 'child')
341 # 'source' remains unset due to the third parameter default of False.
342 self
.assertEqual(element1
.get('source'), None)
344 # Test for List attribute merging
345 # Attribute Concatination
346 element1
= nodes
.Element(ss
='a', sl
='1', ls
=['I'], ll
=['A'])
347 element2
= nodes
.Element(ss
='b', sl
=['2'], ls
='II', ll
=['B'])
348 element1
.update_all_atts_concatenating(element2
)
349 # 'ss' is replaced because non-list
350 self
.assertEqual(element1
['ss'], 'b')
351 # 'sl' is replaced because they are both not lists
352 self
.assertEqual(element1
['sl'], ['2'])
353 # 'ls' is replaced because they are both not lists
354 self
.assertEqual(element1
['ls'], 'II')
355 # 'll' is extended because they are both lists
356 self
.assertEqual(element1
['ll'], ['A', 'B'])
359 element1
= nodes
.Element(ss
='a', sl
='1', ls
=['I'], ll
=['A'])
360 element2
= nodes
.Element(ss
='b', sl
=['2'], ls
='II', ll
=['B'])
361 element1
.update_all_atts_coercion(element2
)
362 # 'ss' is replaced because non-list
363 self
.assertEqual(element1
['ss'], 'b')
364 # 'sl' is converted to a list and appended because element2 has a list
365 self
.assertEqual(element1
['sl'], ['1', '2'])
366 # 'ls' has element2's value appended to the list
367 self
.assertEqual(element1
['ls'], ['I', 'II'])
368 # 'll' is extended because they are both lists
369 self
.assertEqual(element1
['ll'], ['A', 'B'])
371 # Attribute Conversion
372 element1
= nodes
.Element(ss
='a', sl
='1', ls
=['I'], ll
=['A'])
373 element2
= nodes
.Element(ss
='b', sl
=['2'], ls
='II', ll
=['B'])
374 element1
.update_all_atts_convert(element2
)
375 # 'ss' is converted to a list with the values from each element
376 self
.assertEqual(element1
['ss'], ['a', 'b'])
377 # 'sl' is converted to a list and appended
378 self
.assertEqual(element1
['sl'], ['1', '2'])
379 # 'ls' has element2's value appended to the list
380 self
.assertEqual(element1
['ls'], ['I', 'II'])
382 self
.assertEqual(element1
['ll'], ['A', 'B'])
386 grandchild
= nodes
.Text('grandchild text')
387 child
= nodes
.emphasis('childtext', grandchild
, att
='child')
388 e
= nodes
.Element('raw text', child
, att
='e')
390 self
.assertTrue(e
is not e_copy
)
391 # Internal attributes (like `rawsource`) are also copied.
392 self
.assertEqual(e
.rawsource
, 'raw text')
393 self
.assertEqual(e_copy
.rawsource
, e
.rawsource
)
394 self
.assertEqual(e_copy
['att'], 'e')
395 self
.assertEqual(e_copy
.document
, e
.document
)
396 self
.assertEqual(e_copy
.source
, e
.source
)
397 self
.assertEqual(e_copy
.line
, e
.line
)
398 # Children are not copied.
399 self
.assertEqual(len(e_copy
), 0)
401 def test_deepcopy(self
):
403 grandchild
= nodes
.Text('grandchild text')
404 child
= nodes
.emphasis('childtext', grandchild
, att
='child')
405 e
= nodes
.Element('raw text', child
, att
='e')
406 e_deepcopy
= e
.deepcopy()
407 self
.assertEqual(e_deepcopy
.rawsource
, e
.rawsource
)
408 self
.assertEqual(e_deepcopy
['att'], 'e')
409 # Children are copied recursively.
410 self
.assertEqual(e_deepcopy
[0][0], grandchild
)
411 self
.assertTrue(e_deepcopy
[0][0] is not grandchild
)
412 self
.assertEqual(e_deepcopy
[0]['att'], 'child')
414 def test_system_message_copy(self
):
415 e
= nodes
.system_message('mytext', att
='e', rawsource
='raw text')
418 self
.assertTrue(e
is not e_copy
)
419 # Internal attributes (like `rawsource`) are also copied.
420 self
.assertEqual(e
.rawsource
, 'raw text')
421 self
.assertEqual(e_copy
.rawsource
, e
.rawsource
)
422 self
.assertEqual(e_copy
['att'], 'e')
424 def test_replace_self(self
):
425 parent
= nodes
.Element(ids
=['parent'])
426 child1
= nodes
.Element(ids
=['child1'])
427 grandchild
= nodes
.Element(ids
=['grandchild'])
429 child2
= nodes
.Element(ids
=['child2'])
430 twins
= [nodes
.Element(ids
=['twin%s' % i
]) for i
in (1, 2)]
432 child3
= nodes
.Element(ids
=['child3'])
433 child4
= nodes
.Element(ids
=['child4'])
434 parent
+= [child1
, child2
, child3
, child4
]
435 self
.assertEqual(parent
.pformat(), """\
436 <Element ids="parent">
437 <Element ids="child1">
438 <Element ids="grandchild">
439 <Element ids="child2">
440 <Element ids="twin1">
441 <Element ids="twin2">
442 <Element ids="child3">
443 <Element ids="child4">
445 # Replace child1 with the grandchild.
446 child1
.replace_self(child1
[0])
447 self
.assertEqual(parent
[0], grandchild
)
448 # Assert that 'ids' have been updated.
449 self
.assertEqual(grandchild
['ids'], ['grandchild', 'child1'])
450 # Replace child2 with its children.
451 child2
.replace_self(child2
[:])
452 self
.assertEqual(parent
[1:3], twins
)
453 # Assert that 'ids' have been propagated to first child.
454 self
.assertEqual(twins
[0]['ids'], ['twin1', 'child2'])
455 self
.assertEqual(twins
[1]['ids'], ['twin2'])
456 # Replace child3 with new child.
457 newchild
= nodes
.Element(ids
=['newchild'])
458 child3
.replace_self(newchild
)
459 self
.assertEqual(parent
[3], newchild
)
460 self
.assertEqual(newchild
['ids'], ['newchild', 'child3'])
461 # Crazy but possible case: Substitute child4 for itself.
462 child4
.replace_self(child4
)
463 # Make sure the 'child4' ID hasn't been duplicated.
464 self
.assertEqual(child4
['ids'], ['child4'])
465 self
.assertEqual(len(parent
), 5)
467 def test_set_class_deprecation_warning(self
):
468 node
= nodes
.Element('test node')
469 with self
.assertWarns(DeprecationWarning):
470 node
.set_class('parrot')
472 def test_validate(self
):
473 node
= nodes
.paragraph('', 'plain text', classes
='my test classes')
474 node
.append(nodes
.emphasis('', 'emphasised text', ids
='emphtext'))
477 def test_validate_wrong_attribute(self
):
478 node
= nodes
.paragraph('', 'text', id='test-paragraph')
479 with self
.assertRaisesRegex(ValueError,
480 'Element <paragraph> '
481 'has invalid attribute "id".'):
485 class MiscTests(unittest
.TestCase
):
487 def test_node_class_names(self
):
488 node_class_names
= []
490 c
= getattr(nodes
, x
)
491 if (isinstance(c
, type)
492 and issubclass(c
, nodes
.Node
)
493 and len(c
.__bases
__) > 1):
494 node_class_names
.append(x
)
495 node_class_names
.sort()
496 nodes
.node_class_names
.sort()
497 self
.assertEqual(node_class_names
, nodes
.node_class_names
)
500 class TreeCopyVisitorTests(unittest
.TestCase
):
503 document
= utils
.new_document('test data')
504 document
+= nodes
.paragraph('', 'Paragraph 1.')
505 blist
= nodes
.bullet_list()
506 for i
in range(1, 6):
507 item
= nodes
.list_item()
508 for j
in range(1, 4):
509 item
+= nodes
.paragraph('', 'Item %s, paragraph %s.' % (i
, j
))
512 self
.document
= document
514 def compare_trees(self
, one
, two
):
515 self
.assertEqual(one
.__class
__, two
.__class
__)
516 self
.assertNotEqual(id(one
), id(two
))
517 self
.assertEqual(len(one
.children
), len(two
.children
))
518 for i
in range(len(one
.children
)):
519 self
.compare_trees(one
.children
[i
], two
.children
[i
])
521 def test_copy_whole(self
):
522 visitor
= nodes
.TreeCopyVisitor(self
.document
)
523 self
.document
.walkabout(visitor
)
524 newtree
= visitor
.get_tree_copy()
525 self
.assertEqual(self
.document
.pformat(), newtree
.pformat())
526 self
.compare_trees(self
.document
, newtree
)
529 class SetIdTests(unittest
.TestCase
):
532 self
.document
= utils
.new_document('test')
533 self
.elements
= [nodes
.Element(names
=['test']),
534 nodes
.section(), # Name empty
535 nodes
.section(names
=['Test']), # duplicate id
536 nodes
.footnote(names
=['2019-10-30']), # id empty
539 def test_set_id_default(self
):
541 for element
in self
.elements
:
542 self
.document
.set_id(element
)
543 ids
= [element
['ids'] for element
in self
.elements
]
544 self
.assertEqual(ids
, [['test'], ['section-1'],
545 ['test-1'], ['footnote-1']])
547 def test_set_id_custom(self
):
551 self
.document
.settings
.id_prefix
= 'P-'
552 self
.document
.settings
.auto_id_prefix
= 'auto'
554 for element
in self
.elements
:
555 self
.document
.set_id(element
)
556 ids
= [element
['ids'] for element
in self
.elements
]
557 self
.assertEqual(ids
, [['P-test'],
562 def test_set_id_descriptive_auto_id(self
):
563 # Use name or tag-name for auto-id.
566 self
.document
.settings
.auto_id_prefix
= '%'
568 for element
in self
.elements
:
569 self
.document
.set_id(element
)
570 ids
= [element
['ids'] for element
in self
.elements
]
571 self
.assertEqual(ids
, [['test'],
576 def test_set_id_custom_descriptive_auto_id(self
):
577 # Custom prefixes and name or tag-name for auto-id.
580 self
.document
.settings
.id_prefix
= 'P:'
581 self
.document
.settings
.auto_id_prefix
= 'a-%'
583 for element
in self
.elements
:
584 self
.document
.set_id(element
)
585 ids
= [element
['ids'] for element
in self
.elements
]
586 self
.assertEqual(ids
, [['P:test'],
592 class NodeVisitorTests(unittest
.TestCase
):
594 self
.document
= utils
.new_document('test')
595 self
.element
= nodes
.Element()
596 self
.visitor
= nodes
.NodeVisitor(self
.document
)
598 def test_dispatch_visit_unknown(self
):
599 # raise exception if no visit/depart methods are defined for node class
600 with self
.assertRaises(NotImplementedError):
601 self
.visitor
.dispatch_visit(self
.element
)
603 def test_dispatch_visit_optional(self
):
604 # silently skip nodes of a calss in tuple nodes.NodeVisitor.optional
605 rv
= self
.visitor
.dispatch_visit(nodes
.meta())
606 self
.assertIsNone(rv
)
609 class MiscFunctionTests(unittest
.TestCase
):
611 ids
= [('a', 'a'), ('A', 'a'), ('', ''), ('a b \n c', 'a-b-c'),
612 ('a.b.c', 'a-b-c'), (' - a - b - c - ', 'a-b-c'), (' - ', ''),
613 ('\u2020\u2066', ''), ('a \xa7 b \u2020 c', 'a-b-c'),
614 ('1', ''), ('1abc', 'abc'),
618 ('\u00f8 o with stroke', 'o-o-with-stroke'),
619 ('\u0111 d with stroke', 'd-d-with-stroke'),
620 ('\u0127 h with stroke', 'h-h-with-stroke'),
621 ('\u0131 dotless i', 'i-dotless-i'),
622 ('\u0142 l with stroke', 'l-l-with-stroke'),
623 ('\u0167 t with stroke', 't-t-with-stroke'),
624 # From Latin Extended-B
625 ('\u0180 b with stroke', 'b-b-with-stroke'),
626 ('\u0183 b with topbar', 'b-b-with-topbar'),
627 ('\u0188 c with hook', 'c-c-with-hook'),
628 ('\u018c d with topbar', 'd-d-with-topbar'),
629 ('\u0192 f with hook', 'f-f-with-hook'),
630 ('\u0199 k with hook', 'k-k-with-hook'),
631 ('\u019a l with bar', 'l-l-with-bar'),
632 ('\u019e n with long right leg', 'n-n-with-long-right-leg'),
633 ('\u01a5 p with hook', 'p-p-with-hook'),
634 ('\u01ab t with palatal hook', 't-t-with-palatal-hook'),
635 ('\u01ad t with hook', 't-t-with-hook'),
636 ('\u01b4 y with hook', 'y-y-with-hook'),
637 ('\u01b6 z with stroke', 'z-z-with-stroke'),
638 ('\u01e5 g with stroke', 'g-g-with-stroke'),
639 ('\u0225 z with hook', 'z-z-with-hook'),
640 ('\u0234 l with curl', 'l-l-with-curl'),
641 ('\u0235 n with curl', 'n-n-with-curl'),
642 ('\u0236 t with curl', 't-t-with-curl'),
643 ('\u0237 dotless j', 'j-dotless-j'),
644 ('\u023c c with stroke', 'c-c-with-stroke'),
645 ('\u023f s with swash tail', 's-s-with-swash-tail'),
646 ('\u0240 z with swash tail', 'z-z-with-swash-tail'),
647 ('\u0247 e with stroke', 'e-e-with-stroke'),
648 ('\u0249 j with stroke', 'j-j-with-stroke'),
649 ('\u024b q with hook tail', 'q-q-with-hook-tail'),
650 ('\u024d r with stroke', 'r-r-with-stroke'),
651 ('\u024f y with stroke', 'y-y-with-stroke'),
652 # From Latin-1 Supplements
653 ('\u00e0: a with grave', 'a-a-with-grave'),
654 ('\u00e1 a with acute', 'a-a-with-acute'),
655 ('\u00e2 a with circumflex', 'a-a-with-circumflex'),
656 ('\u00e3 a with tilde', 'a-a-with-tilde'),
657 ('\u00e4 a with diaeresis', 'a-a-with-diaeresis'),
658 ('\u00e5 a with ring above', 'a-a-with-ring-above'),
659 ('\u00e7 c with cedilla', 'c-c-with-cedilla'),
660 ('\u00e8 e with grave', 'e-e-with-grave'),
661 ('\u00e9 e with acute', 'e-e-with-acute'),
662 ('\u00ea e with circumflex', 'e-e-with-circumflex'),
663 ('\u00eb e with diaeresis', 'e-e-with-diaeresis'),
664 ('\u00ec i with grave', 'i-i-with-grave'),
665 ('\u00ed i with acute', 'i-i-with-acute'),
666 ('\u00ee i with circumflex', 'i-i-with-circumflex'),
667 ('\u00ef i with diaeresis', 'i-i-with-diaeresis'),
668 ('\u00f1 n with tilde', 'n-n-with-tilde'),
669 ('\u00f2 o with grave', 'o-o-with-grave'),
670 ('\u00f3 o with acute', 'o-o-with-acute'),
671 ('\u00f4 o with circumflex', 'o-o-with-circumflex'),
672 ('\u00f5 o with tilde', 'o-o-with-tilde'),
673 ('\u00f6 o with diaeresis', 'o-o-with-diaeresis'),
674 ('\u00f9 u with grave', 'u-u-with-grave'),
675 ('\u00fa u with acute', 'u-u-with-acute'),
676 ('\u00fb u with circumflex', 'u-u-with-circumflex'),
677 ('\u00fc u with diaeresis', 'u-u-with-diaeresis'),
678 ('\u00fd y with acute', 'y-y-with-acute'),
679 ('\u00ff y with diaeresis', 'y-y-with-diaeresis'),
680 # From Latin Extended-A
681 ('\u0101 a with macron', 'a-a-with-macron'),
682 ('\u0103 a with breve', 'a-a-with-breve'),
683 ('\u0105 a with ogonek', 'a-a-with-ogonek'),
684 ('\u0107 c with acute', 'c-c-with-acute'),
685 ('\u0109 c with circumflex', 'c-c-with-circumflex'),
686 ('\u010b c with dot above', 'c-c-with-dot-above'),
687 ('\u010d c with caron', 'c-c-with-caron'),
688 ('\u010f d with caron', 'd-d-with-caron'),
689 ('\u0113 e with macron', 'e-e-with-macron'),
690 ('\u0115 e with breve', 'e-e-with-breve'),
691 ('\u0117 e with dot above', 'e-e-with-dot-above'),
692 ('\u0119 e with ogonek', 'e-e-with-ogonek'),
693 ('\u011b e with caron', 'e-e-with-caron'),
694 ('\u011d g with circumflex', 'g-g-with-circumflex'),
695 ('\u011f g with breve', 'g-g-with-breve'),
696 ('\u0121 g with dot above', 'g-g-with-dot-above'),
697 ('\u0123 g with cedilla', 'g-g-with-cedilla'),
698 ('\u0125 h with circumflex', 'h-h-with-circumflex'),
699 ('\u0129 i with tilde', 'i-i-with-tilde'),
700 ('\u012b i with macron', 'i-i-with-macron'),
701 ('\u012d i with breve', 'i-i-with-breve'),
702 ('\u012f i with ogonek', 'i-i-with-ogonek'),
703 ('\u0133 ligature ij', 'ij-ligature-ij'),
704 ('\u0135 j with circumflex', 'j-j-with-circumflex'),
705 ('\u0137 k with cedilla', 'k-k-with-cedilla'),
706 ('\u013a l with acute', 'l-l-with-acute'),
707 ('\u013c l with cedilla', 'l-l-with-cedilla'),
708 ('\u013e l with caron', 'l-l-with-caron'),
709 ('\u0140 l with middle dot', 'l-l-with-middle-dot'),
710 ('\u0144 n with acute', 'n-n-with-acute'),
711 ('\u0146 n with cedilla', 'n-n-with-cedilla'),
712 ('\u0148 n with caron', 'n-n-with-caron'),
713 ('\u014d o with macron', 'o-o-with-macron'),
714 ('\u014f o with breve', 'o-o-with-breve'),
715 ('\u0151 o with double acute', 'o-o-with-double-acute'),
716 ('\u0155 r with acute', 'r-r-with-acute'),
717 ('\u0157 r with cedilla', 'r-r-with-cedilla'),
718 ('\u0159 r with caron', 'r-r-with-caron'),
719 ('\u015b s with acute', 's-s-with-acute'),
720 ('\u015d s with circumflex', 's-s-with-circumflex'),
721 ('\u015f s with cedilla', 's-s-with-cedilla'),
722 ('\u0161 s with caron', 's-s-with-caron'),
723 ('\u0163 t with cedilla', 't-t-with-cedilla'),
724 ('\u0165 t with caron', 't-t-with-caron'),
725 ('\u0169 u with tilde', 'u-u-with-tilde'),
726 ('\u016b u with macron', 'u-u-with-macron'),
727 ('\u016d u with breve', 'u-u-with-breve'),
728 ('\u016f u with ring above', 'u-u-with-ring-above'),
729 ('\u0171 u with double acute', 'u-u-with-double-acute'),
730 ('\u0173 u with ogonek', 'u-u-with-ogonek'),
731 ('\u0175 w with circumflex', 'w-w-with-circumflex'),
732 ('\u0177 y with circumflex', 'y-y-with-circumflex'),
733 ('\u017a z with acute', 'z-z-with-acute'),
734 ('\u017c z with dot above', 'z-z-with-dot-above'),
735 ('\u017e z with caron', 'z-z-with-caron'),
736 # From Latin Extended-B
737 ('\u01a1 o with horn', 'o-o-with-horn'),
738 ('\u01b0 u with horn', 'u-u-with-horn'),
739 ('\u01c6 dz with caron', 'dz-dz-with-caron'),
740 ('\u01c9 lj', 'lj-lj'),
741 ('\u01cc nj', 'nj-nj'),
742 ('\u01ce a with caron', 'a-a-with-caron'),
743 ('\u01d0 i with caron', 'i-i-with-caron'),
744 ('\u01d2 o with caron', 'o-o-with-caron'),
745 ('\u01d4 u with caron', 'u-u-with-caron'),
746 ('\u01e7 g with caron', 'g-g-with-caron'),
747 ('\u01e9 k with caron', 'k-k-with-caron'),
748 ('\u01eb o with ogonek', 'o-o-with-ogonek'),
749 ('\u01ed o with ogonek and macron', 'o-o-with-ogonek-and-macron'),
750 ('\u01f0 j with caron', 'j-j-with-caron'),
751 ('\u01f3 dz', 'dz-dz'),
752 ('\u01f5 g with acute', 'g-g-with-acute'),
753 ('\u01f9 n with grave', 'n-n-with-grave'),
754 ('\u0201 a with double grave', 'a-a-with-double-grave'),
755 ('\u0203 a with inverted breve', 'a-a-with-inverted-breve'),
756 ('\u0205 e with double grave', 'e-e-with-double-grave'),
757 ('\u0207 e with inverted breve', 'e-e-with-inverted-breve'),
758 ('\u0209 i with double grave', 'i-i-with-double-grave'),
759 ('\u020b i with inverted breve', 'i-i-with-inverted-breve'),
760 ('\u020d o with double grave', 'o-o-with-double-grave'),
761 ('\u020f o with inverted breve', 'o-o-with-inverted-breve'),
762 ('\u0211 r with double grave', 'r-r-with-double-grave'),
763 ('\u0213 r with inverted breve', 'r-r-with-inverted-breve'),
764 ('\u0215 u with double grave', 'u-u-with-double-grave'),
765 ('\u0217 u with inverted breve', 'u-u-with-inverted-breve'),
766 ('\u0219 s with comma below', 's-s-with-comma-below'),
767 ('\u021b t with comma below', 't-t-with-comma-below'),
768 ('\u021f h with caron', 'h-h-with-caron'),
769 ('\u0227 a with dot above', 'a-a-with-dot-above'),
770 ('\u0229 e with cedilla', 'e-e-with-cedilla'),
771 ('\u022f o with dot above', 'o-o-with-dot-above'),
772 ('\u0233 y with macron', 'y-y-with-macron'),
773 # digraphs From Latin-1 Supplements
774 ('\u00df: ligature sz', 'sz-ligature-sz'),
775 ('\u00e6 ae', 'ae-ae'),
776 ('\u0153 ligature oe', 'oe-ligature-oe'),
777 ('\u0238 db digraph', 'db-db-digraph'),
778 ('\u0239 qp digraph', 'qp-qp-digraph'),
781 def test_make_id(self
):
783 tests
= self
.ids
+ self
.ids_unicode_all
784 for input, expect
in tests
:
785 output
= nodes
.make_id(input)
787 failures
.append("'%s' != '%s'" % (expect
, output
))
789 self
.fail(f
'{len(failures)} failures in {len(self.ids)} ids\n'
790 + "\n".join(failures
))
792 names
= [ # sample, whitespace_normalized, fully_normalized
795 ('A a A ', 'A a A', 'a a a'),
796 ('A a A a', 'A a A a', 'a a a a'),
797 (' AaA\n\r\naAa\tAaA\t\t', 'AaA aAa AaA', 'aaa aaa aaa')
800 def test_whitespace_normalize_name(self
):
801 for (sample
, ws
, full
) in self
.names
:
802 result
= nodes
.whitespace_normalize_name(sample
)
803 self
.assertEqual(result
, ws
)
805 def test_fully_normalize_name(self
):
806 for (sample
, ws
, fully
) in self
.names
:
807 result
= nodes
.fully_normalize_name(sample
)
808 self
.assertEqual(result
, fully
)
811 if __name__
== '__main__':