root/branches/mbutscher/work/extensions/HtmlExporter.py @ 287

Revision 287, 85.0 kB (checked in by mbutscher, 3 years ago)

branches/mbutscher/work:
* Table option to set CSS class (Ross' rep.: part of

073653527c0ea0109c762007b27093f28cab2061)

Line 
1from __future__ import with_statement
2## import profilehooks
3## profile = profilehooks.profile(filename="profile.prf", immediate=False)
4
5# from Enum import Enumeration
6import sys, os, os.path, string, re, traceback, locale, time, urllib
7from os.path import join, exists
8from cStringIO import StringIO
9import shutil
10
11import pwiki.urllib_red as urllib
12
13import wx
14from pwiki.rtlibRepl import minidom
15
16from pwiki.wxHelper import XrcControls, GUI_ID, wxKeyFunctionSink
17
18import Consts
19from pwiki.WikiExceptions import WikiWordNotFoundException, ExportException
20from pwiki.ParseUtilities import getFootnoteAnchorDict
21from pwiki.StringOps import *
22from pwiki import StringOps, Serialization
23from pwiki.WikiPyparsing import StackedCopyDict, SyntaxNode
24from pwiki.TempFileSet import TempFileSet
25
26from pwiki.SearchAndReplace import SearchReplaceOperation, ListWikiPagesOperation, \
27        ListItemWithSubtreeWikiPagesNode
28
29from pwiki import SystemInfo, PluginManager, OsAbstract, DocPages
30
31
32from pwiki.Exporters import AbstractExporter
33
34
35
36WIKIDPAD_PLUGIN = (("Exporters", 1),)
37
38
39def describeExportersV01(mainControl):
40    """
41    Return sequence of exporter classes.
42    """
43    return (HtmlExporter,)
44
45
46
47
48
49def removeBracketsToCompFilename(fn):
50    """
51    Combine unicodeToCompFilename() and removeBracketsFilename() from StringOps
52    """
53    return unicodeToCompFilename(removeBracketsFilename(fn))
54
55
56def _escapeAnchor(name):
57    """
58    Escape name to be usable as HTML anchor (URL fragment)
59    """
60    result = []
61    for c in name:
62        oc = ord(c)
63        if oc < 48 or (57 < oc < 65) or (90 < oc < 97) or oc > 122:
64            if oc > 255:
65                result.append("$%04x" % oc)
66            else:
67                result.append("=%02x" % oc)
68        else:
69            result.append(c)
70    return u"".join(result)
71
72# # Types of export destinations
73# EXPORT_DEST_TYPE_DIR = 1
74# EXPORT_DEST_TYPE_FILE = 2
75
76
77class BasicLinkConverter(object):
78    def __init__(self, wikiDocument, htmlExporter):
79        self.htmlExporter = htmlExporter
80        self.wikiDocument = wikiDocument
81       
82    def getLinkForWikiWord(self, word, default = None):
83        return default
84
85
86class LinkConverterForHtmlSingleFilesExport(BasicLinkConverter):
87    def getLinkForWikiWord(self, word, default = None):
88        relUnAlias = self.wikiDocument.getWikiPageNameForLinkTerm(word)
89        if relUnAlias is None:
90            return default
91        if not self.htmlExporter.shouldExport(word):
92            return default
93
94        return urlFromPathname(
95                self.htmlExporter.filenameConverter.getFilenameForWikiWord(
96                relUnAlias) + ".html")
97
98class LinkConverterForHtmlMultiPageExport(BasicLinkConverter):
99    def getLinkForWikiWord(self, word, default = None):
100        relUnAlias = self.wikiDocument.getWikiPageNameForLinkTerm(word)
101        if relUnAlias is None:
102            return default
103        if not self.htmlExporter.shouldExport(word):
104            return default
105
106        if relUnAlias not in self.htmlExporter.wordList:
107            return default
108
109        return u"#%s" % _escapeAnchor(relUnAlias)
110
111
112class FilenameConverter(object):
113    def __init__(self, asciiOnly):
114        self.asciiOnly = asciiOnly
115        self.reset()
116
117    def reset(self):
118        self._used = {}
119        self._valueSet = set()
120
121    def getFilenameForWikiWord(self, ww):
122        try:
123            return self._used[ww]
124        except KeyError:
125            for fname in iterCompatibleFilename(ww, u"",
126                    asciiOnly=self.asciiOnly, maxLength=245):
127                if not fname in self._valueSet:
128                    self._used[ww] = fname
129                    self._valueSet.add(fname)
130                    return fname
131
132
133
134class SizeValue(object):
135    """
136    Represents a single size value, either a pixel or percent size.
137    """
138
139    UNIT_INVALID = 0
140    UNIT_PIXEL = 1
141    UNIT_FACTOR = 2
142    _UNIT_PERCENT = 3
143   
144    def __init__(self, valStr):
145        self.unit = SizeValue.UNIT_INVALID
146        self.value = 0.0
147        self.setValueStr(valStr)
148
149    def getUnit(self):
150        return self.unit
151   
152    def isValid(self):
153        return self.unit != SizeValue.UNIT_INVALID
154       
155    def getValue(self):
156        return self.value
157
158    def setValueStr(self, valStr):
159        """
160        Set members fo class       
161        """
162        valStr = valStr.strip()
163        if len(valStr) == 0:
164            self.unit = SizeValue.UNIT_INVALID
165            return False
166
167        if valStr[-1] == "%":
168            valStr = valStr[:-1]
169            self.unit = SizeValue._UNIT_PERCENT
170        else:
171            self.unit = SizeValue.UNIT_PIXEL
172
173        try:
174            val = float(valStr)
175            if val >= 0.0:
176                self.value = float(val)
177                if self.unit == SizeValue._UNIT_PERCENT:
178                    self.value /= 100.0
179                    self.unit = SizeValue.UNIT_FACTOR
180               
181                return True
182            else:
183                self.unit = SizeValue.UNIT_INVALID
184                return False
185
186        except ValueError:
187            self.unit = SizeValue.UNIT_INVALID
188            return False
189
190
191
192
193# TODO UTF-8 support for HTML? Other encodings?
194
195class HtmlExporter(AbstractExporter):
196    def __init__(self, mainControl):
197        """
198        mainControl -- PersonalWikiFrame object. Part of "Exporters" plugin API
199        """
200        AbstractExporter.__init__(self, mainControl)
201        self.wordList = None
202        self.exportDest = None
203       
204        # List of tuples (<source CSS path>, <dest CSS file name / url>)
205        self.styleSheetList = []
206        self.basePageAst = None
207               
208        self.exportType = None
209        self.progressHandler = None
210        self.referencedStorageFiles = None
211       
212        self.linkConverter = None
213        self.compatFilenames = None
214        self.listPagesOperation = None
215
216        self.wordAnchor = None  # For multiple wiki pages in one HTML page, this contains the anchor
217                # of the current word.
218        self.tempFileSet = None
219        self.copiedTempFileCache = None  # Dictionary {<original path>: <target URL>}
220        self.filenameConverter = FilenameConverter(False)
221#         self.convertFilename = removeBracketsFilename   # lambda s: mbcsEnc(s, "replace")[0]
222
223        self.result = None
224       
225        # Flag to control how to push output into self.result
226        self.outFlagEatPostBreak = False
227        self.outFlagPostBreakEaten = False
228       
229        self.__sinkWikiDocument = None
230
231    def setWikiDocument(self, wikiDocument):
232        self.wikiDocument = wikiDocument
233        if self.wikiDocument is not None:
234            self.buildStyleSheetList()
235
236    @staticmethod
237    def getExportTypes(mainControl, continuousExport=False):
238        """
239        Part of "Exporters" plugin API.
240        Return sequence of tuples with the description of export types provided
241        by this object. A tuple has the form (<exp. type>,
242            <human readable description>)
243        All exporters must provide this as a static method (which can be called
244        without constructing an object first.
245
246        mainControl -- PersonalWikiFrame object
247        continuousExport -- If True, only types with support for continuous export
248        are listed.
249        """
250        return (
251            (u"html_multi", _(u'One HTML page')),
252            (u"html_single", _(u'Set of HTML pages'))
253            )
254
255
256    def getAddOptPanelsForTypes(self, guiparent, exportTypes):
257        """
258        Part of "Exporters" plugin API.
259        Construct all necessary GUI panels for additional options
260        for the types contained in exportTypes.
261        Returns sequence of tuples (<exp. type>, <panel for add. options or None>)
262
263        The panels should use  guiparent  as parent.
264        If the same panel is used for multiple export types the function can
265        and should include all export types for this panel even if some of
266        them weren't requested. Panel objects must not be shared by different
267        exporter classes.
268        """
269        if not u"html_multi" in exportTypes and \
270                not u"html_single" in exportTypes:
271            return ()
272
273        res = wx.xrc.XmlResource.Get()
274        htmlPanel = res.LoadPanel(guiparent, "ExportSubHtml")
275        ctrls = XrcControls(htmlPanel)
276        config = self.mainControl.getConfig()
277
278        ctrls.cbPicsAsLinks.SetValue(config.getboolean("main",
279                "html_export_pics_as_links"))
280        ctrls.chTableOfContents.SetSelection(config.getint("main",
281                "export_table_of_contents"))
282        ctrls.tfHtmlTocTitle.SetValue(config.get("main",
283                "html_toc_title"))
284
285        return (
286            (u"html_multi", htmlPanel),
287            (u"html_single", htmlPanel)
288            )
289
290
291    def getExportDestinationWildcards(self, exportType):
292        """
293        Part of "Exporters" plugin API.
294        If an export type is intended to go to a file, this function
295        returns a (possibly empty) sequence of tuples
296        (wildcard description, wildcard filepattern).
297       
298        If an export type goes to a directory, None is returned
299        """
300        return None
301
302
303    def getAddOptVersion(self):
304        """
305        Part of "Exporters" plugin API.
306        Returns the version of the additional options information returned
307        by getAddOpt(). If the return value is -1, the version info can't
308        be stored between application sessions.
309       
310        Otherwise, the addopt information can be stored between sessions
311        and can later handled back to the export method of the object
312        without previously showing the export dialog.
313        """
314        return 0
315
316
317    def getAddOpt(self, addoptpanel):
318        """
319        Part of "Exporters" plugin API.
320        Reads additional options from panel addoptpanel.
321        If getAddOptVersion() > -1, the return value must be a sequence
322        of simple string, unicode and/or numeric objects. Otherwise, any object
323        can be returned (normally the addoptpanel itself).
324        Here, it returns a tuple with following items:
325            * bool (as integer) if pictures should be exported as links
326            * integer to control creation of table of contents
327                (0: No; 1: as tree; 2: as list)
328            * unistring: TOC title
329            * unistring: name of export subdir for volatile files
330                (= automatically generated files, e.g. formula images
331                from MimeTeX).
332        """
333        if addoptpanel is None:
334            # Return default set in options
335            config = self.mainControl.getConfig()
336
337            return ( boolToInt(config.getboolean("main",
338                    "html_export_pics_as_links")),
339                    config.getint("main", "export_table_of_contents"),
340                    config.get("main", "html_toc_title"),
341                    u"volatile"
342                     )
343        else:
344            ctrls = XrcControls(addoptpanel)
345
346            picsAsLinks = boolToInt(ctrls.cbPicsAsLinks.GetValue())
347            tableOfContents = ctrls.chTableOfContents.GetSelection()
348            tocTitle = ctrls.tfHtmlTocTitle.GetValue()
349
350            return (picsAsLinks, tableOfContents, tocTitle, u"volatile")
351
352
353    def setAddOpt(self, addOpt, addoptpanel):
354        """
355        Part of "Exporters" plugin API.
356        Shows content of addOpt in the addoptpanel (must not be None).
357        This function is only called if getAddOptVersion() != -1.
358        """
359        picsAsLinks, tableOfContents, tocTitle, volatileDir = \
360                addOpt[:4]
361
362        # volatileDir is currently ignored
363
364        ctrls = XrcControls(addoptpanel)
365
366        ctrls.cbPicsAsLinks.SetValue(picsAsLinks != 0)
367        ctrls.chTableOfContents.SetSelection(tableOfContents)
368        ctrls.tfHtmlTocTitle.SetValue(tocTitle)
369
370       
371
372    def setJobData(self, wikiDocument, wordList, exportType, exportDest,
373            compatFilenames, addOpt, progressHandler):
374        """
375        Set all information necessary to run export operation.
376        """
377
378        self.setWikiDocument(wikiDocument)
379
380        self.wordList = []
381        for w in wordList:
382            if self.wikiDocument.isDefinedWikiLinkTerm(w):
383                self.wordList.append(w)
384
385        if len(self.wordList) == 0:
386            return False
387
388#         self.wordList = wordList
389        self.exportType = exportType
390        self.exportDest = exportDest
391        self.addOpt = addOpt
392        self.progressHandler = progressHandler
393        self.compatFilenames = compatFilenames
394
395#             self.convertFilename = removeBracketsToCompFilename
396        self.filenameConverter = FilenameConverter(bool(compatFilenames))
397#         else:
398#             self.convertFilename = removeBracketsFilename    # lambda s: mbcsEnc(s, "replace")[0]
399
400        self.referencedStorageFiles = None
401       
402        return True
403
404
405    def export(self, wikiDocument, wordList, exportType, exportDest,
406            compatFilenames, addOpt, progressHandler, tempFileSetReset=True):
407        """
408        Part of "Exporters" plugin API.
409        Run export operation. This is only called for real exports,
410        previews use other functions.
411       
412        wikiDocument -- wikiDocument object
413        wordList -- Sequence of wiki words to export
414        exportType -- string tag to identify how to export
415        exportDest -- Path to destination directory or file to export to
416        compatFilenames -- Should the filenames be encoded to be lowest
417                           level compatible (ascii only)?
418        addOpt -- additional options returned by getAddOpt()
419        """
420        if not self.setJobData(wikiDocument, wordList, exportType, exportDest,
421                compatFilenames, addOpt, progressHandler):
422            return
423           
424        if exportType in (u"html_single", u"html_multi"):
425            volatileDir = self.addOpt[3]
426
427            volatileDir = join(self.exportDest, volatileDir)
428
429            # Check if volatileDir is really a subdirectory of exportDest
430            clearVolatile = testContainedInDir(self.exportDest, volatileDir)
431            if clearVolatile:
432                # Warning!!! rmtree() is very dangerous, don't make a mistake here!
433                shutil.rmtree(volatileDir, True)
434
435            # We must prepare a temporary file set for HTML exports
436            self.tempFileSet = TempFileSet()
437            self.tempFileSet.setPreferredPath(volatileDir)
438            self.tempFileSet.setPreferredRelativeTo(self.exportDest)
439
440            self.referencedStorageFiles = set()
441
442
443        if exportType == u"html_multi":
444            browserFile = self.exportHtmlMultiFile()
445        elif exportType == u"html_single":
446            browserFile = self._exportHtmlSingleFiles(self.wordList)
447
448        # Other supported types: html_previewWX, html_previewIE, html_previewMOZ,
449        #   html_previewWK
450        # are not handled in this function
451
452        wx.GetApp().getInsertionPluginManager().taskEnd()
453
454        if self.referencedStorageFiles is not None:
455            # Some files must be available
456            wikiPath = self.wikiDocument.getWikiPath()
457           
458            if not OsAbstract.samefile(wikiPath, self.exportDest):
459                # Now we have to copy the referenced files to new location
460                for rsf in self.referencedStorageFiles:
461                    try:
462                        OsAbstract.copyFile(join(wikiPath, rsf),
463                                join(self.exportDest, rsf))
464                    except IOError, e:
465                        raise ExportException(unicode(e))
466
467
468        if self.mainControl.getConfig().getboolean(
469                "main", "start_browser_after_export") and browserFile:
470            OsAbstract.startFile(self.mainControl, browserFile)
471
472        if tempFileSetReset:
473            self.tempFileSet.reset()
474            self.tempFileSet = None
475            self.copiedTempFileCache = None
476
477
478    def startContinuousExport(self, wikiDocument, listPagesOperation,
479            exportType, exportDest, compatFilenames, addOpt, progressHandler):
480       
481        self.listPagesOperation = listPagesOperation
482
483        wordList = wikiDocument.searchWiki(self.listPagesOperation)
484       
485        self.listPagesOperation.beginWikiSearch(wikiDocument)
486
487        # Initially static export
488        self.export(wikiDocument, wordList, exportType, exportDest,
489            compatFilenames, addOpt, progressHandler, tempFileSetReset=False)
490           
491        self.progressHandler = None
492
493        self.__sinkWikiDocument = wxKeyFunctionSink((
494                ("deleted wiki page", self.onDeletedWikiPage),
495                ("renamed wiki page", self.onRenamedWikiPage),
496                ("updated wiki page", self.onUpdatedWikiPage)
497#                 ("saving new wiki page", self.onSavingNewWikiPage)
498        ), self.wikiDocument.getMiscEvent())
499
500
501    def stopContinuousExport(self):
502        self.listPagesOperation.endWikiSearch()
503        self.listPagesOperation = None
504        self.__sinkWikiDocument.disconnect()
505
506        self.tempFileSet.reset()
507        self.tempFileSet = None
508        self.copiedTempFileCache = None
509
510
511    def onDeletedWikiPage(self, miscEvt):
512        wikiWord = miscEvt.get("wikiPage").getWikiWord()
513
514        if wikiWord not in self.wordList:
515            return
516           
517        self.wordList.remove(wikiWord)
518       
519        if self.exportType == u"html_multi":
520            self.exportHtmlMultiFile()
521
522        elif self.exportType == u"html_single":
523            self._exportHtmlSingleFiles([])
524
525
526    def onRenamedWikiPage(self, miscEvt):
527        oldWord = miscEvt.get("wikiPage").getWikiWord()
528        newWord = miscEvt.get("newWord")
529        newPage = self.wikiDocument.getWikiPage(newWord)
530       
531        oldInList = oldWord in self.wordList
532        newInList = self.listPagesOperation.testWikiPageByDocPage(newPage)
533
534        if not oldInList and not newInList:
535            return
536
537        if oldInList:
538            self.wordList.remove(oldWord)
539       
540        if newInList:
541            self.wordList.append(newWord)
542
543
544        if self.exportType == u"html_multi":
545            self.exportHtmlMultiFile()
546
547        elif self.exportType == u"html_single":
548            if newInList:
549                updList = [newWord]
550            else:
551                updList = []
552
553            self._exportHtmlSingleFiles(updList)
554
555
556    def onUpdatedWikiPage(self, miscEvt):
557        wikiPage = miscEvt.get("wikiPage")
558        wikiWord = wikiPage.getWikiWord()
559
560        oldInList = wikiWord in self.wordList
561        newInList = self.listPagesOperation.testWikiPageByDocPage(wikiPage)
562
563        if not oldInList:
564            if not newInList:
565                # Current set not affected
566                return
567            else:
568                self.wordList.append(wikiWord)
569                updList = [wikiWord]
570        else:
571            if not newInList:
572                self.wordList.remove(wikiWord)
573                updList = []
574            else:
575                updList = [wikiWord]
576       
577        if not wikiWord in self.wordList:
578            return
579
580        try:
581            if self.exportType == u"html_multi":
582                self.exportHtmlMultiFile()
583   
584            elif self.exportType == u"html_single":
585                self._exportHtmlSingleFiles(updList)
586        except WikiWordNotFoundException:
587            pass
588
589
590
591    def getTempFileSet(self):
592        return self.tempFileSet
593
594
595    _INTERNALJUMP_PREFIXMAP = {
596        u"html_previewWX": u"internaljump:",
597        u"html_previewIE": u"http://internaljump/",
598        u"html_previewMOZ": u"file://internaljump/",
599        u"html_previewWK": u"http://internaljump/"
600    }
601
602    def _getInternaljumpPrefix(self):
603        try:
604            return self._INTERNALJUMP_PREFIXMAP[self.exportType]
605        except IndexError:
606            raise InternalError(
607                    u"Trying to get internal jump prefix for non-preview export")
608
609
610    def setLinkConverter(self, linkConverter):
611        self.linkConverter = linkConverter
612
613
614    def exportHtmlMultiFile(self, realfp=None, tocMode=None):
615        """
616        Multiple wiki pages in one file.
617        """
618        config = self.mainControl.getConfig()
619        sepLineCount = config.getint("main",
620                "html_export_singlePage_sepLineCount", 10)
621
622        if sepLineCount < 0:
623            sepLineCount = 10
624#         if len(self.wordList) == 1:
625#             self.exportType = u"html_single"
626#             return self._exportHtmlSingleFiles(self.wordList)
627
628        self.setLinkConverter(LinkConverterForHtmlMultiPageExport(
629                self.wikiDocument, self))
630
631        self.buildStyleSheetList()
632
633        if realfp is None:
634            outputFile = join(self.exportDest,
635                    self.filenameConverter.getFilenameForWikiWord(
636                    self.mainControl.wikiName) + ".html")
637
638            if exists(pathEnc(outputFile)):
639                os.unlink(pathEnc(outputFile))
640
641            realfp = open(pathEnc(outputFile), "w")
642        else:
643            outputFile = None
644
645        filePointer = utf8Writer(realfp, "replace")
646
647        filePointer.write(self.getFileHeaderMultiPage(self.mainControl.wikiName))
648
649        tocTitle = self.addOpt[2]
650       
651        if tocMode is None:
652            tocMode = self.addOpt[1]
653
654        if tocMode == 1:
655            # Write a content tree at beginning
656            rootPage = self.mainControl.getWikiDocument().getWikiPage(
657                        self.mainControl.getWikiDocument().getWikiName())
658            flatTree = rootPage.getFlatTree()
659
660            filePointer.write((u'<h2>%s</h2>\n'
661                    '%s%s<hr size="1"/>') %
662                    (tocTitle, # = "Table of Contents"
663                    self.getContentTreeBody(flatTree, linkAsFragments=True),
664                    u'<br />\n' * sepLineCount))
665
666        elif tocMode == 2:
667            # Write a content list at beginning
668            filePointer.write((u'<h2>%s</h2>\n'
669                    '%s%s<hr size="1"/>') %
670                    (tocTitle, # = "Table of Contents"
671                    self.getContentListBody(linkAsFragments=True),
672                    u'<br />\n' * sepLineCount))
673
674
675        if self.progressHandler is not None:
676            self.progressHandler.open(len(self.wordList))
677            step = 0
678
679        # Then create the big page word by word
680        for word in self.wordList:
681            if self.progressHandler is not None:
682                step += 1
683                self.progressHandler.update(step, _(u"Exporting %s") % word)
684
685            wikiPage = self.wikiDocument.getWikiPage(word)
686            if not self.shouldExport(word, wikiPage):
687                continue
688
689            try:
690                content = wikiPage.getLiveText()
691#                 formatDetails = wikiPage.getFormatDetails()
692                   
693                self.wordAnchor = _escapeAnchor(word)
694                formattedContent = self.formatContent(wikiPage)
695
696                filePointer.write((u'<span class="wiki-name-ref">'
697                        u'[<a name="%s">%s</a>]</span><br /><br />'
698                        u'<span class="parent-nodes">parent nodes: %s</span>'
699                        u'<br />%s%s<hr size="1"/>') %
700                        (self.wordAnchor, word,
701                        self.getParentLinks(wikiPage, False), formattedContent,
702                        u'<br />\n' * sepLineCount))
703            except Exception, e:
704                traceback.print_exc()
705
706        self.wordAnchor = None
707
708        filePointer.write(self.getFileFooter())
709       
710        filePointer.reset()
711
712        if outputFile is not None:
713            realfp.close()
714
715        self.copyCssFiles(self.exportDest)
716        return outputFile
717
718
719    def _exportHtmlSingleFiles(self, wordListToUpdate):
720        self.setLinkConverter(LinkConverterForHtmlSingleFilesExport(
721                self.wikiDocument, self))
722        self.buildStyleSheetList()
723
724
725        if self.addOpt[1] in (1, 2):
726            # TODO Configurable name
727            outputFile = join(self.exportDest, pathEnc(u"index.html"))
728            try:
729                if exists(pathEnc(outputFile)):
730                    os.unlink(pathEnc(outputFile))
731   
732                realfp = open(pathEnc(outputFile), "w")
733                fp = utf8Writer(realfp, "replace")
734
735                # TODO Factor out HTML header generation               
736                fp.write(self._getGenericHtmlHeader(self.addOpt[2]) +
737                        u"    <body>\n")
738                if self.addOpt[1] == 1:
739                    # Write a content tree
740                    rootPage = self.mainControl.getWikiDocument().getWikiPage(
741                                self.mainControl.getWikiDocument().getWikiName())
742                    flatTree = rootPage.getFlatTree()
743   
744                    fp.write((u'<h2>%s</h2>\n'
745                            '%s') %
746                            (self.addOpt[2],  # = "Table of Contents"
747                            self.getContentTreeBody(flatTree, linkAsFragments=False)
748                            ))
749                elif self.addOpt[1] == 2:
750                    # Write a content list
751                    fp.write((u'<h2>%s</h2>\n'
752                            '%s') %
753                            (self.addOpt[2],  # = "Table of Contents"
754                            self.getContentListBody(linkAsFragments=False)
755                            ))
756
757                fp.write(self.getFileFooter())
758
759                fp.reset()       
760                realfp.close()
761            except Exception, e:
762                traceback.print_exc()
763
764
765        if self.progressHandler is not None:
766            self.progressHandler.open(len(self.wordList))
767            step = 0
768
769        for word in wordListToUpdate:
770            if self.progressHandler is not None:
771                step += 1
772                self.progressHandler.update(step, _(u"Exporting %s") % word)
773
774            wikiPage = self.wikiDocument.getWikiPage(word)
775            if not self.shouldExport(word, wikiPage):
776                continue
777
778            self.exportWordToHtmlPage(self.exportDest, word, False)
779
780        self.copyCssFiles(self.exportDest)
781        rootFile = join(self.exportDest,
782                self.filenameConverter.getFilenameForWikiWord(self.wordList[0]) +
783                ".html")
784        return rootFile
785
786
787    def exportWordToHtmlPage(self, dir, word, startFile=True,
788            onlyInclude=None):
789           
790        outputFile = join(dir,
791                self.filenameConverter.getFilenameForWikiWord(word) + ".html")
792
793        try:
794            if exists(pathEnc(outputFile)):
795                os.unlink(pathEnc(outputFile))
796
797            realfp = open(pathEnc(outputFile), "w")
798            fp = utf8Writer(realfp, "replace")
799           
800            wikiPage = self.wikiDocument.getWikiPage(word)
801            fp.write(self.exportWikiPageToHtmlString(wikiPage,
802                    startFile, onlyInclude))
803            fp.reset()       
804            realfp.close()
805        except Exception, e:
806            sys.stderr.write("Error while exporting word %s" % repr(word))
807            traceback.print_exc()
808
809        return outputFile
810
811
812    def exportWikiPageToHtmlString(self, wikiPage,
813            startFile=True, onlyInclude=None):
814        """
815        Read content of wiki word word, create an HTML page and return it
816        """
817        result = []
818
819        formattedContent = self.formatContent(wikiPage)
820
821        if SystemInfo.isUnicode():
822            result.append(self.getFileHeader(wikiPage))
823
824        # if startFile is set then this is the only page being exported so
825        # do not include the parent header.
826        if not startFile:
827            result.append((u'<span class="parent-nodes">parent nodes: %s</span>'
828                    '<br /><br />\n')
829                    % self.getParentLinks(wikiPage, True, onlyInclude))
830
831        result.append(formattedContent)
832        result.append(self.getFileFooter())
833       
834        return u"".join(result)
835
836
837    def _getGenericHtmlHeader(self, title, charSet=u'; charset=UTF-8'):
838        styleSheets = []
839        for dummy, url in self.styleSheetList:
840            styleSheets.append(
841                    u'        <link type="text/css" rel="stylesheet" href="%(url)s">' %
842                    locals())
843       
844        styleSheets = u"\n".join(styleSheets)
845
846#         styleSheet = self.styleSheet
847        config = self.mainControl.getConfig()
848        docType = config.get("main", "html_header_doctype",
849                'DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"')
850
851        return u"""<!%(docType)s>
852<html>
853    <head>
854        <meta http-equiv="content-type" content="text/html%(charSet)s">
855        <title>%(title)s</title>
856%(styleSheets)s
857    </head>
858""" % locals()
859
860
861
862    def getFileHeaderMultiPage(self, title):
863        """
864        Return file header for an HTML file containing multiple pages
865        """
866        return self._getGenericHtmlHeader(title) + u"    <body>\n"
867
868
869    def _getBodyTag(self, wikiPage):
870        # Get application defaults from config
871        config = self.mainControl.getConfig()
872        linkcol = config.get("main", "html_body_link")
873        alinkcol = config.get("main", "html_body_alink")
874        vlinkcol = config.get("main", "html_body_vlink")
875        textcol = config.get("main", "html_body_text")
876        bgcol = config.get("main", "html_body_bgcolor")
877        bgimg = config.get("main", "html_body_background")
878
879        # Get attribute settings
880        linkcol = wikiPage.getAttributeOrGlobal(u"html.linkcolor", linkcol)
881        alinkcol = wikiPage.getAttributeOrGlobal(u"html.alinkcolor", alinkcol)
882        vlinkcol = wikiPage.getAttributeOrGlobal(u"html.vlinkcolor", vlinkcol)
883        textcol = wikiPage.getAttributeOrGlobal(u"html.textcolor", textcol)
884        bgcol = wikiPage.getAttributeOrGlobal(u"html.bgcolor", bgcol)
885        bgimg = wikiPage.getAttributeOrGlobal(u"html.bgimage", bgimg)
886       
887        # Filter color
888        def filterCol(col, prop):
889            if colorDescToRgbTuple(col) is not None:
890                return u'%s="%s"' % (prop, col)
891            else:
892                return u''
893       
894        linkcol = filterCol(linkcol, u"link")
895        alinkcol = filterCol(alinkcol, u"alink")
896        vlinkcol = filterCol(vlinkcol, u"vlink")
897        textcol = filterCol(textcol, u"text")
898        bgcol = filterCol(bgcol, u"bgcolor")
899       
900        if bgimg:
901            if bgimg.startswith(u"rel://"):
902                # Relative URL
903                if self.asHtmlPreview:
904                    # If preview, make absolute
905                    bgimg = self.wikiDocument.makeRelUrlAbsolute(bgimg)
906                else:
907                    # If export, reformat a bit
908                    bgimg = bgimg[6:]
909
910            bgimg = u'background="%s"' % bgimg
911        else:
912            bgimg = u''
913           
914           
915        if self.exportType in (u"html_previewIE", u"html_previewMOZ", u"html_previewWK"):
916            dblClick = 'ondblclick="window.location.href = &quot;' + \
917                    self._getInternaljumpPrefix() + \
918                    'mouse/leftdoubleclick/preview/body&quot;;"'
919
920        else:
921            dblClick = ''
922
923        # Build tagstring
924        bodytag = u" ".join((linkcol, alinkcol, vlinkcol, textcol, bgcol, bgimg, dblClick))
925        if len(bodytag) > 6:  # the 6 spaces
926            bodytag = "<body %s>" % bodytag
927        else:
928            bodytag = "<body>"
929
930        return bodytag
931
932
933    def getFileHeader(self, wikiPage):
934        """
935        Return the header part of an HTML file for wikiPage.
936        wikiPage -- WikiPage object
937        """
938
939        return self._getGenericHtmlHeader(wikiPage.getWikiWord()) + \
940                u"    %s\n" % self._getBodyTag(wikiPage)
941
942
943
944    def getFileFooter(self):
945        return u"""    </body>
946</html>
947"""
948
949    def getParentLinks(self, wikiPage, asHref=True, wordsToInclude=None):
950        parents = u""
951        parentRelations = wikiPage.getParentRelationships()[:]
952        self.mainControl.getCollator().sort(parentRelations)
953       
954        for relation in parentRelations:
955            if wordsToInclude and relation not in wordsToInclude:
956                continue
957
958            if parents != u"":
959                parents = parents + u" | "
960
961            if asHref:
962                parents = parents +\
963                        u'<span class="parent-node"><a href="%s">%s</a></span>' %\
964                        (self.linkConverter.getLinkForWikiWord(relation), relation)
965#                         u'<span class="parent-node"><a href="%s.html">%s</a></span>' %\
966#                         (self.filenameConverter.getFilenameForWikiWord(relation), relation)
967            else:
968                parents = parents +\
969                u'<span class="parent-node"><a href="#%s">%s</a></span>' %\
970                (_escapeAnchor(relation), relation)
971
972        return parents
973
974
975    def getBasePageAst(self):
976        return self.basePageAst
977
978
979    def buildStyleSheetList(self):
980        """
981        Sets the self.styleSheetList. This is a list of tuples
982        (<source CSS path>, <dest CSS file name/url>). The source file name may be
983        None if the file (normally for preview mode) shouldn't be copied.
984        Must be called after export type is set!
985        """
986        asPreview = self.exportType in ("html_previewIE", "html_previewMOZ", u"html_previewWK", "html_previewWX")
987        if asPreview:
988            # Step one: Create paths
989            pathlist = [
990                    # System base file
991                    join(self.mainControl.wikiAppDir, "appbase.css"),
992                    # Administrator modified application base file
993                    join(self.mainControl.wikiAppDir, "export",
994                        "wikistyle.css"),
995
996                    # User modified file
997                    join(wx.GetApp().globalConfigSubDir, "wikistyle.css")
998                ]
999
1000            # Wiki specific file
1001            if self.wikiDocument is not None:
1002                pathlist.append(join(self.wikiDocument.getDataDir(),
1003                        "wikistyle.css"))
1004
1005            # Overruling wikipreview.css files
1006            pathlist += [
1007                    # System base file (does not exist normally)
1008                    join(self.mainControl.wikiAppDir, "prevappbase.css"),
1009                    # Administrator modified application base file
1010                    join(self.mainControl.wikiAppDir, "export",
1011                            "wikipreview.css"),
1012                    # User modified file
1013                    join(wx.GetApp().globalConfigSubDir, "wikipreview.css")
1014                ]
1015
1016            # Wiki specific file
1017            if self.wikiDocument is not None:
1018                pathlist.append(join(self.wikiDocument.getDataDir(),
1019                        "wikipreview.css"))
1020
1021            # Step two: Check files for existence and create styleSheetList
1022            # We don't need the source paths, only a list of URLs to the
1023            # original files
1024            self.styleSheetList = []
1025            for p in pathlist:
1026                if not exists(pathEnc(p)):
1027                    continue
1028               
1029                self.styleSheetList.append((None, "file:" + urlFromPathname(p)))
1030           
1031        else:
1032            result = [
1033                    # System base file
1034                    (join(self.mainControl.wikiAppDir, "appbase.css"), "appbase.css"),
1035                    # Administrator modified application base file
1036                    (join(self.mainControl.wikiAppDir, "export", "wikistyle.css"),
1037                        "admbase.css"),
1038                    # User modified file
1039                    (join(wx.GetApp().globalConfigSubDir, "wikistyle.css"),
1040                        "userbase.css")
1041                ]
1042
1043            # Wiki specific file
1044            if self.wikiDocument is not None:
1045                result.append((join(self.wikiDocument.getDataDir(),
1046                        "wikistyle.css"), "wikistyle.css"))
1047
1048            # Filter non-existent
1049            self.styleSheetList = [ item for item in result
1050                    if exists(pathEnc(item[0])) ]
1051
1052
1053
1054
1055    def copyCssFiles(self, dir):
1056        for src, dst in self.styleSheetList:
1057            if src is None:
1058                continue
1059            try:
1060                OsAbstract.copyFile(pathEnc(src), pathEnc(join(dir, dst)))
1061            except:
1062                traceback.print_exc()
1063
1064
1065    def shouldExport(self, wikiWord, wikiPage=None):
1066        if not wikiPage:
1067            try:
1068                wikiPage = self.wikiDocument.getWikiPage(wikiWord)
1069            except WikiWordNotFoundException:
1070                return False
1071
1072        return strToBool(wikiPage.getAttributes().get("export", ("True",))[-1])
1073
1074
1075    def getContentListBody(self, linkAsFragments):
1076#         if linkAsFragments:
1077#             def wordToLink(wikiWord):
1078#                 relUnAlias = self.wikiDocument.getAliasesWikiWord(wikiWord)
1079#                 # TODO Use self.convertFilename here?
1080#                 return u"#%s" % _escapeAnchor(relUnAlias)
1081#         else:
1082#             def wordToLink(wikiWord):
1083#                 relUnAlias = self.wikiDocument.getAliasesWikiWord(wikiWord)
1084#                 # TODO Use self.convertFilename here?
1085#                 return self.linkConverter.getLinkForWikiWord(relUnAlias)
1086
1087        result = []
1088        wordToLink = self.linkConverter.getLinkForWikiWord
1089       
1090        result.append(u"<ul>\n")
1091        for wikiWord in self.wordList:
1092            result.append(u'<li><a href="%s">%s</a>\n' % (wordToLink(wikiWord),
1093                    wikiWord))
1094
1095        result.append(u"</ul>\n")
1096       
1097        return u"".join(result)
1098
1099
1100    def getContentTreeBody(self, flatTree, linkAsFragments):   # rootWords
1101        """
1102        Return content tree.
1103        flatTree -- flat tree as returned by DocPages.WikiPage.getFlatTree(),
1104            list of tuples (wikiWord, deepness)
1105        """
1106#         if linkAsFragments:
1107#             def wordToLink(wikiWord):
1108#                 relUnAlias = self.wikiDocument.getAliasesWikiWord(wikiWord)
1109#                 # TODO Use self.convertFilename here?
1110#                 return u"#%s" % _escapeAnchor(relUnAlias)
1111#         else:
1112#             def wordToLink(wikiWord):
1113#                 relUnAlias = self.wikiDocument.getAliasesWikiWord(wikiWord)
1114#                 # TODO Use self.convertFilename here?
1115#                 return self.linkConverter.getLinkForWikiWord(relUnAlias)
1116
1117        wordSet = set(self.wordList)
1118        deepStack = [-1]
1119        result = []
1120        wordToLink = self.linkConverter.getLinkForWikiWord
1121        lastdeepness = 0
1122       
1123        for wikiWord, deepness in flatTree:
1124            if not wikiWord in wordSet:
1125                continue
1126               
1127            deepness += 1
1128            if deepness > lastdeepness:
1129                # print "getContentTreeBody9", deepness, lastdeepness
1130                result.append(u"<ul>\n" * (deepness - lastdeepness))
1131            elif deepness < lastdeepness:
1132                # print "getContentTreeBody10", deepness, lastdeepness
1133                result.append(u"</ul>\n" * (lastdeepness - deepness))
1134               
1135            lastdeepness = deepness
1136
1137            wordSet.remove(wikiWord)
1138
1139            # print "getContentTreeBody11", repr(wikiWord)
1140            result.append(u'<li><a href="%s">%s</a>\n' % (wordToLink(wikiWord),
1141                    wikiWord))
1142
1143        result.append(u"</ul>\n" * lastdeepness)
1144
1145        # list words not in the tree
1146        if len(wordSet) > 0:
1147            # print "getContentTreeBody13"
1148            remainList = list(wordSet)
1149            self.mainControl.getCollator().sort(remainList)
1150           
1151            # print "getContentTreeBody14", repr(remainList)
1152            result.append(u"<ul>\n")
1153            for wikiWord in remainList:
1154                result.append(u'<li><a href="%s">%s</a>\n' % (wordToLink(wikiWord),
1155                        wikiWord))
1156
1157            result.append(u"</ul>\n")
1158
1159
1160        return u"".join(result)
1161
1162
1163    def getCurrentWikiWord(self):
1164        """
1165        Returns the wiki word which is currently processed by the exporter.
1166        """
1167        return self.wikiWord
1168
1169
1170    def formatContent(self, wikiPage, content=None):
1171        word = wikiPage.getWikiWord()
1172        formatDetails = wikiPage.getFormatDetails()
1173        if content is None:
1174            content = wikiPage.getLiveText()
1175            self.basePageAst = wikiPage.getLivePageAst()
1176        else:
1177            self.basePageAst = wikiPage.parseTextInContext(content)
1178
1179        if self.linkConverter is None:
1180            self.linkConverter = BasicLinkConverter(self.wikiDocument, self)
1181 
1182        self.asIntHtmlPreview = (self.exportType == "html_previewWX")
1183        self.asHtmlPreview = self.exportType in ("html_previewWX",
1184                "html_previewIE", "html_previewMOZ", u"html_previewWK")
1185        self.wikiWord = word
1186
1187        self.result = []
1188        self.optsStack = StackedCopyDict()
1189        self.insertionVisitStack = []
1190        self.astNodeStack = []
1191        self.copiedTempFileCache = {}
1192
1193        self.outFlagEatPostBreak = False
1194        self.outFlagPostBreakEaten = False
1195
1196        # Get attribute pattern
1197        if self.asHtmlPreview:
1198            proppattern = self.mainControl.getConfig().get(
1199                        "main", "html_preview_proppattern", u"")
1200        else:
1201            proppattern = self.mainControl.getConfig().get(
1202                        "main", "html_export_proppattern", u"")
1203
1204        self.proppattern = re.compile(proppattern,
1205                re.DOTALL | re.UNICODE | re.MULTILINE)
1206
1207        if self.asHtmlPreview:
1208            self.proppatternExcluding = self.mainControl.getConfig().getboolean(
1209                        "main", "html_preview_proppattern_is_excluding", u"True")
1210        else:
1211            self.proppatternExcluding = self.mainControl.getConfig().getboolean(
1212                        "main", "html_export_proppattern_is_excluding", u"True")
1213
1214        if self.asHtmlPreview:
1215            facename = self.mainControl.getConfig().get(
1216                    "main", "facename_html_preview", u"")
1217            if facename:
1218                self.outAppend('<font face="%s">' % facename)
1219
1220        with self.optsStack:
1221            self.optsStack["innermostFullPageAst"] = self.basePageAst
1222            self.optsStack["innermostPageUnifName"] = u"wikipage/" + word
1223            self.processAst(content, self.basePageAst)
1224
1225        if self.asHtmlPreview and facename:
1226            self.outAppend('</font>')
1227
1228        return self.getOutput()
1229
1230
1231    def _getImageDims(self, absUrl):
1232        """
1233        Return tuple (width, height) of image absUrl or (None, None) if it
1234        couldn't be determined.
1235        """
1236        try:
1237            if absUrl.startswith(u"file:"):
1238                absLink = pathnameFromUrl(absUrl)
1239                imgFile = file(absLink, "rb")
1240            else:
1241                imgFile = urllib.urlopen(absUrl)
1242                imgData = imgFile.read()
1243                imgFile.close()
1244                imgFile = StringIO(imgData)
1245
1246            img = wx.EmptyImage(0, 0)
1247            img.LoadStream(imgFile)
1248            imgFile.close()
1249           
1250            if img.Ok():
1251                return img.GetWidth(), img.GetHeight()
1252
1253            return None, None
1254
1255        except IOError:
1256            return None, None
1257
1258
1259    def isHtmlSizeValue(sizeStr):
1260        """
1261        Test unistring sizestr if it is a valid HTML size info and returns
1262        True or False
1263        """
1264        sizeStr = sizeStr.strip()
1265        if len(sizeStr) == 0:
1266            return False
1267
1268        if sizeStr[-1] == "%":
1269            sizeStr = sizeStr[:-1]
1270
1271        try:
1272            val = int(sizeStr)
1273            return val >= 0
1274        except ValueError:
1275            return False
1276
1277    isHtmlSizeValue = staticmethod(isHtmlSizeValue)
1278
1279
1280    def outAppend(self, toAppend, eatPreBreak=False, eatPostBreak=False):
1281        """
1282        Append toAppend to self.result, maybe remove or modify it according to
1283        flags
1284        """
1285        if toAppend == u"":    # .strip()
1286            return
1287
1288        if self.outFlagEatPostBreak and toAppend.strip() == u"<br />":
1289            self.outFlagEatPostBreak = eatPostBreak
1290            self.outFlagPostBreakEaten = True
1291            return
1292
1293        if eatPreBreak and len(self.result) > 0 and \
1294                self.result[-1].strip() == u"<br />" and \
1295                not self.outFlagPostBreakEaten:
1296            self.result[-1] = toAppend
1297            self.outFlagEatPostBreak = eatPostBreak
1298            return
1299       
1300        if self.outFlagPostBreakEaten:
1301            self.outFlagPostBreakEaten = (toAppend.strip() == u"<br />")
1302
1303        self.outFlagEatPostBreak = eatPostBreak
1304        self.result.append(toAppend)
1305
1306
1307    def outEatBreaks(self, toAppend, **kpars):
1308        """
1309        Sets flags so that a <br /> before and/or after the item toAppend
1310        are eaten (removed) and appends toAppend to self.result
1311        """
1312        kpars["eatPreBreak"] = True
1313        kpars["eatPostBreak"] = True
1314
1315        self.outAppend(toAppend, **kpars)
1316
1317
1318
1319    START_INDENT_MAP = {"normalindent": u"<ul>", "ul": u"<ul>", "ol": u"<ol>", "ol-roman" : u"<ol class='withroman'>", "ol-alpha" : u"<ol class='withalpha'>"}
1320
1321    END_INDENT_MAP = {"normalindent": u"</ul>\n", "ul": u"</ul>\n",
1322            "ol": u"</ol>\n", "ol-roman" : u"</ol>\n", "ol-alpha" : u"</ol>\n"}
1323
1324    def outStartIndentation(self, indType):
1325        """
1326        Insert indentation, bullet, or numbered list start tag.
1327        ind -- indentation depth
1328        """
1329        if indType == "normalindent" and self.asIntHtmlPreview:
1330            self.outEatBreaks(u"<blockquote>")
1331        else:
1332            tag = self.START_INDENT_MAP[indType]
1333
1334# TODO: (hasStates() was removed) if self.hasStates() or self.asIntHtmlPreview:
1335            if self.asIntHtmlPreview:
1336                # It is already indented, so additional indents will not
1337                # produce blank lines which must be eaten
1338                self.outAppend(tag)
1339            else:
1340                self.outEatBreaks(tag)
1341
1342    def outEndIndentation(self, indType):
1343        """
1344        Insert indentation, bullet, or numbered list start tag.
1345        ind -- indentation depth
1346        """
1347        if indType == "normalindent" and self.asIntHtmlPreview:
1348            self.outEatBreaks(u"</blockquote>\n")
1349        else:
1350            tag = self.END_INDENT_MAP[indType]
1351
1352# TODO: (hasStates() was removed) if self.hasStates() or self.asIntHtmlPreview:
1353            if self.asIntHtmlPreview:
1354                # It is already indented, so additional indents will not
1355                # produce blank lines which must be eaten  (?)
1356                self.outAppend(tag, eatPreBreak=True)
1357            else:
1358                self.outEatBreaks(tag)
1359
1360
1361    def getOutput(self):
1362        return u"".join(self.result)
1363
1364
1365
1366    def _processTable(self, content, astNode):
1367        """
1368        Write out content of a table as HTML code.
1369       
1370        astNode -- node of type "table"
1371        """
1372        self.astNodeStack.append(astNode)
1373
1374        # Retrieve table appendix values
1375        cssClass = u""
1376        tableModeAppendix = astNode.findFlatByName("tableModeAppendix")
1377        if tableModeAppendix:
1378            # Written this way to keep compatible if user's own parser wasn't
1379            # updated properly
1380            style = getattr(tableModeAppendix, "cssClass", u"")
1381            if style:
1382                cssClass = u' class="{0}"'.format(style)
1383
1384        self.outAppend(u'<table border="2"{0}>\n'.format(cssClass))
1385
1386        for row in astNode.iterFlatByName("tableRow"):
1387            self.outAppend(u"<tr>")
1388            for cell in row.iterFlatByName("tableCell"):
1389                self.outAppend(u"<td>")
1390                self.processAst(content, cell)
1391                self.outAppend(u"</td>")
1392            self.outAppend(u"</tr>\n")
1393
1394        if self.asIntHtmlPreview:
1395            self.outAppend(u'</table>\n<br />\n') # , eatPostBreak=not self.asIntHtmlPreview)
1396        else:
1397            self.outAppend(u'</table>\n')
1398
1399        self.astNodeStack.pop()
1400
1401
1402    def _processInsertion(self, fullContent, astNode):
1403        self.astNodeStack.append(astNode)
1404        astNode.astNodeStack = self.astNodeStack
1405
1406        try:
1407            return self._actualProcessInsertion(fullContent, astNode)
1408        finally:
1409            self.astNodeStack.pop()
1410
1411
1412    # TODO Context support so an insertion reacts differently in e.g. tables
1413    def _actualProcessInsertion(self, fullContent, astNode):
1414        """
1415        Process an insertion (e.g. "[:page:WikiWord]")
1416       
1417        astNode -- node of type "insertion"
1418        """
1419        wordList = None
1420        content = None
1421        htmlContent = None
1422        key = astNode.key
1423        value = astNode.value
1424        appendices = astNode.appendices
1425
1426        if key == u"page":
1427            if (u"wikipage/" + value) in self.insertionVisitStack:
1428                # Prevent infinite recursion
1429                return
1430
1431            docpage = self.wikiDocument.getWikiPageNoError(value)
1432            pageAst = docpage.getLivePageAst()
1433           
1434            # Value to add to heading level to fix level inside inserted pages
1435            offsetHead = 0
1436
1437            # Code to adjust heading level
1438            if (u"adjheading" in appendices) or (u"adjheading+" in appendices):
1439                minHead = 1000 # Just assuming that there won't be 1000 levels deep heading
1440                # Adjust heading level of insertion
1441                # First find the lowest heading level used in the inserted page
1442                for hn in pageAst.iterDeepByName("heading"):
1443                    minHead = min(minHead, hn.level)
1444               
1445                # if minHead is 1000 yet, there is no heading to adjust. Otherwise:
1446                if minHead < 1000:
1447                    fatherAst = astNode.astNodeStack[-2]
1448                   
1449                    # lastSurroundHead becomes the heading level in which the insertion
1450                    # is embedded (taken from last heading before insertion tag)
1451                    lastSurroundHead = -1
1452                    for hn in fatherAst.iterDeepByName("heading"):
1453                        if hn.pos > astNode.pos:
1454                            break
1455                        lastSurroundHead = hn.level
1456
1457                    # Finally the offset is calculated
1458                    if lastSurroundHead > -1:
1459                        offsetHead = lastSurroundHead - (minHead - 1)
1460
1461
1462            self.insertionVisitStack.append(u"wikipage/" + value)
1463            try:
1464               
1465                # Inside an inserted page we don't want anchors to the
1466                # headings to avoid collisions with headings of surrounding
1467                # page.
1468
1469                with self.optsStack:
1470                    self.optsStack["innermostFullPageAst"] = pageAst
1471                    self.optsStack["innermostPageUnifName"] = \
1472                            docpage.getUnifiedPageName()
1473
1474                    self.optsStack["anchorForHeading"] = False
1475                    if offsetHead != 0:
1476                        self.optsStack["offsetHeadingLevel"] = \
1477                                self.optsStack.get("offsetHeadingLevel", 0) + \
1478                                offsetHead
1479
1480                    self.processAst(docpage.getLiveText(), pageAst)
1481
1482            finally:
1483                del self.insertionVisitStack[-1]
1484
1485            return
1486           
1487        elif key == u"rel":
1488            # List relatives (children, parents)
1489            if value == u"parents":
1490                wordList = self.wikiDocument.getWikiData().getParentRelationships(
1491                        self.wikiWord)
1492            elif value == u"children":
1493                existingonly = (u"existingonly" in appendices) # or \
1494                        # (u"existingonly +" in insertionAstNode.appendices)
1495                wordList = self.wikiDocument.getWikiData().getChildRelationships(
1496                        self.wikiWord, existingonly=existingonly,
1497                        selfreference=False)
1498            elif value == u"parentless":
1499                wordList = self.wikiDocument.getWikiData().getParentlessWikiWords()
1500            elif value == u"undefined":
1501                wordList = self.wikiDocument.getWikiData().getUndefinedWords()
1502            elif value == u"top":
1503                htmlContent = u'<a href="#">Top</a>'
1504            elif value == u"back":
1505                if self.asHtmlPreview:
1506                    htmlContent = \
1507                            u'<a href="' + self._getInternaljumpPrefix() + \
1508                            u'action/history/back">Back</a>'
1509                else:
1510                    htmlContent = \
1511                            u'<a href="javascript:history.go(-1)">Back</a>'
1512
1513        elif key == u"self":
1514            htmlContent = escapeHtml(self.getCurrentWikiWord())
1515
1516        elif key == u"savedsearch":
1517            datablock = self.wikiDocument.getWikiData().retrieveDataBlock(
1518                    u"savedsearch/" + value)
1519            if datablock is not None:
1520                searchOp = SearchReplaceOperation()
1521                searchOp.setPackedSettings(datablock)
1522                searchOp.replaceOp = False
1523                wordList = self.wikiDocument.searchWiki(searchOp)
1524        elif key == u"search":
1525            searchOp = SearchReplaceOperation()
1526            searchOp.replaceOp = False
1527            searchOp.searchStr = value
1528           
1529            searchType = Consts.SEARCHTYPE_BOOLEANREGEX
1530            removeSelf = False
1531            # Based on
1532            # SearchAndReplaceDialogs.SearchWikiDialog._buildSearchReplaceOperation
1533            for ap in appendices:
1534                if ap == "type boolean" or ap == "type bool":
1535                    searchType = Consts.SEARCHTYPE_BOOLEANREGEX
1536                elif ap == "type regex":
1537                    searchType = Consts.SEARCHTYPE_REGEX
1538                elif ap == "type asis" or ap == "type plain":
1539                    searchType = Consts.SEARCHTYPE_ASIS
1540                elif ap == "type index" and \
1541                        self.wikiDocument.isSearchIndexEnabled():
1542                    searchType = Consts.SEARCHTYPE_INDEX
1543                elif ap == "casesensitive":
1544                    searchOp.caseSensitive = True
1545                elif ap == "wholewordsonly":
1546                    searchOp.wholeWord = True
1547                elif ap == "removeself" or ap == "removethis":
1548                    removeSelf = True
1549
1550            searchOp.booleanOp = searchType == Consts.SEARCHTYPE_BOOLEANREGEX
1551            searchOp.indexSearch = 'no' if searchType != Consts.SEARCHTYPE_INDEX \
1552                    else 'default'
1553            searchOp.wildCard = 'regex' if searchType != Consts.SEARCHTYPE_ASIS \
1554                    else 'no'
1555
1556            wordList = self.wikiDocument.searchWiki(searchOp)
1557
1558            # Because a simple search for "foo" includes the page containing
1559            # the insertion searching for "foo" there is option "removeself"
1560            # to remove the page containing the insertion from the list
1561            if removeSelf and self.optsStack["innermostPageUnifName"]\
1562                    .startswith(u"wikipage/"):
1563               
1564                selfPageName = self.wikiDocument.getWikiPageNameForLinkTermOrAsIs(
1565                        self.optsStack["innermostPageUnifName"][9:])
1566                try:
1567                    wordList.remove(selfPageName)
1568                except ValueError:
1569                    pass
1570                   
1571
1572
1573        elif key == u"toc" and value == u"":
1574            pageAst = self.getBasePageAst()
1575#             pageAst = self.optsStack["innermostFullPageAst"]
1576
1577            self.outAppend(u'<div class="page-toc">\n')
1578
1579            for node in pageAst.iterFlatByName("heading"):
1580                headLevel = node.level           
1581                if self.asIntHtmlPreview:
1582                    # Simple indent for internal preview
1583                    self.outAppend(u"&nbsp;&nbsp;" * (headLevel - 1))
1584                else:
1585                    # use css otherwise
1586                    self.outAppend(u'<div class="page-toc-level%i">' %
1587                            headLevel)
1588
1589                if self.wordAnchor:
1590                    anchor = self.wordAnchor + (u"#.h%i" % node.pos)
1591                else:
1592                    anchor = u".h%i" % node.pos
1593
1594                self.outAppend(u'<a href="#%s">' % anchor)
1595
1596                with self.optsStack:
1597                    self.optsStack["suppressLinks"] = True
1598                    self.processAst(fullContent, node.contentNode)
1599
1600                self.outAppend(u'</a>')
1601
1602                if self.asIntHtmlPreview:
1603                    self.outAppend(u'<br />\n')
1604                else:
1605                    self.outAppend(u'</div>\n')
1606
1607            self.outAppend(u"</div>\n")
1608#             htmlContent = u"".join(htmlContent)
1609
1610        elif key == u"eval":
1611            if not self.mainControl.getConfig().getboolean("main",
1612                    "insertions_allow_eval", False):
1613                # Evaluation of such insertions not allowed
1614                htmlContent = _(u"<pre>[Allow evaluation of insertions in "
1615                        "\"Options\", page \"Security\", option "
1616                        "\"Process insertion scripts\"]</pre>")
1617            else:
1618                evalScope = {"pwiki": self.getMainControl(),
1619                        "lib": self.getMainControl().evalLib}
1620                expr = astNode.value
1621                # TODO Test security
1622                try:
1623                    content = unicode(eval(re.sub(u"[\n\r]", u"", expr),
1624                            evalScope))
1625                except Exception, e:
1626                    s = StringIO()
1627                    traceback.print_exc(file=s)
1628                    htmlContent = u"\n<pre>\n" + \
1629                            escapeHtmlNoBreaks(s.getvalue()) + u"\n</pre>\n"
1630        elif key == u"iconimage":
1631            imgName = astNode.value
1632            icPath = wx.GetApp().getIconCache().lookupIconPath(imgName)
1633            if icPath is None:
1634                htmlContent = _(u"<pre>[Icon '%s' not found]</pre>" % imgName)
1635            else:
1636                url = self.copiedTempFileCache.get(icPath)
1637                if url is None:
1638                    tfs = self.getTempFileSet()
1639                    # TODO Take suffix from icPath
1640                    dstFullPath = tfs.createTempFile("", ".gif", relativeTo="")
1641                    pythonUrl = (self.exportType != "html_previewWX")
1642                    url = tfs.getRelativeUrl(None, dstFullPath, pythonUrl=pythonUrl)
1643
1644                    OsAbstract.copyFile(icPath, dstFullPath)
1645                    self.copiedTempFileCache[icPath] = url
1646               
1647                htmlContent = u'<img src="%s" />' % url
1648        else:
1649            # Call external plugins
1650            exportType = self.exportType
1651            handler = wx.GetApp().getInsertionPluginManager().getHandler(self,
1652                    exportType, key)
1653
1654            if handler is None and self.asHtmlPreview:
1655                # No handler found -> try to find generic HTML preview handler
1656                exportType = "html_preview"
1657                handler = wx.GetApp().getInsertionPluginManager().getHandler(self,
1658                        exportType, key)
1659
1660            if handler is not None:
1661                try:
1662                    htmlContent = handler.createContent(self, exportType,
1663                            astNode)
1664                except Exception, e:
1665                    s = StringIO()
1666                    traceback.print_exc(file=s)
1667                    htmlContent = u"<pre>" + mbcsDec(s.getvalue(), 'replace')[0] + u"</pre>"
1668
1669                if htmlContent is None:
1670                    htmlContent = u""
1671            else:
1672                # Try to find a generic handler for export type
1673                # "wikidpad_language"
1674                handler = wx.GetApp().getInsertionPluginManager().getHandler(self,
1675                        "wikidpad_language", key)
1676                if handler is not None:
1677                    try:
1678                        # This content is in WikidPad markup language
1679                        # and must be postprocessed
1680                        content = handler.createContent(self,
1681                                "wikidpad_language", astNode)
1682                    except Exception, e:
1683                        s = StringIO()
1684                        traceback.print_exc(file=s)
1685                        htmlContent = u"<pre>" + mbcsDec(s.getvalue(), 'replace')[0] + u"</pre>"
1686
1687        if wordList is not None:
1688            # Create content as a nicely formatted list of wiki words
1689
1690            if len(wordList) == 0:
1691                content = u""
1692            else:
1693                # wordList was set, so build a nicely formatted list of wiki words
1694
1695                # Check for desired number of columns (as appendix e.g.
1696                # "columns 3" was set) and other settings
1697                cols = 1
1698                coldirDown = False
1699                asList = False
1700                colwidth = None
1701                tableBorderWidth = 0
1702
1703                for ap in appendices:
1704                    if ap.startswith(u"columns "):
1705                        try:
1706                            v = int(ap[8:])
1707                            if v > 0:
1708                                cols = v
1709                        except ValueError:
1710                            pass
1711                    elif ap == "aslist":
1712                        # No table but comma-separated list
1713                        asList = True
1714                    elif ap == u"coldir down":
1715                        # Order in table goes downward first then right
1716                        # Default is right first then downward
1717                        coldirDown = True
1718                    elif ap == u"colwidth equal":
1719                        # All columns will have same width
1720                        colwidth = u"equal"
1721                    elif ap.startswith(u"table_border_width "):
1722                        try:
1723                            v = int(ap[18:])
1724                            if v >= 0:
1725                                tableBorderWidth = v
1726                        except ValueError:
1727                            pass
1728
1729                # To ensure that the sum of percentages is 100 there is a bit
1730                # work to do. On the other hand this means that the columns
1731                # don't have exactly equal width
1732                if colwidth == u"equal":
1733                    colwidth = []
1734                    usedPerc = 0
1735                    for i in range(1, cols + 1):
1736                        cw = i * 100 // cols - usedPerc
1737                        usedPerc += cw
1738                        colwidth.append(u"%i%%" % cw)
1739
1740                self.mainControl.getCollator().sort(wordList)
1741   
1742                if asList:
1743                    firstWord = True
1744                    for word in wordList:
1745                        if firstWord:
1746                            firstWord = False
1747                        else:
1748                            self.outAppend(", ")
1749                        self._processWikiWord(word)
1750                else:
1751#                 if cols > 1:
1752                    # Convert colwidth to HTML attribute
1753                    if colwidth is None:
1754                        colwidthAttr = [u""] * cols
1755                    else:
1756                        colwidthAttr = [u' width="%s"' % cw for cw in colwidth]
1757                   
1758                    tableAttrs = u""
1759                    if tableBorderWidth > 0:
1760                        tableAttrs += u' border="%s"' % tableBorderWidth
1761                   
1762                    # We need a table for the wordlist
1763                    self.outAppend(u"<table%s>\n" % tableAttrs)
1764                    colpos = 0
1765                    firstRow = True
1766
1767                    if coldirDown:
1768                        # Reorder words for downwards direction
1769
1770                        result = []
1771                        wordCount = len(wordList)
1772                        rowCount = (wordCount + cols - 1) // cols
1773                        for r in range(rowCount):
1774                            result += [wordList[i]
1775                                    for i in range(r, wordCount, rowCount)]
1776                        wordList = result
1777                   
1778                    for word in wordList:
1779                        if colpos == 0:
1780                            # Start table row
1781                            self.outAppend(u"<tr>")
1782                           
1783                        if firstRow:
1784                            self.outAppend(u'<td valign="top"%s>' %
1785                                    colwidthAttr[colpos])
1786                        else:
1787                            self.outAppend(u'<td valign="top">')
1788                        self._processWikiWord(word)
1789                        self.outAppend(u'</td>')
1790                       
1791                        colpos += 1
1792                        if colpos == cols:
1793                            # At the end of a row
1794                            colpos = 0
1795                            firstRow = False
1796                            self.outAppend(u"</tr>\n")
1797                           
1798                    # Fill the last incomplete row with empty cells if necessary
1799                    if colpos > 0:
1800                        if firstRow:
1801                            while colpos < cols:
1802                                self.outAppend(u"<td%s></td>" %
1803                                        colwidthAttr[colpos])
1804                                colpos += 1
1805                        else:
1806                            while colpos < cols:
1807                                self.outAppend(u"<td></td>")
1808                                colpos += 1
1809   
1810                        self.outAppend(u"</tr>\n")
1811                   
1812                    self.outAppend(u"</table>")
1813
1814
1815#                 else:   # cols == 1 and not asList
1816#                     firstWord = True
1817#                     for word in wordList:
1818#                         if firstWord:
1819#                             firstWord = False
1820#                         else:
1821#                             self.outAppend("<br />\n")
1822#                         
1823#                         # TODO:  td  without  table ?
1824#                         self.outAppend(u'<td valign="top">')
1825#                         self._processWikiWord(word)
1826#                         self.outAppend(u'</td>')
1827                   
1828                return
1829
1830
1831        if content is not None:
1832            # Content was set, so use standard formatting rules to create
1833            # tokens out of it and process them
1834            docPage = self.wikiDocument.getWikiPageNoError(self.wikiWord)
1835            self.processAst(content, docPage.parseTextInContext(content))
1836
1837        elif htmlContent is not None:
1838            self.outAppend(htmlContent)
1839
1840
1841    def _processWikiWord(self, astNodeOrWord, fullContent=None):
1842        self.astNodeStack.append(astNodeOrWord)
1843
1844        if isinstance(astNodeOrWord, SyntaxNode):
1845            wikiWord = astNodeOrWord.wikiWord
1846            anchorLink = astNodeOrWord.anchorLink
1847            titleNode = astNodeOrWord.titleNode
1848        else:
1849            wikiWord = astNodeOrWord
1850            anchorLink = None
1851            titleNode = None
1852           
1853
1854        self.linkConverter.wikiDocument = self.wikiDocument
1855        link = self.linkConverter.getLinkForWikiWord(wikiWord)
1856       
1857        selfLink = False
1858
1859        if link:
1860            linkTo = self.wikiDocument.getWikiPageNameForLinkTerm(wikiWord)
1861
1862            # Test if link to same page itself (maybe with an anchor fragment)
1863            if not self.exportType in (u"html_multi", u"xml"):
1864                linkFrom = self.wikiDocument.getWikiPageNameForLinkTerm(
1865                        self.wikiWord)
1866                if linkTo is not None and linkTo == linkFrom:
1867                    # Page links to itself
1868                    selfLink = True
1869
1870            # Add anchor fragment if present
1871            if anchorLink:
1872                if selfLink:
1873                    link = u"#" + anchorLink
1874                else:
1875                    link += u"#" + anchorLink
1876
1877            title = None
1878            if linkTo is not None:
1879                propList = self.wikiDocument.getAttributeTriples(linkTo,
1880                        u"short_hint", None)
1881                if len(propList) > 0:
1882                    title = propList[-1][2]
1883
1884            if self.optsStack.get("suppressLinks", False):
1885                self.outAppend(u'<span class="wiki-link">')
1886            else:
1887                if title is not None:
1888                    self.outAppend(u'<span class="wiki-link"><a href="%s" title="%s">' %
1889                            (link, escapeHtmlNoBreaks(title)))
1890                else:
1891                    self.outAppend(u'<span class="wiki-link"><a href="%s">' %
1892                            link)
1893
1894            if titleNode is not None:
1895                with self.optsStack:
1896                    self.optsStack["suppressLinks"] = True
1897                    self.processAst(fullContent, titleNode)
1898            else:
1899                self.outAppend(escapeHtml(wikiWord))                       
1900
1901            if self.optsStack.get("suppressLinks", False):
1902                self.outAppend(u'</span>')
1903            else:
1904                self.outAppend(u'</a></span>')
1905        else:
1906            if titleNode is not None:
1907                self.processAst(fullContent, titleNode)
1908            else:
1909                if isinstance(astNodeOrWord, SyntaxNode):
1910                    self.outAppend(escapeHtml(astNodeOrWord.getString()))
1911                else:
1912                    self.outAppend(escapeHtml(astNodeOrWord))
1913
1914        self.astNodeStack.pop()
1915
1916
1917    def _processUrlLink(self, fullContent, astNode):
1918        link = astNode.url
1919        pointRelative = False  # Should final link be relative?
1920
1921        if link.startswith(u"rel://"):
1922            pointRelative = True
1923            absUrl = self.wikiDocument.makeRelUrlAbsolute(link)
1924
1925            # Relative URL
1926            if self.asHtmlPreview:
1927                # If preview, make absolute
1928                link = absUrl
1929            else:
1930                if self.referencedStorageFiles is not None:
1931                    # Get absolute path to the file
1932                    absPath = StringOps.pathnameFromUrl(absUrl)
1933                    # and to the file storage
1934                    stPath = self.wikiDocument.getFileStorage().getStoragePath()
1935                   
1936                    isCont = testContainedInDir(stPath, absPath)
1937
1938                    if isCont:
1939                        # File is in file storage -> add to
1940                        # referenced storage files                           
1941                        self.referencedStorageFiles.add(
1942                                StringOps.relativeFilePath(
1943                                self.wikiDocument.getWikiPath(),
1944                                absPath))
1945                       
1946                        relPath = StringOps.pathnameFromUrl(link[6:], False)
1947               
1948                        absUrl = u"file:" + StringOps.urlFromPathname(
1949                                os.path.abspath(os.path.join(self.exportDest,
1950                                relPath)))
1951                        link = absUrl
1952
1953        else:
1954            absUrl = link
1955
1956        lowerLink = link.lower()
1957       
1958        if astNode.appendixNode is None:
1959            appendixDict = {}
1960        else:
1961            appendixDict = dict(astNode.appendixNode.entries)
1962
1963        # Decide if this is an image link
1964        if appendixDict.has_key("l"):
1965            urlAsImage = False
1966        elif appendixDict.has_key("i"):
1967            urlAsImage = True
1968        elif self.asHtmlPreview and \
1969                self.mainControl.getConfig().getboolean(
1970                "main", "html_preview_pics_as_links"):
1971            urlAsImage = False
1972        elif not self.asHtmlPreview and self.addOpt[0]:
1973            urlAsImage = False
1974        elif lowerLink.split(".")[-1] in ("jpg", "jpeg", "gif", "png", "tif",
1975                "bmp"):
1976#         lowerLink.endswith(".jpg") or \
1977#                 lowerLink.endswith(".gif") or \
1978#                 lowerLink.endswith(".png") or \
1979#                 lowerLink.endswith(".tif") or \
1980#                 lowerLink.endswith(".bmp"):
1981            urlAsImage = True
1982        else:
1983            urlAsImage = False
1984
1985        pointingType = appendixDict.get("p")
1986
1987        relRelocate = True # Relocate(=rewrite) relative link?
1988
1989        # Decide if link should be relative or absolute
1990        if self.asHtmlPreview:
1991            # For preview always absolute link
1992            pointRelative = False
1993        elif pointingType == u"abs":
1994            pointRelative = False
1995        elif pointingType == u"rel":
1996            pointRelative = True
1997        elif pointingType == u"rnr":
1998            pointRelative = True
1999            relRelocate = False
2000
2001        if pointRelative:
2002            if not relRelocate and lowerLink.startswith("rel://"):
2003                # Do not relocate relative link (might link to a resource
2004                # outside of WikidPad, relative to export destination)
2005                link = link[6:]
2006            elif lowerLink.startswith("file:") or \
2007                    lowerLink.startswith("rel://"):
2008                # Even if link is already relative it is relative to
2009                # wrong location in most cases
2010                absPath = StringOps.pathnameFromUrl(absUrl)
2011                relPath = StringOps.relativeFilePath(self.exportDest,
2012                        absPath)
2013                if relPath is None:
2014                    link = absUrl
2015                else:
2016                    link = StringOps.urlFromPathname(relPath)
2017        else:
2018            if lowerLink.startswith("rel://"):
2019                link = absUrl
2020
2021
2022        if urlAsImage:
2023            # Ignore title, use image
2024            sizeInTag = u""
2025
2026            # Size info for direct setting in HTML code
2027            sizeInfo = appendixDict.get("s")
2028            # Relative size info which modifies real image size
2029            relSizeInfo = appendixDict.get("r")
2030
2031            if sizeInfo is not None:
2032                try:
2033                    widthStr, heightStr = sizeInfo.split(u"x")
2034                    if self.isHtmlSizeValue(widthStr) and \
2035                            self.isHtmlSizeValue(heightStr):
2036                        sizeInTag = ' width="%s" height="%s"' % \
2037                                (widthStr, heightStr)
2038                except:
2039                    # something does not meet syntax requirements
2040                    pass
2041           
2042            elif relSizeInfo is not None:
2043                params = relSizeInfo.split(u"x")
2044                if len(params) == 1:
2045                    if params[0] == u"":
2046                        widthStr, heightStr = "100%", "100%"
2047                    else:
2048                        widthStr, heightStr = params[0], params[0]
2049                else:
2050                    widthStr, heightStr = params[0], params[1]
2051
2052                width = SizeValue(widthStr)
2053                height = SizeValue(heightStr)
2054
2055                if width.isValid() and height.isValid() and \
2056                        (width.getUnit() == height.getUnit()):
2057                    imgWidth, imgHeight = self._getImageDims(absUrl)
2058                    if imgWidth is not None:
2059                        # TODO !!!
2060                        if width.getUnit() == width.UNIT_FACTOR:
2061                            imgWidth = int(imgWidth * width.getValue())
2062                            imgHeight = int(imgHeight * height.getValue())
2063
2064                        sizeInTag = ' width="%s" height="%s"' % \
2065                                (imgWidth, imgHeight)
2066
2067            alignInTag = u""
2068            alignInfo = appendixDict.get("a")
2069            if alignInfo is not None:
2070                try:
2071                    if alignInfo == u"t":
2072                        alignInTag = u' align="top"'
2073                    elif alignInfo == u"m":
2074                        alignInTag = u' align="middle"'
2075                    elif alignInfo == u"b":
2076                        alignInTag = u' align="bottom"'
2077                    elif alignInfo == u"l":
2078                        alignInTag = u' align="left"'
2079                    elif alignInfo == u"r":
2080                        alignInTag = u' align="right"'
2081                except:
2082                    # something does not match syntax requirements
2083                    pass
2084
2085            if self.asIntHtmlPreview and lowerLink.startswith("file:"):
2086                # At least under Windows, wxWidgets has another
2087                # opinion how a local file URL should look like
2088                # than Python
2089                p = pathnameFromUrl(link)
2090                link = wx.FileSystem.FileNameToURL(p)
2091            self.outAppend(u'<img src="%s" alt="" border="0"%s%s />' %
2092                    (link, sizeInTag, alignInTag))
2093        else:
2094            if not self.optsStack.get("suppressLinks", False):
2095                # If we would be in a title, only image urls are allowed
2096                self.outAppend(u'<span class="url-link"><a href="%s">' % link)
2097                if astNode.titleNode is not None:
2098                    with self.optsStack:
2099                        self.optsStack["suppressLinks"] = True
2100                        self.processAst(fullContent, astNode.titleNode)
2101                else:
2102                    self.outAppend(escapeHtml(astNode.coreNode.getString()))                       
2103                self.outAppend(u'</a></span>')
2104
2105
2106
2107    def processAst(self, content, pageAst):
2108        """
2109        Actual token to HTML converter. May be called recursively
2110        """
2111        self.astNodeStack.append(pageAst)
2112
2113        for node in pageAst.iterFlatNamed():
2114            tname = node.name
2115           
2116            if tname is None:
2117                continue           
2118            elif tname == "plainText":
2119                self.outAppend(escapeHtml(node.getString()))
2120            elif tname == "lineBreak":
2121                self.outAppend(u"<br />\n")
2122            elif tname == "newParagraph":
2123                self.outAppend(u"\n<p />")
2124            elif tname == "whitespace":
2125                self.outAppend(u" ")
2126
2127            elif tname == "indentedText":
2128                self.outStartIndentation("normalindent")
2129                self.processAst(content, node)
2130                self.outEndIndentation("normalindent")
2131            elif tname == "orderedList":
2132                self.outStartIndentation("ol")
2133                self.processAst(content, node)
2134                self.outEndIndentation("ol")
2135            elif tname == "unorderedList":
2136                self.outStartIndentation("ul")
2137                self.processAst(content, node)
2138                self.outEndIndentation("ul")
2139            elif tname == "romanList":
2140                self.outStartIndentation("ol-roman")
2141                self.processAst(content, node)
2142                self.outEndIndentation("ol-roman")
2143            elif tname == "alphaList":
2144                self.outStartIndentation("ol-alpha")
2145                self.processAst(content, node)
2146                self.outEndIndentation("ol-alpha")
2147
2148            elif tname == "bullet":
2149                self.outAppend(u"\n<li />", eatPreBreak=True)
2150            elif tname == "number":
2151                self.outAppend(u"\n<li />", eatPreBreak=True)
2152            elif tname == "roman":
2153                self.outAppend(u"\n<li />", eatPreBreak=True)
2154            elif tname == "alpha":
2155                self.outAppend(u"\n<li />", eatPreBreak=True)
2156
2157            elif tname == "italics":
2158                self.outAppend(u"<i>")
2159                self.processAst(content, node)
2160                self.outAppend(u"</i>")
2161            elif tname == "bold":
2162                self.outAppend(u"<b>")
2163                self.processAst(content, node)
2164                self.outAppend(u"</b>")
2165
2166            elif tname == "htmlTag" or tname == "htmlEntity":
2167                self.outAppend(node.getString())
2168
2169            elif tname == "heading":
2170                if self.optsStack.get("anchorForHeading", True):
2171                    if self.wordAnchor:
2172                        anchor = self.wordAnchor + (u"#.h%i" % node.pos)
2173                    else:
2174                        anchor = u".h%i" % node.pos
2175
2176                    self.outAppend(u'<a name="%s"></a>' % anchor)
2177
2178                headLevel = node.level + self.optsStack.get(
2179                        "offsetHeadingLevel", 0)
2180
2181                boundHeadLevel = min(6, headLevel)
2182                self.outAppend(u"<h%i class=\"heading-level%i\">" %
2183                        (boundHeadLevel, headLevel), eatPreBreak=True)
2184                self.processAst(content, node.contentNode)
2185                self.outAppend(u"</h%i>\n" % boundHeadLevel, eatPostBreak=True)
2186
2187            elif tname == "horizontalLine":
2188                self.outEatBreaks(u'<hr size="1" />\n')
2189
2190            elif tname == "preBlock":
2191                self.outAppend(u"<pre>%s</pre>\n" %
2192                        escapeHtmlNoBreaks(
2193                        node.findFlatByName("preText").getString()), True,
2194                        not self.asIntHtmlPreview)
2195                if self.asIntHtmlPreview:
2196                    self.outAppend(u"<br />\n")
2197            elif tname == "todoEntry":
2198                self.outAppend(u'<span class="todo">%s%s' %
2199                        (node.key, node.delimiter))
2200                self.processAst(content, node.valueNode)
2201                self.outAppend(u'</span>')
2202            # TODO remove "property"-compatibility
2203            elif tname in ("property", "attribute"):  # for compatibility with old language plugins
2204                for propKey, propValue in node.attrs:
2205                    standardAttribute = u"%s: %s" % (propKey, propValue)
2206                    standardAttributeMatching = \
2207                            bool(self.proppattern.match(standardAttribute))
2208                    # Output only for different truth values
2209                    # (Either it matches and matching attrs should not be
2210                    # hidden or vice versa)
2211                    if standardAttributeMatching != self.proppatternExcluding:
2212                        # TODO remove "property"-compatibility
2213                        self.outAppend( u'<span class="property attribute">[%s: %s]</span>' %
2214                                (escapeHtml(propKey),
2215                                escapeHtml(propValue)) )
2216            elif tname == "insertion":
2217                self._processInsertion(content, node)
2218            elif tname == "script":
2219                pass  # Hide scripts
2220            elif tname == "noExport":
2221                pass  # Hide no export areas
2222            elif tname == "anchorDef":
2223                if self.wordAnchor:
2224                    self.outAppend('<a name="%s"></a>' %
2225                            (self.wordAnchor + u"#" + node.anchorLink))
2226                else:
2227                    self.outAppend('<a name="%s"></a>' % node.anchorLink)               
2228            elif tname == "wikiWord":
2229                self._processWikiWord(node, content)
2230            elif tname == "table":
2231                self._processTable(content, node)
2232            elif tname == "footnote":
2233                footnoteId = node.footnoteId
2234                fnAnchorNode = getFootnoteAnchorDict(self.basePageAst).get(
2235                        footnoteId)
2236
2237                if fnAnchorNode is None:
2238                    self.outAppend(escapeHtml(node.getString()))
2239                else:
2240                    if self.wordAnchor:
2241                        fnAnchor = self.wordAnchor + u"#.f" + _escapeAnchor(
2242                                footnoteId)
2243                    else:
2244                        fnAnchor = u".f" + _escapeAnchor(footnoteId)
2245
2246                    if fnAnchorNode.pos == node.pos:
2247                        # Current footnote token tok is an anchor (=last
2248                        # footnote token with this footnoteId)
2249
2250                        self.outAppend(u'<a name="%s"></a>' % fnAnchor)
2251                        self.outAppend(escapeHtml(node.getString()))
2252                    else:
2253                        if not self.optsStack.get("suppressLinks", False):
2254                            # Current token is not an anchor -> make it a link.
2255                            self.outAppend(u'<a href="#%s">%s</a>' % (fnAnchor,
2256                            escapeHtml(node.getString())))
2257            elif tname == "urlLink":
2258                self._processUrlLink(content, node)
2259            elif tname == "stringEnd":
2260                pass
2261            else:
2262                self.outAppend(u'<tt>' + escapeHtmlNoBreaks(
2263                        _(u'[Unknown parser node with name "%s" found]') % tname) + \
2264                        u'</tt>')
2265
2266        self.astNodeStack.pop()
2267
2268
Note: See TracBrowser for help on using the browser.