root/branches/mbutscher/work/lib/pwiki/Exporters.py @ 247

Revision 247, 99.6 kB (checked in by mbutscher, 2 years ago)

branches/stable-2.0:
2.0final

branches/mbutscher/work:
2.1beta11
* Option to show iframe content from external

sources inside the HTML IE preview

* Remove plus signs in front of headings in page

structure view, use indentation instead

* Translating of menu accelerators enhanced

(scanning for all 16 bit unicode characters)

* Internal, index search: Store a format number for

search index and rebuild index if number doesn't
match the number for current WikidPad version

* Internal: Moved system detection functions from

"Configuration" to new module "SystemInfo?", made
more imports relative

* Experimental, Windows: Option to control if to

scroll control under pointer with mouse wheel on
Windows (instead of focused control)

* Reordered some options (on global "Advanced" page

and below)

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