root/branches/stable-2.1/lib/pwiki/Exporters.py @ 249

Revision 249, 100.1 kB (checked in by mbutscher, 2 years ago)

branches/stable-2.0:
* Use help wiki as default wiki if none is given in global config (may happen when an empty config file is used to make WikidPad portable)
* Chinese translation updated
* Bug fixed: Sometimes relative links not possible due to undesired case sensitivity

branches/stable-2.1:
* Support for . to refer to current wiki page
* Introduced "prel" and "pabs" in URL appendix and automatic adaption of relative links
* Windows binary installer now supports creation of a portable install (no uninstall, no registry or start menu changes)
* Use help wiki as default wiki if none is given in global config (may happen when an empty config file is used to make WikidPad portable)
* Chinese translation updated

* Bug fixed: Autocompletion for anchors treated wiki links as wiki words (no relative or absolute paths supported)
* Bug fixed: Indexing of updated pages was called directly in event handling instead of the update thread
* Bug fixed: Error in error handling for unknown wiki language tag on opening a wiki
* Bug fixed: Problems with IE 6 on Win XP SP2 reduced (problems with iframe remain)
* Bug fixed: Sometimes relative links not possible due to undesired case sensitivity

branches/mbutscher/work:
* Support for . to refer to current wiki page
* Introduced splash window. Imports were reordered to show it as soon as possible. Option to switch it off.
* Introduced "prel" and "pabs" in URL appendix and automatic adaption of relative links
* Use help wiki as default wiki if none is given in global config (may happen when an empty config file is used to make WikidPad portable)
* Windows binary installer now supports creation of a portable install (no uninstall, no registry or start menu changes)
* Removed annoying "blinking" on current heading in doc structure window (thanks to Christian Ziemski)
* Incremental search now also supported in inline diff view
* Chinese translation updated
* App.addWikiPluginOptionsDlgPanel() to place wiki-bound plugin options pages correctly

* Bug fixed: Autocompletion for anchors treated wiki links as wiki words (no relative or absolute paths supported)
* Bug fixed: Indexing of updated pages was called directly in event handling instead of the update thread
* Bug fixed: Error in error handling for unknown wiki language tag on opening a wiki
* Bug fixed: "SetCallFilterEvent?" not supported by older wxPython versions
* Bug fixed: Problems with IE 6 on Win XP SP2 reduced (problems with iframe remain)
* Bug fixed: Sometimes relative links not possible due to undesired case sensitivity

* Internal: Moved some menu item handlers from the large PersonalWikiFrame? out to new PWikiNonCore and created some infrastructure (may lead to customizable menus in the long run)

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