1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
4 # ++ single anchor added to individual output file
5 # ++ two anchors added to individual output file
6 # ++ anchors added to individual output files
7 # ++ entry added to index
8 # ++ index entry pointing to correct file and anchor
9 # ++ multiple entries added to index
10 # ++ multiple index entries pointing to correct files and anchors
11 # __ all of above for files in deep directory structure
13 # ++ group index entries by indexed term
14 # ++ sort index entries by indexed term
15 # __ hierarchical index entries (e.g. language!programming)
17 # ++ add parameter for what the index filename should be
18 # ++ add (default) ability to NOT index (if index not specified)
20 # ++ put actual index filename into INDEX link (if any) in the template
21 # __ make index links RELATIVE!
22 # __ make index pay attention to the outputdir!
24 # __ make index look nice
26 # ++ add section numbers to headers in lore output
27 # ++ make text of index entry links be chapter numbers
28 # ++ make text of index entry links be section numbers
30 # __ put all of our test files someplace neat and tidy
33 import os, shutil, errno, time
34 from StringIO import StringIO
35 from xml.dom import minidom as dom
37 from twisted.trial import unittest
38 from twisted.python.filepath import FilePath
40 from twisted.lore import tree, process, indexer, numberer, htmlbook, default
41 from twisted.lore.default import factory
42 from twisted.lore.latex import LatexSpitter
44 from twisted.python.util import sibpath
46 from twisted.lore.scripts import lore
48 from twisted.web import domhelpers
50 def sp(originalFileName):
51 return sibpath(__file__, originalFileName)
53 options = {"template" : sp("template.tpl"), 'baseurl': '%s', 'ext': '.xhtml' }
57 class _XMLAssertionMixin:
59 Test mixin defining a method for comparing serialized XML documents.
61 def assertXMLEqual(self, first, second):
63 Verify that two strings represent the same XML document.
66 dom.parseString(first).toxml(),
67 dom.parseString(second).toxml())
70 class TestFactory(unittest.TestCase, _XMLAssertionMixin):
72 file = sp('simple.html')
75 def assertEqualFiles1(self, exp, act):
76 if (exp == act): return True
78 self.assertEqualsFile(exp, fact.read())
80 def assertEqualFiles(self, exp, act):
81 if (exp == act): return True
83 self.assertEqualsFile(exp, fact.read())
85 def assertEqualsFile(self, exp, act):
86 expected = open(sp(exp)).read()
87 self.assertEqual(expected, act)
89 def makeTemp(self, *filenames):
92 for filename in filenames:
93 tmpFile = os.path.join(tmp, filename)
94 shutil.copyfile(sp(filename), tmpFile)
97 ########################################
103 def testProcessingFunctionFactory(self):
104 base = FilePath(self.mktemp())
107 simple = base.child('simple.html')
108 FilePath(__file__).sibling('simple.html').copyTo(simple)
110 htmlGenerator = factory.generate_html(options)
111 htmlGenerator(simple.path, self.linkrel)
115 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
116 <head><title>Twisted Documentation: My Test Lore Input</title></head>
117 <body bgcolor="white">
118 <h1 class="title">My Test Lore Input</h1>
119 <div class="content">
123 <a href="index.xhtml">Index</a>
126 simple.sibling('simple.xhtml').getContent())
129 def testProcessingFunctionFactoryWithFilenameGenerator(self):
130 base = FilePath(self.mktemp())
133 def filenameGenerator(originalFileName, outputExtension):
134 name = os.path.splitext(FilePath(originalFileName).basename())[0]
135 return base.child(name + outputExtension).path
137 htmlGenerator = factory.generate_html(options, filenameGenerator)
138 htmlGenerator(self.file, self.linkrel)
141 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
142 <head><title>Twisted Documentation: My Test Lore Input</title></head>
143 <body bgcolor="white">
144 <h1 class="title">My Test Lore Input</h1>
145 <div class="content">
149 <a href="index.xhtml">Index</a>
152 base.child("simple.xhtml").getContent())
155 def test_doFile(self):
156 base = FilePath(self.mktemp())
159 simple = base.child('simple.html')
160 FilePath(__file__).sibling('simple.html').copyTo(simple)
162 templ = dom.parse(open(d['template']))
164 tree.doFile(simple.path, self.linkrel, d['ext'], d['baseurl'], templ, d)
167 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
168 <head><title>Twisted Documentation: My Test Lore Input</title></head>
169 <body bgcolor="white">
170 <h1 class="title">My Test Lore Input</h1>
171 <div class="content">
175 <a href="index.xhtml">Index</a>
178 base.child("simple.xhtml").getContent())
181 def test_doFile_withFilenameGenerator(self):
182 base = FilePath(self.mktemp())
185 def filenameGenerator(originalFileName, outputExtension):
186 name = os.path.splitext(FilePath(originalFileName).basename())[0]
187 return base.child(name + outputExtension).path
189 templ = dom.parse(open(d['template']))
190 tree.doFile(self.file, self.linkrel, d['ext'], d['baseurl'], templ, d, filenameGenerator)
194 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
195 <head><title>Twisted Documentation: My Test Lore Input</title></head>
196 <body bgcolor="white">
197 <h1 class="title">My Test Lore Input</h1>
198 <div class="content">
202 <a href="index.xhtml">Index</a>
205 base.child("simple.xhtml").getContent())
208 def test_munge(self):
209 indexer.setIndexFilename("lore_index_file.html")
210 doc = dom.parse(open(self.file))
211 node = dom.parse(open(d['template']))
212 tree.munge(doc, node, self.linkrel,
213 os.path.dirname(self.file),
215 d['ext'], d['baseurl'], d)
219 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
220 <head><title>Twisted Documentation: My Test Lore Input</title></head>
221 <body bgcolor="white">
222 <h1 class="title">My Test Lore Input</h1>
223 <div class="content">
227 <a href="lore_index_file.html">Index</a>
233 def test_mungeAuthors(self):
235 If there is a node with a I{class} attribute set to C{"authors"},
236 L{tree.munge} adds anchors as children to it, takeing the necessary
237 information from any I{link} nodes in the I{head} with their I{rel}
238 attribute set to C{"author"}.
240 document = dom.parseString(
244 <title>munge authors</title>
245 <link rel="author" title="foo" href="bar"/>
246 <link rel="author" title="baz" href="quux"/>
247 <link rel="author" title="foobar" href="barbaz"/>
250 <h1>munge authors</h1>
253 template = dom.parseString(
255 <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
262 <div class="authors" />
267 document, template, self.linkrel, os.path.dirname(self.file),
268 self.file, d['ext'], d['baseurl'], d)
273 <?xml version="1.0" ?><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
275 <title>munge authors</title>
276 <link href="bar" rel="author" title="foo"/><link href="quux" rel="author" title="baz"/><link href="barbaz" rel="author" title="foobar"/></head>
279 <div class="content">
282 <div class="authors"><span><a href="bar">foo</a>, <a href="quux">baz</a>, and <a href="barbaz">foobar</a></span></div>
287 def test_getProcessor(self):
289 base = FilePath(self.mktemp())
291 input = base.child("simple3.html")
292 FilePath(__file__).sibling("simple3.html").copyTo(input)
294 options = { 'template': sp('template.tpl'), 'ext': '.xhtml', 'baseurl': 'burl',
295 'filenameMapping': None }
296 p = process.getProcessor(default, "html", options)
297 p(input.path, self.linkrel)
300 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
301 <head><title>Twisted Documentation: My Test Lore Input</title></head>
302 <body bgcolor="white">
303 <h1 class="title">My Test Lore Input</h1>
304 <div class="content">
308 <a href="index.xhtml">Index</a>
311 base.child("simple3.xhtml").getContent())
313 def test_outputdirGenerator(self):
314 normp = os.path.normpath; join = os.path.join
315 inputdir = normp(join("/", 'home', 'joe'))
316 outputdir = normp(join("/", 'away', 'joseph'))
317 actual = process.outputdirGenerator(join("/", 'home', 'joe', "myfile.html"),
318 '.xhtml', inputdir, outputdir)
319 expected = normp(join("/", 'away', 'joseph', 'myfile.xhtml'))
320 self.assertEqual(expected, actual)
322 def test_outputdirGeneratorBadInput(self):
323 options = {'outputdir': '/away/joseph/', 'inputdir': '/home/joe/' }
324 self.assertRaises(ValueError, process.outputdirGenerator, '.html', '.xhtml', **options)
326 def test_makeSureDirectoryExists(self):
327 dirname = os.path.join("tmp", 'nonexistentdir')
328 if os.path.exists(dirname):
330 self.failIf(os.path.exists(dirname), "Hey: someone already created the dir")
331 filename = os.path.join(dirname, 'newfile')
332 tree.makeSureDirectoryExists(filename)
333 self.failUnless(os.path.exists(dirname), 'should have created dir')
337 def test_indexAnchorsAdded(self):
338 indexer.setIndexFilename('theIndexFile.html')
339 # generate the output file
340 templ = dom.parse(open(d['template']))
341 tmp = self.makeTemp('lore_index_test.xhtml')
343 tree.doFile(os.path.join(tmp, 'lore_index_test.xhtml'),
344 self.linkrel, '.html', d['baseurl'], templ, d)
348 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
349 <head><title>Twisted Documentation: The way of the program</title></head>
350 <body bgcolor="white">
351 <h1 class="title">The way of the program</h1>
352 <div class="content">
356 <p>The first paragraph.</p>
359 <h2>The Python programming language<a name="auto0"/></h2>
363 <p>The second paragraph.</p>
367 <a href="theIndexFile.html">Index</a>
370 FilePath(tmp).child("lore_index_test.html").getContent())
373 def test_indexEntriesAdded(self):
374 indexer.addEntry('lore_index_test.html', 'index02', 'language of programming', '1.3')
375 indexer.addEntry('lore_index_test.html', 'index01', 'programming language', '1.2')
376 indexer.setIndexFilename("lore_index_file.html")
377 indexer.generateIndex()
378 self.assertEqualFiles1("lore_index_file_out.html", "lore_index_file.html")
381 tmp = self.makeTemp()
382 inputFilename = sp('lore_index_test.xhtml')
384 bookFilename = os.path.join(tmp, 'lore_test_book.book')
385 bf = open(bookFilename, 'w')
386 bf.write('Chapter(r"%s", None)\r\n' % inputFilename)
389 book = htmlbook.Book(bookFilename)
390 expected = {'indexFilename': None,
391 'chapters': [(inputFilename, None)],
395 self.assertEqual(dct[k], expected[k])
397 def test_runningLore(self):
398 options = lore.Options()
399 tmp = self.makeTemp('lore_index_test.xhtml')
401 templateFilename = sp('template.tpl')
402 inputFilename = os.path.join(tmp, 'lore_index_test.xhtml')
403 indexFilename = 'theIndexFile'
405 bookFilename = os.path.join(tmp, 'lore_test_book.book')
406 bf = open(bookFilename, 'w')
407 bf.write('Chapter(r"%s", None)\n' % inputFilename)
410 options.parseOptions(['--null', '--book=%s' % bookFilename,
411 '--config', 'template=%s' % templateFilename,
412 '--index=%s' % indexFilename
414 result = lore.runGivenOptions(options)
415 self.assertEqual(None, result)
416 self.assertEqualFiles1("lore_index_file_unnumbered_out.html", indexFilename + ".html")
419 def test_runningLoreMultipleFiles(self):
420 tmp = self.makeTemp('lore_index_test.xhtml', 'lore_index_test2.xhtml')
421 templateFilename = sp('template.tpl')
422 inputFilename = os.path.join(tmp, 'lore_index_test.xhtml')
423 inputFilename2 = os.path.join(tmp, 'lore_index_test2.xhtml')
424 indexFilename = 'theIndexFile'
426 bookFilename = os.path.join(tmp, 'lore_test_book.book')
427 bf = open(bookFilename, 'w')
428 bf.write('Chapter(r"%s", None)\n' % inputFilename)
429 bf.write('Chapter(r"%s", None)\n' % inputFilename2)
432 options = lore.Options()
433 options.parseOptions(['--null', '--book=%s' % bookFilename,
434 '--config', 'template=%s' % templateFilename,
435 '--index=%s' % indexFilename
437 result = lore.runGivenOptions(options)
438 self.assertEqual(None, result)
441 # XXX This doesn't seem like a very good index file.
443 aahz: <a href="lore_index_test2.html#index03">link</a><br />
444 aahz2: <a href="lore_index_test2.html#index02">link</a><br />
445 language of programming: <a href="lore_index_test.html#index02">link</a>, <a href="lore_index_test2.html#index01">link</a><br />
446 programming language: <a href="lore_index_test.html#index01">link</a><br />
448 file(FilePath(indexFilename + ".html").path).read())
452 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
453 <head><title>Twisted Documentation: The way of the program</title></head>
454 <body bgcolor="white">
455 <h1 class="title">The way of the program</h1>
456 <div class="content">
460 <p>The first paragraph.</p>
463 <h2>The Python programming language<a name="auto0"/></h2>
467 <p>The second paragraph.</p>
471 <a href="theIndexFile.html">Index</a>
474 FilePath(tmp).child("lore_index_test.html").getContent())
478 <?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
479 <head><title>Twisted Documentation: The second page to index</title></head>
480 <body bgcolor="white">
481 <h1 class="title">The second page to index</h1>
482 <div class="content">
486 <p>The first paragraph of the second page.</p>
489 <h2>The Jython programming language<a name="auto0"/></h2>
494 <p>The second paragraph of the second page.</p>
498 <a href="theIndexFile.html">Index</a>
501 FilePath(tmp).child("lore_index_test2.html").getContent())
505 def XXXtest_NumberedSections(self):
506 # run two files through lore, with numbering turned on
507 # every h2 should be numbered:
508 # first file's h2s should be 1.1, 1.2
509 # second file's h2s should be 2.1, 2.2
510 templateFilename = sp('template.tpl')
511 inputFilename = sp('lore_numbering_test.xhtml')
512 inputFilename2 = sp('lore_numbering_test2.xhtml')
513 indexFilename = 'theIndexFile'
515 # you can number without a book:
516 options = lore.Options()
517 options.parseOptions(['--null',
518 '--index=%s' % indexFilename,
519 '--config', 'template=%s' % templateFilename,
520 '--config', 'ext=%s' % ".tns",
522 inputFilename, inputFilename2])
523 result = lore.runGivenOptions(options)
525 self.assertEqual(None, result)
526 #self.assertEqualFiles1("lore_index_file_out_multiple.html", indexFilename + ".tns")
527 # VVV change to new, numbered files
528 self.assertEqualFiles("lore_numbering_test_out.html", "lore_numbering_test.tns")
529 self.assertEqualFiles("lore_numbering_test_out2.html", "lore_numbering_test2.tns")
532 def test_setTitle(self):
534 L{tree.setTitle} inserts the given title into the first I{title}
535 element and the first element with the I{title} class in the given
538 parent = dom.Element('div')
539 firstTitle = dom.Element('title')
540 parent.appendChild(firstTitle)
541 secondTitle = dom.Element('span')
542 secondTitle.setAttribute('class', 'title')
543 parent.appendChild(secondTitle)
545 titleNodes = [dom.Text()]
546 # minidom has issues with cloning documentless-nodes. See Python issue
548 titleNodes[0].ownerDocument = dom.Document()
549 titleNodes[0].data = 'foo bar'
551 tree.setTitle(parent, titleNodes, None)
552 self.assertEqual(firstTitle.toxml(), '<title>foo bar</title>')
554 secondTitle.toxml(), '<span class="title">foo bar</span>')
557 def test_setTitleWithChapter(self):
559 L{tree.setTitle} includes a chapter number if it is passed one.
561 document = dom.Document()
563 parent = dom.Element('div')
564 parent.ownerDocument = document
566 title = dom.Element('title')
567 parent.appendChild(title)
569 titleNodes = [dom.Text()]
570 titleNodes[0].ownerDocument = document
571 titleNodes[0].data = 'foo bar'
573 # Oh yea. The numberer has to agree to put the chapter number in, too.
574 numberer.setNumberSections(True)
576 tree.setTitle(parent, titleNodes, '13')
577 self.assertEqual(title.toxml(), '<title>13. foo bar</title>')
580 def test_setIndexLink(self):
582 Tests to make sure that index links are processed when an index page
583 exists and removed when there is not.
585 templ = dom.parse(open(d['template']))
586 indexFilename = 'theIndexFile'
587 numLinks = len(domhelpers.findElementsWithAttribute(templ,
591 # if our testing template has no index-link nodes, complain about it
592 self.assertNotEquals(
594 domhelpers.findElementsWithAttribute(templ,
598 tree.setIndexLink(templ, indexFilename)
602 domhelpers.findElementsWithAttribute(templ,
606 indexLinks = domhelpers.findElementsWithAttribute(templ,
609 self.assertTrue(len(indexLinks) >= numLinks)
611 templ = dom.parse(open(d['template']))
612 self.assertNotEquals(
614 domhelpers.findElementsWithAttribute(templ,
619 tree.setIndexLink(templ, indexFilename)
623 domhelpers.findElementsWithAttribute(templ,
628 def test_addMtime(self):
630 L{tree.addMtime} inserts a text node giving the last modification time
631 of the specified file wherever it encounters an element with the
634 path = FilePath(self.mktemp())
636 when = time.ctime(path.getModificationTime())
638 parent = dom.Element('div')
639 mtime = dom.Element('span')
640 mtime.setAttribute('class', 'mtime')
641 parent.appendChild(mtime)
643 tree.addMtime(parent, path.path)
645 mtime.toxml(), '<span class="mtime">' + when + '</span>')
648 def test_makeLineNumbers(self):
650 L{tree._makeLineNumbers} takes an integer and returns a I{p} tag with
651 that number of line numbers in it.
653 numbers = tree._makeLineNumbers(1)
654 self.assertEqual(numbers.tagName, 'p')
655 self.assertEqual(numbers.getAttribute('class'), 'py-linenumber')
656 self.assertIsInstance(numbers.firstChild, dom.Text)
657 self.assertEqual(numbers.firstChild.nodeValue, '1\n')
659 numbers = tree._makeLineNumbers(10)
660 self.assertEqual(numbers.tagName, 'p')
661 self.assertEqual(numbers.getAttribute('class'), 'py-linenumber')
662 self.assertIsInstance(numbers.firstChild, dom.Text)
664 numbers.firstChild.nodeValue,
665 ' 1\n 2\n 3\n 4\n 5\n'
666 ' 6\n 7\n 8\n 9\n10\n')
669 def test_fontifyPythonNode(self):
671 L{tree.fontifyPythonNode} accepts a text node and replaces it in its
672 parent with a syntax colored and line numbered version of the Python
675 parent = dom.Element('div')
677 source.data = 'def foo():\n pass\n'
678 parent.appendChild(source)
680 tree.fontifyPythonNode(source)
683 <div><pre class="python"><p class="py-linenumber">1
685 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>():
686 <span class="py-src-keyword">pass</span>
689 self.assertEqual(parent.toxml(), expected)
692 def test_addPyListings(self):
694 L{tree.addPyListings} accepts a document with nodes with their I{class}
695 attribute set to I{py-listing} and replaces those nodes with Python
696 source listings from the file given by the node's I{href} attribute.
698 listingPath = FilePath(self.mktemp())
699 listingPath.setContent('def foo():\n pass\n')
701 parent = dom.Element('div')
702 listing = dom.Element('a')
703 listing.setAttribute('href', listingPath.basename())
704 listing.setAttribute('class', 'py-listing')
705 parent.appendChild(listing)
707 tree.addPyListings(parent, listingPath.dirname())
710 <div><div class="py-listing"><pre><p class="py-linenumber">1
712 </p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>():
713 <span class="py-src-keyword">pass</span>
714 </pre><div class="caption"> - <a href="temp"><span class="filename">temp</span></a></div></div></div>"""
716 self.assertEqual(parent.toxml(), expected)
719 def test_addPyListingsSkipLines(self):
721 If a node with the I{py-listing} class also has a I{skipLines}
722 attribute, that number of lines from the beginning of the source
725 listingPath = FilePath(self.mktemp())
726 listingPath.setContent('def foo():\n pass\n')
728 parent = dom.Element('div')
729 listing = dom.Element('a')
730 listing.setAttribute('href', listingPath.basename())
731 listing.setAttribute('class', 'py-listing')
732 listing.setAttribute('skipLines', 1)
733 parent.appendChild(listing)
735 tree.addPyListings(parent, listingPath.dirname())
738 <div><div class="py-listing"><pre><p class="py-linenumber">1
739 </p> <span class="py-src-keyword">pass</span>
740 </pre><div class="caption"> - <a href="temp"><span class="filename">temp</span></a></div></div></div>"""
742 self.assertEqual(parent.toxml(), expected)
745 def test_fixAPI(self):
747 The element passed to L{tree.fixAPI} has all of its children with the
748 I{API} class rewritten to contain links to the API which is referred to
749 by the text they contain.
751 parent = dom.Element('div')
752 link = dom.Element('span')
753 link.setAttribute('class', 'API')
756 link.appendChild(text)
757 parent.appendChild(link)
759 tree.fixAPI(parent, 'http://example.com/%s')
762 '<div><span class="API">'
763 '<a href="http://example.com/foo" title="foo">foo</a>'
767 def test_fixAPIBase(self):
769 If a node with the I{API} class and a value for the I{base} attribute
770 is included in the DOM passed to L{tree.fixAPI}, the link added to that
771 node refers to the API formed by joining the value of the I{base}
772 attribute to the text contents of the node.
774 parent = dom.Element('div')
775 link = dom.Element('span')
776 link.setAttribute('class', 'API')
777 link.setAttribute('base', 'bar')
780 link.appendChild(text)
781 parent.appendChild(link)
783 tree.fixAPI(parent, 'http://example.com/%s')
787 '<div><span class="API">'
788 '<a href="http://example.com/bar.baz" title="bar.baz">baz</a>'
792 def test_fixLinks(self):
794 Links in the nodes of the DOM passed to L{tree.fixLinks} have their
795 extensions rewritten to the given extension.
797 parent = dom.Element('div')
798 link = dom.Element('a')
799 link.setAttribute('href', 'foo.html')
800 parent.appendChild(link)
802 tree.fixLinks(parent, '.xhtml')
804 self.assertEqual(parent.toxml(), '<div><a href="foo.xhtml"/></div>')
807 def test_setVersion(self):
809 Nodes of the DOM passed to L{tree.setVersion} which have the I{version}
810 class have the given version added to them a child.
812 parent = dom.Element('div')
813 version = dom.Element('span')
814 version.setAttribute('class', 'version')
815 parent.appendChild(version)
817 tree.setVersion(parent, '1.2.3')
820 parent.toxml(), '<div><span class="version">1.2.3</span></div>')
823 def test_footnotes(self):
825 L{tree.footnotes} finds all of the nodes with the I{footnote} class in
826 the DOM passed to it and adds a footnotes section to the end of the
827 I{body} element which includes them. It also inserts links to those
828 footnotes from the original definition location.
830 parent = dom.Element('div')
831 body = dom.Element('body')
832 footnote = dom.Element('span')
833 footnote.setAttribute('class', 'footnote')
835 text.data = 'this is the footnote'
836 footnote.appendChild(text)
837 body.appendChild(footnote)
838 body.appendChild(dom.Element('p'))
839 parent.appendChild(body)
841 tree.footnotes(parent)
846 '<a href="#footnote-1" title="this is the footnote">'
851 '<ol><li><a name="footnote-1">'
852 '<span class="footnote">this is the footnote</span>'
857 def test_generateTableOfContents(self):
859 L{tree.generateToC} returns an element which contains a table of
860 contents generated from the headers in the document passed to it.
862 parent = dom.Element('body')
863 header = dom.Element('h2')
865 text.data = u'header & special character'
866 header.appendChild(text)
867 parent.appendChild(header)
868 subheader = dom.Element('h3')
870 text.data = 'subheader'
871 subheader.appendChild(text)
872 parent.appendChild(subheader)
874 tableOfContents = tree.generateToC(parent)
876 tableOfContents.toxml(),
877 '<ol><li><a href="#auto0">header & special character</a></li><ul><li><a href="#auto1">subheader</a></li></ul></ol>')
881 '<h2>header & special character<a name="auto0"/></h2>')
885 '<h3>subheader<a name="auto1"/></h3>')
888 def test_putInToC(self):
890 L{tree.putInToC} replaces all of the children of the first node with
891 the I{toc} class with the given node representing a table of contents.
893 parent = dom.Element('div')
894 toc = dom.Element('span')
895 toc.setAttribute('class', 'toc')
896 toc.appendChild(dom.Element('foo'))
897 parent.appendChild(toc)
899 tree.putInToC(parent, dom.Element('toc'))
901 self.assertEqual(toc.toxml(), '<span class="toc"><toc/></span>')
904 def test_invalidTableOfContents(self):
906 If passed a document with I{h3} elements before any I{h2} element,
907 L{tree.generateToC} raises L{ValueError} explaining that this is not a
910 parent = dom.Element('body')
911 parent.appendChild(dom.Element('h3'))
912 err = self.assertRaises(ValueError, tree.generateToC, parent)
914 str(err), "No H3 element is allowed until after an H2 element")
917 def test_notes(self):
919 L{tree.notes} inserts some additional markup before the first child of
920 any node with the I{note} class.
922 parent = dom.Element('div')
923 noteworthy = dom.Element('span')
924 noteworthy.setAttribute('class', 'note')
925 noteworthy.appendChild(dom.Element('foo'))
926 parent.appendChild(noteworthy)
932 '<span class="note"><strong>Note: </strong><foo/></span>')
935 def test_findNodeJustBefore(self):
937 L{tree.findNodeJustBefore} returns the previous sibling of the node it
938 is passed. The list of nodes passed in is ignored.
940 parent = dom.Element('div')
941 result = dom.Element('foo')
942 target = dom.Element('bar')
943 parent.appendChild(result)
944 parent.appendChild(target)
946 self.assertIdentical(
947 tree.findNodeJustBefore(target, [parent, result]),
950 # Also, support other configurations. This is a really not nice API.
951 newTarget = dom.Element('baz')
952 target.appendChild(newTarget)
953 self.assertIdentical(
954 tree.findNodeJustBefore(newTarget, [parent, result]),
958 def test_getSectionNumber(self):
960 L{tree.getSectionNumber} accepts an I{H2} element and returns its text
963 header = dom.Element('foo')
966 header.appendChild(text)
967 self.assertEqual(tree.getSectionNumber(header), 'foobar')
970 def test_numberDocument(self):
972 L{tree.numberDocument} inserts section numbers into the text of each
975 parent = dom.Element('foo')
976 section = dom.Element('h2')
979 section.appendChild(text)
980 parent.appendChild(section)
982 tree.numberDocument(parent, '7')
984 self.assertEqual(section.toxml(), '<h2>7.1 foo</h2>')
987 def test_parseFileAndReport(self):
989 L{tree.parseFileAndReport} parses the contents of the filename passed
990 to it and returns the corresponding DOM.
992 path = FilePath(self.mktemp())
993 path.setContent('<foo bar="baz">hello</foo>\n')
995 document = tree.parseFileAndReport(path.path)
998 '<?xml version="1.0" ?><foo bar="baz">hello</foo>')
1001 def test_parseFileAndReportMismatchedTags(self):
1003 If the contents of the file passed to L{tree.parseFileAndReport}
1004 contain a mismatched tag, L{process.ProcessingFailure} is raised
1005 indicating the location of the open and close tags which were
1008 path = FilePath(self.mktemp())
1009 path.setContent(' <foo>\n\n </bar>')
1011 err = self.assertRaises(
1012 process.ProcessingFailure, tree.parseFileAndReport, path.path)
1015 "mismatched close tag at line 3, column 4; expected </foo> "
1016 "(from line 1, column 2)")
1018 # Test a case which requires involves proper close tag handling.
1019 path.setContent('<foo><bar></bar>\n </baz>')
1021 err = self.assertRaises(
1022 process.ProcessingFailure, tree.parseFileAndReport, path.path)
1025 "mismatched close tag at line 2, column 4; expected </foo> "
1026 "(from line 1, column 0)")
1029 def test_parseFileAndReportParseError(self):
1031 If the contents of the file passed to L{tree.parseFileAndReport} cannot
1032 be parsed for a reason other than mismatched tags,
1033 L{process.ProcessingFailure} is raised with a string describing the
1036 path = FilePath(self.mktemp())
1037 path.setContent('\n foo')
1039 err = self.assertRaises(
1040 process.ProcessingFailure, tree.parseFileAndReport, path.path)
1041 self.assertEqual(str(err), 'syntax error at line 2, column 3')
1044 def test_parseFileAndReportIOError(self):
1046 If an L{IOError} is raised while reading from the file specified to
1047 L{tree.parseFileAndReport}, a L{process.ProcessingFailure} is raised
1048 indicating what the error was. The file should be closed by the
1049 time the exception is raised to the caller.
1053 def read(self, bytes=None):
1054 raise IOError(errno.ENOTCONN, 'socket not connected')
1059 theFile = FakeFile()
1060 def fakeOpen(filename):
1063 err = self.assertRaises(
1064 process.ProcessingFailure, tree.parseFileAndReport, "foo", fakeOpen)
1065 self.assertEqual(str(err), "socket not connected, filename was 'foo'")
1066 self.assertFalse(theFile._open)
1070 class XMLParsingTests(unittest.TestCase):
1072 Tests for various aspects of parsing a Lore XML input document using
1073 L{tree.parseFileAndReport}.
1075 def _parseTest(self, xml):
1076 path = FilePath(self.mktemp())
1077 path.setContent(xml)
1078 return tree.parseFileAndReport(path.path)
1081 def test_withoutDocType(self):
1083 A Lore XML input document may omit a I{DOCTYPE} declaration. If it
1084 does so, the XHTML1 Strict DTD is used.
1086 # Parsing should succeed.
1087 document = self._parseTest("<foo>uses an xhtml entity: ©</foo>")
1088 # But even more than that, the © entity should be turned into the
1089 # appropriate unicode codepoint.
1091 domhelpers.gatherTextNodes(document.documentElement),
1092 u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
1095 def test_withTransitionalDocType(self):
1097 A Lore XML input document may include a I{DOCTYPE} declaration
1098 referring to the XHTML1 Transitional DTD.
1100 # Parsing should succeed.
1101 document = self._parseTest("""\
1102 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1103 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1104 <foo>uses an xhtml entity: ©</foo>
1106 # But even more than that, the © entity should be turned into the
1107 # appropriate unicode codepoint.
1109 domhelpers.gatherTextNodes(document.documentElement),
1110 u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
1113 def test_withStrictDocType(self):
1115 A Lore XML input document may include a I{DOCTYPE} declaration
1116 referring to the XHTML1 Strict DTD.
1118 # Parsing should succeed.
1119 document = self._parseTest("""\
1120 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
1121 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1122 <foo>uses an xhtml entity: ©</foo>
1124 # But even more than that, the © entity should be turned into the
1125 # appropriate unicode codepoint.
1127 domhelpers.gatherTextNodes(document.documentElement),
1128 u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
1131 def test_withDisallowedDocType(self):
1133 A Lore XML input document may not include a I{DOCTYPE} declaration
1134 referring to any DTD other than XHTML1 Transitional or XHTML1 Strict.
1137 process.ProcessingFailure,
1140 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
1141 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
1142 <foo>uses an xhtml entity: ©</foo>
1147 class XMLSerializationTests(unittest.TestCase, _XMLAssertionMixin):
1149 Tests for L{tree._writeDocument}.
1151 def test_nonASCIIData(self):
1153 A document which contains non-ascii characters is serialized to a
1156 document = dom.Document()
1157 parent = dom.Element('foo')
1159 text.data = u'\N{SNOWMAN}'
1160 parent.appendChild(text)
1161 document.appendChild(parent)
1162 outFile = self.mktemp()
1163 tree._writeDocument(outFile, document)
1164 self.assertXMLEqual(
1165 FilePath(outFile).getContent(),
1166 u'<foo>\N{SNOWMAN}</foo>'.encode('utf-8'))
1170 class LatexSpitterTestCase(unittest.TestCase):
1172 Tests for the Latex output plugin.
1174 def test_indexedSpan(self):
1176 Test processing of a span tag with an index class results in a latex
1177 \\index directive the correct value.
1179 doc = dom.parseString('<span class="index" value="name" />').documentElement
1181 spitter = LatexSpitter(out.write)
1182 spitter.visitNode(doc)
1183 self.assertEqual(out.getvalue(), u'\\index{name}\n')
1187 class ScriptTests(unittest.TestCase):
1189 Tests for L{twisted.lore.scripts.lore}, the I{lore} command's
1192 def test_getProcessor(self):
1194 L{lore.getProcessor} loads the specified output plugin from the
1195 specified input plugin.
1197 processor = lore.getProcessor("lore", "html", options)
1198 self.assertNotIdentical(processor, None)