root/branches/mbutscher/work/extensions/wikidPadParser/WikidPadParser.py @ 245

Revision 245, 76.0 kB (checked in by mbutscher, 3 years ago)

* Bug fixed: Autocompletion for anchors treated wiki

links as wiki words (no relative or absolute paths
supported)

* Bug fixed: Indexing of updated pages was called

directly in event handling instead of in the
update thread

Line 
1## import hotshot
2## _prof = hotshot.Profile("hotshot.prf")
3
4# Official parser plugin for wiki language "WikidPad default 2.0"
5# Last modified (format YYYY-MM-DD): 2011-01-12
6
7
8import locale, pprint, time, sys, string, traceback
9
10from textwrap import fill
11
12import wx
13
14import re    # from pwiki.rtlibRepl import re
15from pwiki.WikiExceptions import *
16from pwiki.StringOps import UPPERCASE, LOWERCASE, revStr
17from pwiki.WikiDocument import WikiDocument
18from pwiki.OptionsDialog import PluginOptionsPanel
19
20sys.stderr = sys.stdout
21
22locale.setlocale(locale.LC_ALL, '')
23
24from pwiki.WikiPyparsing import *
25
26
27WIKIDPAD_PLUGIN = (("WikiParser", 1),)
28
29
30
31LETTERS = UPPERCASE + LOWERCASE
32
33
34# The specialized optimizer in WikiPyParsing can't handle automatic whitespace
35# removing
36ParserElement.setDefaultWhitespaceChars("")
37
38
39
40RE_FLAGS = re.DOTALL | re.UNICODE | re.MULTILINE
41
42
43class IndentInfo(object):
44    __slots__ = ("level", "type")
45    def __init__(self, type):
46        self.level = 0
47        self.type = type
48
49
50
51def buildRegex(regex, name=None, hideOnEmpty=False):
52    if name is None:
53        element = Regex(regex, RE_FLAGS)
54    else:
55        element = Regex(regex, RE_FLAGS).setResultsName(name).setName(name)
56   
57    if hideOnEmpty:
58        element.setParseAction(actionHideOnEmpty)
59       
60    return element
61
62stringEnd = buildRegex(ur"(?!.)", "stringEnd")
63
64
65
66def getFirstTerminalNode(t):
67    if t.getChildrenCount() == 0:
68        return None
69   
70    lt = t[-1]
71    if not isinstance(lt, TerminalNode):
72        return None
73       
74    return lt
75
76
77def actionHideOnEmpty(s, l, st, t):
78    if t.strLength == 0:
79        return []
80
81
82def actionCutRightWhitespace(s, l, st, t):
83    lt = getFirstTerminalNode(t)
84    if lt is None:
85        return None
86
87    txt = lt.getText()
88    for i in xrange(len(txt) - 1, -1, -1):
89        if txt[i] not in (u"\t", u" ", u"\n", u"\r"):
90            if i < len(txt) - 1:
91                lt.text = txt[:i+1]
92                lt.recalcStrLength()
93               
94                t2 = buildSyntaxNode(txt[i+1:], lt.pos + i + 1)
95                t.append(t2)
96            return t
97
98    return None
99
100 
101_CHECK_LEFT_RE = re.compile(ur"[ \t]*$", RE_FLAGS)
102
103
104def preActCheckNothingLeft(s, l, st, pe):
105    # Technically we have to look behind, but this is not supported very
106    # well by reg. ex., so we take the reverse string and look ahead
107    revText = st.revText
108    revLoc = len(s) - l
109#     print "--preActCheckNothingLeft4", repr((revLoc, revText[revLoc: revLoc+20]))
110    if not _CHECK_LEFT_RE.match(revText, revLoc):
111        raise ParseException(s, l, "left of block markup (e.g. table) not empty")
112
113
114def validateNonEmpty(s, l, st, t):
115    if t.strLength == 0:
116        raise ParseException(s, l, "matched token must not be empty")
117
118
119
120
121def precTest(s, l, st, pe):
122    print "--precTest", repr((l, st, type(pe)))
123
124
125
126def createCheckNotIn(tokNames):
127    tokNames = frozenset(tokNames)
128
129    def checkNoContain(s, l, st, pe):
130        for tn in tokNames:
131            if tn in st.nameStack[:-1]:
132#                 print "--notcontain exc", repr(st.nameStack[:-1]), tn
133                raise ParseException(s, l, "token '%s' is not allowed here" % tn)
134
135    return checkNoContain
136
137
138def pseudoActionFindMarkup(s, l, st, t):
139    if t.strLength == 0:
140        return []
141    t.name = "plainText"
142    return t
143
144
145
146
147
148# Forward definition of normal content and content in table cells, headings, ...
149content = Forward()
150oneLineContent = Forward()
151
152tableContentInCell = Forward().setResultsNameNoCopy("tableCell")
153headingContent = Forward().setResultsNameNoCopy("headingContent")
154todoContent = Forward().setResultsNameNoCopy("value")
155titleContent = Forward().setResultsNameNoCopy("title")
156
157
158whitespace = buildRegex(ur"[ \t]*")
159whitespace = whitespace.setParseAction(actionHideOnEmpty)
160
161
162# The mode appendix for URLs and tables
163def actionModeAppendix(s, l, st, t):
164    entries = []
165
166    for entry in t.iterFlatByName("entry"):
167        key = entry.findFlatByName("key").getText()
168        data = entry.findFlatByName("data").getText()
169        entries.append((key, data))
170       
171    t.entries = entries
172    return t
173
174
175
176modeAppendixEntry = buildRegex(ur"(?!;)\S", "key") + buildRegex(ur"(?:(?!;)\S)*", "data")
177modeAppendixEntry = modeAppendixEntry.setResultsNameNoCopy("entry")
178modeAppendix = modeAppendixEntry + ZeroOrMore(buildRegex(ur";") + modeAppendixEntry)
179modeAppendix = modeAppendix.addParseAction(actionModeAppendix)
180
181
182
183
184# -------------------- Simple formatting --------------------
185
186EscapePlainCharPAT = ur"\\"
187
188
189escapedChar = buildRegex(EscapePlainCharPAT) + buildRegex(ur".", "plainText")
190
191italicsStart = buildRegex(ur"\b_")
192italicsStart = italicsStart.setParseStartAction(createCheckNotIn(("italics",)))
193
194italicsEnd = buildRegex(ur"_\b")
195
196italics = italicsStart + content + italicsEnd
197italics = italics.setResultsNameNoCopy("italics").setName("italics")
198
199boldStart = buildRegex(ur"\*(?=\S)")
200boldStart = boldStart.setParseStartAction(createCheckNotIn(("bold",)))
201
202boldEnd = buildRegex(ur"\*")
203
204bold = boldStart + content + boldEnd
205bold = bold.setResultsNameNoCopy("bold").setName("bold")
206
207
208script = buildRegex(ur"<%") + buildRegex(ur".*?(?=%>)", "code") + \
209        buildRegex(ur"%>")
210script = script.setResultsNameNoCopy("script")
211
212horizontalLine = buildRegex(ur"----+[ \t]*$", "horizontalLine")\
213        .setParseStartAction(preActCheckNothingLeft)
214
215
216# -------------------- HTML --------------------
217
218htmlTag = buildRegex(ur"</?[A-Za-z][A-Za-z0-9:]*(?:/| [^\n>]*)?>", "htmlTag")
219
220htmlEntity = buildRegex(
221        ur"&(?:[A-Za-z0-9]{2,10}|#[0-9]{1,10}|#x[0-9a-fA-F]{1,8});",
222        "htmlEntity")
223
224
225
226# -------------------- Heading --------------------
227
228def actionHeading(s, l, st, t):
229    t.level = len(t[0].getText())
230    t.contentNode = t.findFlatByName("headingContent")
231    if t.contentNode is None:
232        raise ParseException(s, l, "a heading needs content")
233
234headingEnd = buildRegex(ur"\n")
235
236heading = buildRegex(ur"^\+{1,15}(?!\+)") + Optional(buildRegex(ur" ")) + \
237        headingContent + headingEnd
238heading = heading.setResultsNameNoCopy("heading").setParseAction(actionHeading)
239
240
241
242# -------------------- Todo-Entry --------------------
243
244def actionTodoEntry(s, l, st, t):
245    t.key = t.findFlatByName("key").getString()
246    t.keyComponents = t.key.split(u".")
247    t.delimiter = t.findFlatByName("todoDelimiter").getString()
248    t.valueNode = t.findFlatByName("value")
249    t.todos = [(t.key, t.valueNode)]
250
251
252
253todoKey = buildRegex(ur"\b(?:todo|done|wait|action|track|issue|"
254        ur"question|project)(?:\.[^:\s]+)?", "key")
255# todoKey = todoKey.setParseStartAction(preActCheckNothingLeft)
256
257todoEnd = buildRegex(ur"\n|\||(?!.)")
258
259todoEntry = todoKey + buildRegex(ur":", "todoDelimiter") + todoContent + \
260        Optional(buildRegex(ur"\|"))
261
262todoEntry = todoEntry.setResultsNameNoCopy("todoEntry")\
263        .setParseAction(actionTodoEntry)
264
265# Only for LanguageHelper.parseTodoEntry()
266todoAsWhole = todoEntry + stringEnd
267
268# -------------------- Indented text, (un)ordered list --------------------
269
270def validateLessIndent(s, l, st, t):
271    if t.strLength >= st.dictStack["indentInfo"].level:
272        raise ParseException(s, l, "expected less indentation")
273
274
275def validateMoreIndent(s, l, st, t):
276    if t.strLength <= st.dictStack["indentInfo"].level:
277        raise ParseException(s, l, "expected more indentation")
278
279
280def validateEqualIndent(s, l, st, t):
281    if t.strLength > st.dictStack["indentInfo"].level:
282        raise ParseException(s, l, "expected equal indentation, but more found")
283    if t.strLength < st.dictStack["indentInfo"].level:
284        raise ParseException(s, l, "expected equal indentation, but less found")
285
286
287def validateEquivalIndent(s, l, st, t):
288    if t.strLength > st.dictStack["indentInfo"].level and \
289            st.dictStack["indentInfo"].type == "normal":
290        raise ParseException(s, l, "expected equival. indentation, but more found")
291    if t.strLength < st.dictStack["indentInfo"].level:
292        raise ParseException(s, l, "expected equival. indentation, but less found")
293
294
295def validateInmostIndentNormal(s, l, st, t):
296    if st.dictStack["indentInfo"].type != "normal":
297        raise ParseException(s, l, 'Inmost indentation not "normal"')
298
299
300def actionResetIndent(s, l, st, t):
301    st.dictStack.getSubTopDict()["lastIdentation"] = 0
302
303
304def actionIndent(s, l, st, t):
305    st.dictStack.getSubTopDict()["lastIdentation"] = t.strLength
306
307
308def actionListStartIndent(s, l, st, t):
309    if t.strLength <= st.dictStack["indentInfo"].level:
310        raise ParseException(s, l, "expected list start indentation, but less or equal found")
311
312    return actionIndent(s, l, st, t)
313
314
315def actionMoreIndent(s, l, st, t):
316    """
317    Called for more indentation before a general indented text.
318    """
319    newIdInfo = IndentInfo("normal")
320   
321    result = actionIndent(s, l, st, t)
322
323    newIdInfo.level = st.dictStack.getSubTopDict().get("lastIdentation", 0)
324    st.dictStack.getSubTopDict()["indentInfo"] = newIdInfo
325   
326    return result
327
328
329
330def preActNewLinesParagraph(s, l, st, pe):
331    if "preHtmlTag" in st.nameStack:
332        raise ParseException(s, l, "Newlines aren't paragraph inside <pre> tag")
333   
334    wikiFormatDetails = st.dictStack["wikiFormatDetails"]
335    if not wikiFormatDetails.paragraphMode:
336        raise ParseException(s, l, "Newlines are only paragraph in paragraph mode")
337
338
339def preActNewLineLineBreak(s, l, st, pe):
340    if "preHtmlTag" in st.nameStack:
341        raise ParseException(s, l, "Newline isn't line break inside <pre> tag")
342   
343    wikiFormatDetails = st.dictStack["wikiFormatDetails"]
344    if wikiFormatDetails.paragraphMode:
345        raise ParseException(s, l, "Newline isn't line break in paragraph mode")
346
347
348def preActNewLineWhitespace(s, l, st, pe):
349    if "preHtmlTag" in st.nameStack:
350        raise ParseException(s, l, "Newline isn't whitespace inside <pre> tag")
351   
352    wikiFormatDetails = st.dictStack["wikiFormatDetails"]
353    if not wikiFormatDetails.paragraphMode:
354        raise ParseException(s, l, "Newline is only whitespace in paragraph mode")
355
356
357
358
359def preActUlPrepareStack(s, l, st, pe):
360    oldIdInfo = st.dictStack.getSubTopDict().get("indentInfo")
361    newIdInfo = IndentInfo("ul")
362
363    newIdInfo.level = st.dictStack.get("lastIdentation", 0)
364    st.dictStack["indentInfo"] = newIdInfo
365
366def preActOlPrepareStack(s, l, st, pe):
367    oldIdInfo = st.dictStack.getSubTopDict().get("indentInfo")
368    newIdInfo = IndentInfo("ol")
369
370    newIdInfo.level = st.dictStack.get("lastIdentation", 0)
371    st.dictStack["indentInfo"] = newIdInfo
372
373
374
375def inmostIndentChecker(typ):
376    def startAction(s, l, st, pe):
377        if st.dictStack["indentInfo"].type != typ:
378            raise ParseException(s, l, 'Expected inmost indent type "%s"' % typ)
379
380    return startAction
381
382
383
384# Only an empty line
385fakeIndentation = buildRegex(ur"^[ \t]+$")
386
387newLine = buildRegex(ur"\n") + Optional(fakeIndentation)
388
389
390
391newLinesParagraph = newLine + OneOrMore(newLine)
392newLinesParagraph = newLinesParagraph.setResultsNameNoCopy("newParagraph")\
393        .setParseStartAction(preActNewLinesParagraph)\
394        .setParseAction(actionResetIndent)
395
396
397newLineLineBreak = newLine
398newLineLineBreak = newLineLineBreak.setResultsName("lineBreak")\
399        .setParseStartAction(preActNewLineLineBreak)\
400        .setParseAction(actionResetIndent)
401
402
403newLineWhitespace = newLine
404newLineWhitespace = newLineWhitespace.setResultsName("whitespace")\
405        .setParseStartAction(preActNewLineWhitespace)
406
407
408moreIndentation = buildRegex(ur"^[ \t]*(?!\n)").setValidateAction(validateMoreIndent)
409moreIndentation = moreIndentation.setParseStartAction(validateInmostIndentNormal).\
410        setParseAction(actionMoreIndent).setName("moreIndentation")
411
412equalIndentation = buildRegex(ur"^[ \t]*(?!\n)").setValidateAction(validateEqualIndent)
413equalIndentation = equalIndentation.setParseAction(actionIndent).\
414        setName("equalIndentation")
415
416lessIndentation = buildRegex(ur"^[ \t]*(?!\n)").setValidateAction(validateLessIndent)
417lessIndentation = lessIndentation.setParseAction(actionIndent).\
418        setName("lessIndentation")
419
420lessIndentOrEnd = stringEnd | lessIndentation
421lessIndentOrEnd = lessIndentOrEnd.setName("lessIndentOrEnd")
422
423
424equivalIndentation = buildRegex(ur"^[ \t]+(?!\n)").setValidateAction(validateEquivalIndent)
425equivalIndentation = equivalIndentation.setParseAction(actionIndent).\
426        setName("equivalIndentation")
427
428
429indentedText = moreIndentation + content + FollowedBy(lessIndentOrEnd)
430indentedText = indentedText.setResultsNameNoCopy("indentedText")
431
432
433listStartIndentation  = buildRegex(ur"^[ \t]*")
434listStartIndentation = listStartIndentation.\
435        setParseAction(actionListStartIndent).setName("listStartIndentation")
436
437
438bullet = buildRegex(ur"\*[ \t]", "bullet")
439
440bulletEntry = equalIndentation.copy()\
441        .addParseStartAction(inmostIndentChecker("ul")) + bullet  # + \
442       
443
444
445unorderedList = listStartIndentation + bullet + \
446        (content + FollowedBy(lessIndentOrEnd))\
447        .addParseStartAction(preActUlPrepareStack)
448unorderedList = unorderedList.setResultsNameNoCopy("unorderedList")
449
450
451number = buildRegex(ur"(?:\d+\.)*(\d+)\.[ \t]|#[ \t]", "number")
452
453numberEntry = equalIndentation.copy()\
454        .addParseStartAction(inmostIndentChecker("ol")) + number
455
456
457
458orderedList = listStartIndentation + number + \
459        (content + FollowedBy(lessIndentOrEnd))\
460        .addParseStartAction(preActOlPrepareStack)
461orderedList = orderedList.setResultsNameNoCopy("orderedList")
462
463
464
465
466# -------------------- Table --------------------
467
468
469tableEnd = buildRegex(ur"^[ \t]*>>[ \t]*(?:\n|$)")
470newRow = buildRegex(ur"\n")
471
472newCellBar = buildRegex(ur"\|")
473newCellTab = buildRegex(ur"\t")
474
475
476def chooseCellEnd(s, l, st, pe):
477    """
478    """
479    if st.dictStack.get("table.tabSeparated", False):
480        return newCellTab
481    else:
482        return newCellBar
483
484
485newCell = Choice([newCellBar, newCellTab], chooseCellEnd)
486
487
488tableRow = tableContentInCell + ZeroOrMore(newCell + tableContentInCell)
489tableRow = tableRow.setResultsNameNoCopy("tableRow").setParseAction(actionHideOnEmpty)
490
491
492def actionTableModeAppendix(s, l, st, t):
493    for key, data in t.entries:
494        if key == "t":
495            st.dictStack.getNamedDict("table")["table.tabSeparated"] = True
496            return
497
498    st.dictStack.getNamedDict("table")["table.tabSeparated"] = False
499
500
501tableModeAppendix = modeAppendix.setResultsName("tableModeAppendix").addParseAction(actionTableModeAppendix)
502
503table = buildRegex(ur"<<\|").setParseStartAction(preActCheckNothingLeft) + \
504        Optional(tableModeAppendix) + buildRegex(ur"[ \t]*\n") + tableRow + ZeroOrMore(newRow + tableRow) + tableEnd
505table = table.setResultsNameNoCopy("table")
506
507
508
509# -------------------- Suppress highlighting and no export --------------------
510
511suppressHighlightingMultipleLines = buildRegex(ur"<<[ \t]*\n")\
512        .setParseStartAction(preActCheckNothingLeft) + \
513        buildRegex(ur".*?(?=^[ \t]*>>[ \t]*(?:\n|$))", "plainText") + \
514        buildRegex(ur"^[ \t]*>>[ \t]*(?:\n|$)")
515
516suppressHighlightingSingleLine = buildRegex(ur"<<") + \
517        buildRegex(ur"[^\n]*?(?=>>)", "plainText") + buildRegex(ur">>")
518
519# suppressHighlighting = suppressHighlightingMultipleLines | suppressHighlightingSingleLine
520
521
522
523
524# -------------------- No export area--------------------
525
526def actionNoExport(s, l, st, t):
527    # Change name to reduce work when interpreting
528    t.name = "noExport"
529
530
531
532noExportMultipleLinesEnd = buildRegex(ur"^[ \t]*>>[ \t]*(?:\n|$)")
533noExportSingleLineEnd = buildRegex(ur">>")
534
535
536noExportMultipleLines = buildRegex(ur"<<hide[ \t]*\n")\
537        .setParseStartAction(preActCheckNothingLeft,
538        createCheckNotIn(("noExportMl", "noExportSl"))) + \
539        content + noExportMultipleLinesEnd
540noExportMultipleLines = noExportMultipleLines.setResultsNameNoCopy("noExportMl")\
541        .setParseAction(actionNoExport)
542
543noExportSingleLine = buildRegex(ur"<<hide[ \t]") + oneLineContent + \
544        noExportSingleLineEnd
545noExportSingleLine = noExportSingleLine.setResultsNameNoCopy("noExportSl")\
546        .setParseStartAction(
547        createCheckNotIn(("noExportMl", "noExportSl")))\
548        .setParseAction(actionNoExport)
549
550
551
552# -------------------- Pre block --------------------
553
554preBlock = buildRegex(ur"<<pre[ \t]*\n")\
555        .setParseStartAction(preActCheckNothingLeft) + \
556        buildRegex(ur".*?(?=^[ \t]*>>[ \t]*(?:\n|$))", "preText") + \
557        buildRegex(ur"^[ \t]*>>[ \t]*(?:\n|$)")
558preBlock = preBlock.setResultsNameNoCopy("preBlock")
559
560
561# -------------------- Auto generated area --------------------
562# TODO
563
564# autoGeneratedArea = buildRegex(ur"<<[ \t]+")\
565#         .setParseStartAction(preActCheckNothingLeft) + \
566#         buildRegex(ur"[^\n]+", "expression") + \
567#         buildRegex(ur"\n") + buildRegex(ur".*?(?=>>)", "plainText") + \
568#         buildRegex(ur">>[ \t]*$").setParseStartAction(preActCheckNothingLeft)
569
570
571
572
573# -------------------- <pre> html tag --------------------
574
575def actionPreHtmlTag(s, l, st, t):
576    """
577    Remove the node name so this doesn't become an own NTNode.
578    """
579    t.name = None
580
581
582
583preHtmlStart = buildRegex(ur"<pre(?: [^\n>]*)?>", "htmlTag")\
584        .setParseStartAction(createCheckNotIn(("preHtmlTag",)))
585
586preHtmlEnd = buildRegex(ur"</pre(?: [^\n>]*)?>", "htmlTag")
587
588preHtmlTag = preHtmlStart + content + preHtmlEnd
589preHtmlTag = preHtmlTag.setResultsNameNoCopy("preHtmlTag")\
590        .setParseAction(actionPreHtmlTag)
591
592
593
594# -------------------- Wikiwords and URLs --------------------
595
596BracketStart = u"["
597BracketStartPAT = ur"\["
598
599BracketEnd = u"]"
600BracketEndPAT = ur"\]"
601# WikiWordNccPAT = ur"/?(?:/?[^\\/\[\]\|\000-\037=:;#!]+)+" # ur"[\w\-\_ \t]+"
602
603# Single part of subpage path
604WikiWordPathPartPAT = ur"(?!\.\.)[^\\/\[\]\|\000-\037=:;#!]+"
605WikiPageNamePAT = WikiWordPathPartPAT + "(?:/" + WikiWordPathPartPAT + ")*"
606
607# Begins with dotted path parts which mean to go upward in subpage path
608WikiWordDottedPathPAT = ur"\.\.(/\.\.)*(?:/" + WikiWordPathPartPAT + ")*"
609WikiWordNonDottedPathPAT = ur"/{0,2}" + WikiPageNamePAT
610
611WikiWordNccPAT = WikiWordDottedPathPAT + ur"|" + WikiWordNonDottedPathPAT
612
613WikiWordTitleStartPAT = ur"\|"
614WikiWordAnchorStart = u"!"
615WikiWordAnchorStartPAT = ur"!"
616
617# Bracket start, escaped for reverse RE pattern (for autocompletion)
618BracketStartRevPAT = ur"\["
619# Bracket end, escaped for reverse RE pattern (for autocompletion)
620BracketEndRevPAT = ur"\]"
621
622WikiWordNccRevPAT = ur"[^\\\[\]\|\000-\037=:;#!]+?"  # ur"[\w\-\_ \t.]+?"
623
624
625
626WikiWordCcPAT = (ur"(?:[" +
627        UPPERCASE +
628        ur"]+[" +
629        LOWERCASE +
630        ur"]+[" +
631        UPPERCASE +
632        ur"]+[" +
633        LETTERS + string.digits +
634        ur"]*|[" +
635        UPPERCASE +
636        ur"]{2,}[" +
637        LOWERCASE +
638        ur"]+)")
639
640
641UrlPAT = ur'(?:(?:wiki|https?|ftp|rel)://|mailto:|Outlook:\S|file://?)'\
642        ur'(?:(?![.,;:!?)\]]+(?:["\s]|$))[^"\s<>])*'
643
644
645# UrlInBracketsPAT = ur'(?:(?:wiki|https?|ftp|rel)://|mailto:|Outlook:\S|file://?)'\
646#         ur'(?:[^"\t\n<>' + BracketEndPAT + '])*'
647
648
649bracketStart = buildRegex(BracketStartPAT)
650bracketEnd = buildRegex(BracketEndPAT)
651
652
653UnescapeExternalFragmentRE   = re.compile(ur"#(.)",
654                              re.DOTALL | re.UNICODE | re.MULTILINE)
655
656
657def reThrough(matchobj):
658    return matchobj.group(1)
659
660
661def actionSearchFragmentExtern(s, l, st, t):
662    """
663    Called to unescape external fragment of wikiword.
664    """
665    lt2 = getFirstTerminalNode(t)
666    if lt2 is None:
667        return None
668   
669    lt2.unescaped = UnescapeExternalFragmentRE.sub(ur"\1", lt2.text)
670
671
672UnescapeStandardRE = re.compile(EscapePlainCharPAT + ur"(.)",
673                              re.DOTALL | re.UNICODE | re.MULTILINE)
674
675def actionSearchFragmentIntern(s, l, st, t):
676    lt2 = getFirstTerminalNode(t)
677    if lt2 is None:
678        return None
679
680    lt2.unescaped = UnescapeStandardRE.sub(ur"\1", lt2.text)
681
682
683
684def resolveWikiWordLink(link, basePage):
685    """
686    If using subpages this is used to resolve a link to the right wiki word
687    relative to basePage on which the link is placed.
688    It returns the absolute link (page name).
689    """
690    return _TheHelper.resolvePrefixSilenceAndWikiWordLink(link, basePage)[2]
691   
692
693
694
695def actionWikiWordNcc(s, l, st, t):
696    t.wikiWord = t.findFlatByName("word")
697    if t.wikiWord is not None:
698        t.wikiWord = resolveWikiWordLink(t.wikiWord.getString(),
699                st.dictStack["wikiFormatDetails"].basePage)
700
701        if t.wikiWord == u"":
702            raise ParseException(s, l, "Subpage resolution of wikiword failed")
703
704    t.titleNode = t.findFlatByName("title")
705
706    fragmentNode = t.findFlatByName("searchFragment")
707    if fragmentNode is not None:
708        t.searchFragment = fragmentNode.unescaped
709    else:
710        t.searchFragment = None
711   
712    t.anchorLink = t.findFlatByName("anchorLink")
713    if t.anchorLink is not None:
714        t.anchorLink = t.anchorLink.getString()
715
716
717
718def preActCheckWikiWordCcAllowed(s, l, st, pe):
719    try:
720        wikiFormatDetails = st.dictStack["wikiFormatDetails"]
721       
722        if not wikiFormatDetails.withCamelCase:
723            raise ParseException(s, l, "CamelCase words not allowed here")
724    except KeyError:
725        pass
726
727
728def actionWikiWordCc(s, l, st, t):
729    t.wikiWord = t.findFlatByName("word")
730    if t.wikiWord is not None:
731        wikiFormatDetails = st.dictStack["wikiFormatDetails"]
732
733        t.wikiWord = resolveWikiWordLink(t.wikiWord.getString(),
734                wikiFormatDetails.basePage)
735
736        if t.wikiWord == u"":
737            raise ParseException(s, l, "Subpage resolution of wikiword failed")
738
739        try:
740#             wikiFormatDetails = st.dictStack["wikiFormatDetails"]
741           
742            if t.wikiWord in wikiFormatDetails.wikiDocument.getCcWordBlacklist():
743                raise ParseException(s, l, "CamelCase word is in blacklist")
744        except KeyError:
745            pass
746
747    t.titleNode = None
748
749    fragmentNode = t.findFlatByName("searchFragment")
750    if fragmentNode is not None:
751        t.searchFragment = fragmentNode.unescaped
752    else:
753        t.searchFragment = None
754
755    t.anchorLink = t.findFlatByName("anchorLink")
756    if t.anchorLink is not None:
757        t.anchorLink = t.anchorLink.getString()
758
759
760
761
762
763def actionExtractableWikiWord(s, l, st, t):
764    t.wikiWord = t.findFlatByName("word")
765    if t.wikiWord is not None:
766        t.wikiWord = t.wikiWord.getString()
767
768
769
770def actionUrlLink(s, l, st, t):
771    t.appendixNode = t.findFlatByName("urlModeAppendix")
772
773    t.url = t.findFlatByName("url").getString()
774    t.titleNode = t.findFlatByName("title")
775#     print "--actionUrlLink3", repr(t.url)
776
777
778def actionAnchorDef(s, l, st, t):
779    t.anchorLink = t.findFlatByName("anchor").getString()
780
781
782searchFragmentExtern = buildRegex(ur"#") + \
783        buildRegex(ur"(?:(?:#.)|[^ \t\n#])+", "searchFragment")\
784        .setParseAction(actionSearchFragmentExtern)
785
786searchFragmentIntern = buildRegex(ur"#") + buildRegex(ur"(?:(?:" + EscapePlainCharPAT +
787        ur".)|(?!" + WikiWordTitleStartPAT +
788        ur"|" +  BracketEndPAT + ur").)+", "searchFragment")\
789        .setParseAction(actionSearchFragmentIntern)
790
791wikiWordAnchorLink = buildRegex(WikiWordAnchorStartPAT) + \
792        buildRegex(ur"[A-Za-z0-9\_]+", "anchorLink")
793
794
795title = buildRegex(WikiWordTitleStartPAT + ur"[ \t]*") + titleContent    # content.setResultsName("title")
796
797
798wikiWordNccCore = buildRegex(WikiWordNccPAT, "word")
799
800wikiWordNcc = bracketStart + \
801        wikiWordNccCore.copy().addParseAction(actionCutRightWhitespace) + \
802        Optional(MatchFirst([searchFragmentIntern, wikiWordAnchorLink])) + whitespace + \
803        Optional(title) + bracketEnd + \
804        Optional(MatchFirst([searchFragmentExtern, wikiWordAnchorLink]))
805
806wikiWordNcc = wikiWordNcc.setResultsNameNoCopy("wikiWord").setName("wikiWordNcc")\
807        .setParseAction(actionWikiWordNcc)
808
809
810anchorDef = buildRegex(ur"^[ \t]*anchor:[ \t]*") + buildRegex(ur"[A-Za-z0-9\_]+",
811        "anchor") + buildRegex(ur"\n")
812anchorDef = anchorDef.setResultsNameNoCopy("anchorDef").setParseAction(actionAnchorDef)
813
814
815AnchorRE = re.compile(ur"^[ \t]*anchor:[ \t]*(?P<anchorValue>[A-Za-z0-9\_]+)\n",
816        re.DOTALL | re.UNICODE | re.MULTILINE)
817
818
819
820urlModeAppendix = modeAppendix.setResultsName("urlModeAppendix")
821
822urlWithAppend = buildRegex(UrlPAT, "url") + Optional(buildRegex(ur">") + \
823        urlModeAppendix)
824
825# urlWithAppendInBrackets = buildRegex(UrlInBracketsPAT, "url") + Optional(buildRegex(ur">") + \
826#         urlModeAppendix)
827
828
829urlBare = urlWithAppend.setResultsName("urlLink")
830urlBare = urlBare.setParseAction(actionUrlLink)
831
832# urlTitled = bracketStart + urlWithAppendInBrackets + whitespace + \
833#         Optional(title) + bracketEnd
834urlTitled = bracketStart + urlWithAppend + whitespace + \
835        Optional(title) + bracketEnd
836urlTitled = urlTitled.setResultsNameNoCopy("urlLink").setParseAction(actionUrlLink)
837
838
839
840urlRef = urlTitled | urlBare
841
842
843# TODO anchor/fragment
844wikiWordCc = buildRegex(ur"\b(?<!~)" + WikiWordCcPAT + ur"\b", "word") + \
845        Optional(MatchFirst([searchFragmentExtern, wikiWordAnchorLink])) # Group( )
846wikiWordCc = wikiWordCc.setResultsNameNoCopy("wikiWord").setName("wikiWordCc")\
847        .setParseStartAction(preActCheckWikiWordCcAllowed)\
848        .setParseAction(actionWikiWordCc)
849
850wikiWord = wikiWordNcc | wikiWordCc
851
852
853
854# Needed for _TheHelper.extractWikiWordFromLink()
855
856extractableWikiWord = (wikiWordNccCore | wikiWordNcc) + stringEnd
857extractableWikiWord = extractableWikiWord.setResultsNameNoCopy("extractableWikiWord")\
858        .setParseAction(actionExtractableWikiWord).optimize(("regexcombine",))\
859        .parseWithTabs()
860
861
862wikiPageNameRE = re.compile(ur"^" + WikiPageNamePAT + ur"$",
863        re.DOTALL | re.UNICODE | re.MULTILINE)
864
865
866wikiWordCcRE = re.compile(ur"^" + WikiWordCcPAT + ur"$",
867        re.DOTALL | re.UNICODE | re.MULTILINE)
868
869def isCcWikiWord(word):
870    return bool(wikiWordCcRE.match(word))
871
872
873wikiLinkCoreRE = re.compile(ur"^" + WikiWordNccPAT + ur"$",
874        re.DOTALL | re.UNICODE | re.MULTILINE)
875
876
877
878# -------------------- Footnotes --------------------
879
880footnotePAT = ur"[0-9]+"
881
882def preActCheckFootnotesAllowed(s, l, st, pe):
883    wikiFormatDetails = st.dictStack["wikiFormatDetails"]
884   
885    if wikiFormatDetails.wikiLanguageDetails.footnotesAsWws:
886        raise ParseException(s, l, "CamelCase words not allowed here")
887
888
889def actionFootnote(s, l, st, t):
890    t.footnoteId = t.findFlatByName("footnoteId").getString()
891
892
893footnote = bracketStart + buildRegex(footnotePAT, "footnoteId") + bracketEnd
894footnote = footnote.setResultsNameNoCopy("footnote")\
895        .setParseStartAction(preActCheckFootnotesAllowed)\
896        .setParseAction(actionFootnote)
897
898
899footnoteRE = re.compile(ur"^" + footnotePAT + ur"$",
900        re.DOTALL | re.UNICODE | re.MULTILINE)
901
902
903# -------------------- Attributes (=properties) and insertions --------------------
904
905
906def actionAttrInsValueQuoteStart(s, l, st, t):
907    st.dictStack.getSubTopDict()["attrInsValueQuote"] = t[0].text
908
909def actionAttrInsValueQuoteEnd(s, l, st, t):
910    if t[0].text != st.dictStack.getSubTopDict().get("attrInsValueQuote"):
911        raise ParseException(s, l, "End quote of attribute/insertion does not match start")
912
913
914def pseudoActionAttrInsQuotedValue(s, l, st, t):
915    if t.strLength == 0:
916        return []
917    t.name = "value"
918    return t
919
920
921def actionAttribute(s, l, st, t):
922    key = t.findFlatByName("key").getString()
923    t.key = key
924    t.keyComponents = t.key.split(u".")
925    t.attrs = [(key, vNode.getString()) for vNode in t.iterFlatByName("value")]
926
927
928def actionInsertion(s, l, st, t):
929    t.key = t.findFlatByName("key").getString()
930    t.keyComponents = t.key.split(u".")
931    values = list(vNode.getString() for vNode in t.iterFlatByName("value"))
932    t.value = values[0]
933    del values[0]
934    t.appendices = values
935
936
937
938attrInsQuote = buildRegex(ur"\"+|'+|/+|\\+")
939attrInsQuoteStart = attrInsQuote.copy()\
940        .setParseAction(actionAttrInsValueQuoteStart)
941attrInsQuoteEnd = attrInsQuote.copy()\
942        .setParseAction(actionAttrInsValueQuoteEnd)
943
944attrInsQuotedValue = FindFirst([], attrInsQuoteEnd)\
945        .setPseudoParseAction(pseudoActionAttrInsQuotedValue)
946
947# attrInsNonQuotedValue = buildRegex(ur"[\w\-\_ \t:,.!?#%|/]*", "value")
948attrInsNonQuotedValue = buildRegex(ur"(?:[ \t]*[\w\-\_:,.!?#%|/]+)*", "value")
949
950
951attrInsValue = whitespace + ((attrInsQuoteStart + attrInsQuotedValue + \
952        attrInsQuoteEnd) | attrInsNonQuotedValue)
953
954attrInsKey = buildRegex(ur"[\w\-\_\.]+", "key")
955
956attribute = bracketStart + whitespace + attrInsKey + \
957        buildRegex(ur"[ \t]*[=:]") + attrInsValue + \
958        ZeroOrMore(buildRegex(ur";") + attrInsValue) + whitespace + bracketEnd
959attribute = attribute.setResultsNameNoCopy("attribute").setParseAction(actionAttribute)
960
961
962insertion = bracketStart + buildRegex(ur":") + whitespace + attrInsKey + \
963        buildRegex(ur"[ \t]*[=:]") + attrInsValue + \
964        ZeroOrMore(buildRegex(ur";") + attrInsValue) + whitespace + bracketEnd
965insertion = insertion.setResultsNameNoCopy("insertion").setParseAction(actionInsertion)
966
967
968
969# -------------------- Additional regexes to provide --------------------
970
971
972# Needed for auto-bullet/auto-unbullet functionality of editor
973BulletRE        = re.compile(ur"^(?P<indentBullet>[ \t]*)(?P<actualBullet>\*[ \t])",
974        re.DOTALL | re.UNICODE | re.MULTILINE)
975NumericSimpleBulletRE = re.compile(ur"^(?P<indentBullet>[ \t]*)(?P<actualBullet>#[ \t])",
976        re.DOTALL | re.UNICODE | re.MULTILINE)
977NumericBulletRE = re.compile(ur"^(?P<indentNumeric>[ \t]*)(?P<preLastNumeric>(?:\d+\.)*)(\d+)\.[ \t]",
978        re.DOTALL | re.UNICODE | re.MULTILINE)
979
980
981# Needed for handleRewrapText
982EmptyLineRE     = re.compile(ur"^[ \t\r\n]*$",
983        re.DOTALL | re.UNICODE | re.MULTILINE)
984
985
986
987
988# Reverse REs for autocompletion
989revSingleWikiWord    =       (ur"(?:[" +
990                             LETTERS + string.digits +
991                             ur"]*[" +
992                             UPPERCASE+
993                             ur"])")   # Needed for auto-completion
994
995RevWikiWordRE      = re.compile(ur"^" +
996                             revSingleWikiWord + ur"(?![\~])\b",
997                             re.DOTALL | re.UNICODE | re.MULTILINE)
998                             # Needed for auto-completion
999
1000
1001RevWikiWordRE2     = re.compile(ur"^" + WikiWordNccRevPAT + BracketStartRevPAT,
1002        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1003
1004RevAttributeValue     = re.compile(
1005        ur"^([\w\-\_ \t:;,.!?#/|]*?)([ \t]*[=:][ \t]*)([\w\-\_ \t\.]+?)" +
1006        BracketStartRevPAT,
1007        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1008
1009
1010RevTodoKeyRE = re.compile(ur"^(?:[^:\s]{0,40}\.)??"
1011        ur"(?:odot|enod|tiaw|noitca|kcart|eussi|noitseuq|tcejorp)",
1012        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1013
1014RevTodoValueRE = re.compile(ur"^[^\n:]{0,30}:" + RevTodoKeyRE.pattern[1:],
1015        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1016
1017
1018RevWikiWordAnchorRE = re.compile(ur"^(?P<anchorBegin>[A-Za-z0-9\_]{0,20})" +
1019        WikiWordAnchorStartPAT + ur"(?P<wikiWord>" + RevWikiWordRE.pattern[1:] + ur")",
1020        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1021       
1022RevWikiWordAnchorRE2 = re.compile(ur"^(?P<anchorBegin>[A-Za-z0-9\_]{0,20})" +
1023        WikiWordAnchorStartPAT + BracketEndRevPAT + ur"(?P<wikiWord>" +
1024        WikiWordNccRevPAT + ur")" + BracketStartRevPAT,
1025        re.DOTALL | re.UNICODE | re.MULTILINE)  # Needed for auto-completion
1026
1027
1028# Simple todo RE for autocompletion.
1029ToDoREWithCapturing = re.compile(ur"^([^:\s]+):[ \t]*(.+?)$",
1030        re.DOTALL | re.UNICODE | re.MULTILINE)
1031
1032
1033
1034# For auto-link mode relax
1035AutoLinkRelaxSplitRE = re.compile(r"[\W]+", re.IGNORECASE | re.UNICODE)
1036
1037AutoLinkRelaxJoinPAT = ur"[\W]+"
1038AutoLinkRelaxJoinFlags = re.IGNORECASE | re.UNICODE
1039
1040
1041
1042# For spell checking
1043TextWordRE = re.compile(ur"(?P<negative>[0-9]+|"+ UrlPAT + u"|\b(?<!~)" +
1044        WikiWordCcPAT + ur"\b)|\b[\w']+",
1045        re.DOTALL | re.UNICODE | re.MULTILINE)
1046
1047
1048
1049
1050
1051# -------------------- End tokens --------------------
1052
1053
1054TOKEN_TO_END = {
1055        "bold": boldEnd,
1056        "italics": italicsEnd,
1057        "unorderedList": lessIndentOrEnd,
1058        "orderedList": lessIndentOrEnd,
1059        "indentedText": lessIndentOrEnd,
1060        "wikiWord": bracketEnd,
1061        "urlLink": bracketEnd,
1062        "table": tableEnd,
1063        "preHtmlTag": preHtmlEnd,
1064        "heading": headingEnd,
1065        "todoEntry": todoEnd,
1066        "noExportMl": noExportMultipleLinesEnd,
1067        "noExportSl": noExportSingleLineEnd
1068    }
1069
1070
1071def chooseEndToken(s, l, st, pe):
1072    """
1073    """
1074    for tokName in reversed(st.nameStack):
1075        end = TOKEN_TO_END.get(tokName)
1076        if end is not None:
1077            return end
1078
1079    return stringEnd
1080
1081
1082endToken = Choice([stringEnd]+TOKEN_TO_END.values(), chooseEndToken)
1083
1084endTokenInTable = endToken | newCell | newRow
1085
1086
1087# -------------------- Content definitions --------------------
1088
1089
1090findMarkupInCell = FindFirst([bold, italics, noExportSingleLine,
1091        suppressHighlightingSingleLine,
1092        urlRef, insertion, escapedChar, footnote, wikiWord,
1093        htmlTag, htmlEntity], endTokenInTable)
1094findMarkupInCell = findMarkupInCell.setPseudoParseAction(pseudoActionFindMarkup)
1095
1096temp = ZeroOrMore(NotAny(endTokenInTable) + findMarkupInCell)
1097temp = temp.leaveWhitespace().parseWithTabs()
1098tableContentInCell << temp
1099
1100
1101
1102endTokenInTitle = endToken | buildRegex(ur"\n")
1103
1104
1105findMarkupInTitle = FindFirst([bold, italics, noExportSingleLine,
1106        suppressHighlightingSingleLine,
1107        urlRef, insertion, escapedChar, footnote, htmlTag, htmlEntity],
1108        endTokenInTitle)
1109findMarkupInTitle = findMarkupInTitle.setPseudoParseAction(pseudoActionFindMarkup)
1110
1111temp = ZeroOrMore(NotAny(endTokenInTitle) + findMarkupInTitle)
1112temp = temp.leaveWhitespace().parseWithTabs()
1113titleContent << temp
1114
1115
1116
1117findMarkupInHeading = FindFirst([bold, italics, noExportSingleLine,
1118        suppressHighlightingSingleLine,
1119        urlRef, insertion, escapedChar, footnote, wikiWord, htmlTag,
1120        htmlEntity], endToken)
1121findMarkupInHeading = findMarkupInHeading.setPseudoParseAction(
1122        pseudoActionFindMarkup)
1123
1124temp = ZeroOrMore(NotAny(endToken) + findMarkupInHeading)
1125temp = temp.leaveWhitespace().parseWithTabs()
1126headingContent << temp
1127
1128
1129
1130findMarkupInTodo = FindFirst([bold, italics, noExportSingleLine,
1131        suppressHighlightingSingleLine,
1132        urlRef, attribute, insertion, escapedChar, footnote, wikiWord,   # wikiWordNcc, wikiWordCc,
1133        htmlTag, htmlEntity], endToken)
1134findMarkupInTodo = findMarkupInTodo.setPseudoParseAction(
1135        pseudoActionFindMarkup)
1136
1137temp = OneOrMore(NotAny(endToken) + findMarkupInTodo)
1138temp = temp.leaveWhitespace().parseWithTabs()
1139todoContent << temp
1140oneLineContent << temp
1141
1142
1143findMarkup = FindFirst([bold, italics, noExportSingleLine,
1144        suppressHighlightingSingleLine, urlRef,
1145        attribute, insertion, escapedChar, footnote, wikiWord,
1146        newLinesParagraph, newLineLineBreak, newLineWhitespace, heading,
1147        todoEntry, anchorDef, preHtmlTag, htmlTag,
1148        htmlEntity, bulletEntry, unorderedList, numberEntry, orderedList,
1149        indentedText, table, preBlock, noExportMultipleLines,
1150        suppressHighlightingMultipleLines,
1151        script, horizontalLine, equivalIndentation], endToken)
1152findMarkup = findMarkup.setPseudoParseAction(pseudoActionFindMarkup)
1153
1154
1155content << ZeroOrMore(NotAny(endToken) + findMarkup)  # .setResultsName("ZeroOrMore")
1156content = content.leaveWhitespace().setValidateAction(validateNonEmpty).parseWithTabs()
1157
1158
1159
1160text = content + stringEnd
1161
1162
1163# Run optimizer
1164
1165# Separate element for LanguageHelper.parseTodoEntry()
1166todoAsWhole = todoAsWhole.optimize(("regexcombine",)).parseWithTabs()
1167
1168# Whole text, optimizes subelements recursively
1169text = text.optimize(("regexcombine",)).parseWithTabs()
1170# text = text.parseWithTabs()
1171
1172
1173# text.setDebugRecurs(True)
1174
1175
1176
1177def _buildBaseDict(wikiDocument=None, formatDetails=None):
1178    if formatDetails is None:
1179        if wikiDocument is None:
1180            formatDetails = WikiDocument.getUserDefaultWikiPageFormatDetails()
1181            formatDetails.setWikiLanguageDetails(WikiLanguageDetails(None, None))
1182        else:
1183            formatDetails = wikiDocument.getWikiDefaultWikiPageFormatDetails()
1184
1185    return {"indentInfo": IndentInfo("normal"),
1186            "wikiFormatDetails": formatDetails
1187        }
1188
1189
1190
1191# -------------------- API for plugin WikiParser --------------------
1192# During beta state of the WikidPad version, this API isn't stable yet,
1193# so changes may occur!
1194
1195
1196class _TheParser(object):
1197    @staticmethod
1198    def reset():
1199        """
1200        Reset possible internal states of a (non-thread-safe) object for
1201        later reuse.
1202        """
1203        pass
1204
1205    @staticmethod
1206    def getWikiLanguageName():
1207        """
1208        Return the internal name of the wiki language implemented by this
1209        parser.
1210        """
1211        return "wikidpad_default_2_0"
1212
1213
1214
1215    @staticmethod
1216    def _postProcessing(intLanguageName, content, formatDetails, pageAst,
1217            threadstop):
1218        """
1219        Do some cleanup after main parsing.
1220        Not part of public API.
1221        """
1222        autoLinkRelaxRE = None
1223        if formatDetails.autoLinkMode == u"relax":
1224            relaxList = formatDetails.wikiDocument.getAutoLinkRelaxInfo()
1225
1226            def recursAutoLink(ast):
1227                newAstNodes = []
1228                for node in ast.getChildren():
1229                    if isinstance(node, NonTerminalNode):
1230                        newAstNodes.append(recursAutoLink(node))
1231                        continue
1232   
1233                    if node.name == "plainText":
1234                        text = node.text
1235                        start = node.pos
1236                       
1237                        threadstop.testRunning()
1238                        while text != u"":
1239                            # The foundWordText is the text as typed in the page
1240                            # foundWord is the word as entered in database
1241                            # These two may differ (esp. in whitespaces)
1242                            foundPos = len(text)
1243                            foundWord = None
1244                            foundWordText = None
1245                           
1246                            # Search all regexes for the earliest match
1247                            for regex, word in relaxList:
1248                                match = regex.search(text)
1249                                if match:
1250                                    pos = match.start(0)
1251                                    if pos < foundPos:
1252                                        # Match is earlier than previous
1253                                        foundPos = pos
1254                                        foundWord = word
1255                                        foundWordText = match.group(0)
1256                                        if pos == 0:
1257                                            # Can't find a better match -> stop loop
1258                                            break
1259
1260                            # Add token for text before found word (if any)
1261                            preText = text[:foundPos]
1262                            if preText != u"":
1263                                newAstNodes.append(buildSyntaxNode(preText,
1264                                        start, "plainText"))
1265               
1266                                start += len(preText)
1267                                text = text[len(preText):]
1268                           
1269                            if foundWord is not None:
1270                                wwNode = buildSyntaxNode(
1271                                        [buildSyntaxNode(foundWordText, start, "word")],
1272                                        start, "wikiWord")
1273                                       
1274                                wwNode.searchFragment = None
1275                                wwNode.anchorLink = None
1276                                wwNode.wikiWord = foundWord
1277                                wwNode.titleNode = buildSyntaxNode(foundWordText, start, "plainText") # None
1278
1279                                newAstNodes.append(wwNode)
1280
1281                                inc = max(len(foundWordText), 1)
1282                                start += inc
1283                                text = text[inc:]
1284
1285                        continue
1286
1287                    newAstNodes.append(node)
1288
1289
1290                ast.sub = newAstNodes
1291   
1292                return ast
1293
1294            pageAst = recursAutoLink(pageAst)
1295       
1296        return pageAst
1297
1298    @staticmethod
1299    def parse(intLanguageName, content, formatDetails, threadstop):
1300        """
1301        Parse the  content  written in wiki language  intLanguageName  using
1302        formatDetails  and regularly call  threadstop.testRunning()  to
1303        raise exception if execution thread is no longer current parsing
1304        thread.
1305        """
1306
1307        if len(content) == 0:
1308            return buildSyntaxNode([], 0, "text")
1309
1310        if formatDetails.noFormat:
1311            return buildSyntaxNode([buildSyntaxNode(content, 0, "plainText")],
1312                    0, "text")
1313
1314        baseDict = _buildBaseDict(formatDetails=formatDetails)
1315
1316##         _prof.start()
1317        try:
1318            t = text.parseString(content, parseAll=True, baseDict=baseDict,
1319                    threadstop=threadstop)
1320            t = buildSyntaxNode(t, 0, "text")
1321
1322            t = _TheParser._postProcessing(intLanguageName, content, formatDetails,
1323                    t, threadstop)
1324
1325        finally:
1326##             _prof.stop()
1327            pass
1328
1329        return t
1330
1331THE_PARSER = _TheParser()
1332
1333
1334
1335
1336
1337class WikiLanguageDetails(object):
1338    """
1339    Stores state of wiki language specific options and allows to check if
1340    two option sets are equivalent.
1341    """
1342    __slots__ = ("__weakref__", "footnotesAsWws", "wikiDocument")
1343
1344    def __init__(self, wikiDocument, docPage):
1345        self.wikiDocument = wikiDocument
1346        if self.wikiDocument is None:
1347            # Set wiki-independent default values
1348            self.footnotesAsWws = False
1349        else:
1350            self.footnotesAsWws = self.wikiDocument.getWikiConfig().getboolean(
1351                    "main", "footnotes_as_wikiwords", False)
1352
1353    @staticmethod
1354    def getWikiLanguageName():
1355        return "wikidpad_default_2_0"
1356
1357
1358    def isEquivTo(self, details):
1359        """
1360        Compares with other details object if both are "equivalent"
1361        """
1362        return self.getWikiLanguageName() == details.getWikiLanguageName() and \
1363                self.footnotesAsWws == details.footnotesAsWws
1364
1365
1366class _WikiLinkPath(object):
1367    __slots__ = ("upwardCount", "components")
1368    def __init__(self, link=None, pageName=None, upwardCount=-1,
1369            components=None):
1370        assert (link is None) or (pageName is None)
1371
1372        if pageName is not None:
1373            # Handle wiki word as absolute link
1374            self.upwardCount = -1
1375            self.components = pageName.split(u"/")
1376            return
1377
1378        if link is None:
1379            if components is None:
1380                components = []
1381
1382            self.upwardCount = upwardCount
1383            self.components = components
1384            return
1385
1386        if link.startswith("//"):
1387            self.upwardCount = -1
1388            self.components = link[2:].split(u"/")
1389            return
1390       
1391        if link.startswith("/"):
1392            self.upwardCount = 0
1393            self.components = link[1:].split(u"/")
1394            return
1395
1396        comps = link.split(u"/")
1397
1398        for i in xrange(0, len(comps)):
1399            if comps[i] != "..":
1400                self.upwardCount = i + 1
1401                self.components = comps[i:]
1402                return
1403       
1404        self.upwardCount = len(comps)
1405        self.components = []
1406       
1407    def clone(self):
1408        result = _WikiLinkPath()
1409        result.upwardCount = self.upwardCount
1410        result.components = self.components[:]
1411       
1412        return result
1413
1414    def isAbsolute(self):
1415        return self.upwardCount == -1
1416       
1417    def join(self, otherPath):
1418        if otherPath.upwardCount == -1:
1419            self.upwardCount = -1
1420            self.components = otherPath.components[:]
1421            return
1422
1423        self.components = self.components[:-otherPath.upwardCount] + \
1424                otherPath.components
1425
1426
1427    def getLinkCore(self):
1428        comps = u"/".join(self.components)
1429        if self.upwardCount == -1:
1430            return u"//" + comps
1431        elif self.upwardCount == 0:
1432            return u"/" + comps
1433        elif self.upwardCount == 1:
1434            return comps
1435        else:
1436            return u"/".join([u".."] * (self.upwardCount - 1)) + u"/" + comps
1437
1438
1439    def resolveWikiWord(self, basePath):
1440        if self.isAbsolute():
1441            # Absolute is checked separately so basePath can be None if
1442            # self is absolute
1443            return u"/".join(self.components)
1444
1445        absPath = basePath.joinTo(self)
1446        return u"/".join(absPath.components)
1447
1448
1449    def resolvePrefixSilenceAndWikiWordLink(self, basePath):
1450        """
1451        If using subpages this is used to resolve a link to the right wiki word
1452        for autocompletion. It returns a tuple (prefix, silence, pageName).
1453        Autocompletion now searches for all wiki words starting with pageName. For
1454        all found items it removes the first  silence  characters, prepends the  prefix
1455        instead and uses the result as suggestion for autocompletion.
1456       
1457        If prefix is None autocompletion is not possible.
1458        """
1459        if self.isAbsolute():
1460            return u"//", 0, self.resolveWikiWord(None)
1461
1462        assert basePath.isAbsolute()
1463       
1464        if len(self.components) == 0:
1465            # link path only consists of ".." -> autocompletion not possible
1466            if self.upwardCount == 0:
1467                return None, None, u"/".join(basePath.components)
1468
1469            return None, None, u"/".join(basePath.components[:-self.upwardCount])
1470
1471        if self.upwardCount == 0:
1472            return u"/", len(basePath.resolveWikiWord(None)) + 1, \
1473                    u"/".join(basePath.components + self.components)
1474
1475        def lenAddOne(s):
1476            return len(s) + 1 if s != "" else 0
1477
1478        if self.upwardCount == 1:
1479            return u"", \
1480                    lenAddOne(u"/".join(basePath.components[:-1])), \
1481                    u"/".join(basePath.components[:-1] + self.components)
1482
1483        return u"/".join([u".."] * (self.upwardCount - 1)) + u"/", \
1484                lenAddOne(u"/".join(basePath.components[:-self.upwardCount])), \
1485                u"/".join(basePath.components[:-self.upwardCount] +
1486                self.components)
1487
1488
1489
1490    def joinTo(self, otherPath):
1491        result = self.clone()
1492        result.join(otherPath)
1493        return result
1494
1495
1496
1497    @staticmethod
1498    def isAbsoluteLinkCore(linkCore):
1499        return linkCore.startswith(u"//")
1500
1501
1502    @staticmethod
1503    def getRelativePathByAbsPaths(targetAbsPath, baseAbsPath,
1504            downwardOnly=True):
1505        """
1506        Create a link to targetAbsPath relative to baseAbsPath.
1507        If downwardOnly is False, the link may contain parts to go to parents
1508            or siblings
1509        in path (in this wiki language, ".." are used for this).
1510        If downwardOnly is True, the function may return None if a relative
1511        link can't be constructed.
1512        """
1513        assert targetAbsPath.isAbsolute() and baseAbsPath.isAbsolute()
1514
1515        wordPath = targetAbsPath.components[:]
1516        baseWordPath = baseAbsPath.components[:]
1517       
1518        result = _WikiLinkPath()
1519       
1520        if downwardOnly:
1521            if len(baseWordPath) >= len(wordPath):
1522                return None
1523            if baseWordPath != wordPath[:len(baseWordPath)]:
1524                return None
1525           
1526            result.upwardCount = 0
1527            result.components = wordPath[len(baseWordPath):]
1528            return result
1529        # TODO test downwardOnly == False
1530        else:
1531            # Remove common path elements
1532            while len(wordPath) > 0 and len(baseWordPath) > 0 and \
1533                    wordPath[0] == baseWordPath[0]:
1534                del wordPath[0]
1535                del baseWordPath[0]
1536           
1537            if len(baseWordPath) == 0:
1538                if len(wordPath) == 0:
1539                    return None  # word == baseWord, TODO return u"." or something
1540
1541                result.upwardCount = 0
1542                result.components = wordPath
1543                return result
1544
1545            result.upwardCount = len(baseWordPath)
1546            result.components = wordPath
1547            return result
1548       
1549
1550
1551
1552
1553
1554
1555_RE_LINE_INDENT = re.compile(ur"^[ \t]*")
1556
1557class _TheHelper(object):
1558    @staticmethod
1559    def reset():
1560        pass
1561
1562    @staticmethod
1563    def getWikiLanguageName():
1564        return "wikidpad_default_2_0"
1565
1566
1567    # TODO More descriptive error messages (which character(s) is/are wrong?)
1568    @staticmethod   # isValidWikiWord
1569    def checkForInvalidWikiWord(word, wikiDocument=None, settings=None):
1570        """
1571        Test if word is syntactically a valid wiki word and no settings
1572        are against it. The camelCase black list is not checked.
1573        The function returns None IFF THE WORD IS VALID, an error string
1574        otherwise
1575        """
1576        if settings is not None and settings.has_key("footnotesAsWws"):
1577            footnotesAsWws = settings["footnotesAsWws"]
1578        else:
1579            if wikiDocument is None:
1580                footnotesAsWws = False
1581            else:
1582                footnotesAsWws = wikiDocument.getWikiConfig().getboolean(
1583                        "main", "footnotes_as_wikiwords", False)
1584
1585        if not footnotesAsWws and footnoteRE.match(word):
1586            return _(u"This is a footnote")
1587
1588        if wikiPageNameRE.match(word):
1589            return None
1590        else:
1591            return _(u"This is syntactically not a wiki word")
1592
1593
1594    # TODO More descriptive error messages (which character(s) is/are wrong?)
1595    @staticmethod   # isValidWikiWord
1596    def checkForInvalidWikiLink(word, wikiDocument=None, settings=None):
1597        """
1598        Test if word is syntactically a valid wiki word and no settings
1599        are against it. The camelCase black list is not checked.
1600        The function returns None IFF THE WORD IS VALID, an error string
1601        otherwise
1602        """
1603        if settings is not None and settings.has_key("footnotesAsWws"):
1604            footnotesAsWws = settings["footnotesAsWws"]
1605        else:
1606            if wikiDocument is None:
1607                footnotesAsWws = False
1608            else:
1609                footnotesAsWws = wikiDocument.getWikiConfig().getboolean(
1610                        "main", "footnotes_as_wikiwords", False)
1611
1612        if not footnotesAsWws and footnoteRE.match(word):
1613            return _(u"This is a footnote")
1614
1615        if wikiLinkCoreRE.match(word):
1616            return None
1617        else:
1618            return _(u"This is syntactically not a wiki word")
1619
1620
1621    @staticmethod
1622    def extractWikiWordFromLink(word, wikiDocument=None, basePage=None):  # TODO Problems with subpages?
1623        """
1624        Strip brackets and other link details if present and return wikiWord
1625        if a valid wiki word can be extracted, None otherwise.
1626        """
1627        if wikiDocument is None and basePage is not None:
1628            wikiDocument = basePage.getWikiDocument()
1629
1630        if basePage is None:
1631            baseDict = _buildBaseDict(wikiDocument=wikiDocument)
1632        else:
1633            baseDict = _buildBaseDict(formatDetails=basePage.getFormatDetails())
1634
1635        try:
1636            t = extractableWikiWord.parseString(word, parseAll=True,
1637                    baseDict=baseDict)
1638            t = t[0]
1639            return t.wikiWord
1640        except ParseException:
1641            return None
1642
1643
1644    resolveWikiWordLink = staticmethod(resolveWikiWordLink)
1645    """
1646    If using subpages this is used to resolve a link to the right wiki word
1647    relative to basePage on which the link is placed.
1648    It returns the absolute link (page name).
1649    """
1650
1651
1652    @staticmethod
1653    def resolvePrefixSilenceAndWikiWordLink(link, basePage):
1654        """
1655        If using subpages this is used to resolve a link to the right wiki word
1656        for autocompletion. It returns a tuple (prefix, silence, pageName).
1657        Autocompletion now searches for all wiki words starting with pageName. For
1658        all found items it removes the first  silence  characters, prepends the  prefix
1659        instead and uses the result as suggestion for autocompletion.
1660       
1661        If prefix is None autocompletion is not possible.
1662        """
1663        linkPath = _WikiLinkPath(link=link)
1664        if linkPath.isAbsolute():
1665            return linkPath.resolvePrefixSilenceAndWikiWordLink(None)
1666
1667        if basePage is None:
1668            return u"", 0, link  # TODO:  Better reaction?
1669       
1670        basePageName = basePage.getWikiWord()
1671        if basePageName is None:
1672            return u"", 0, link  # TODO:  Better reaction?
1673       
1674        return linkPath.resolvePrefixSilenceAndWikiWordLink(_WikiLinkPath(
1675                pageName=basePageName))
1676
1677
1678
1679    @staticmethod
1680    def parseTodoValue(todoValue, wikiDocument=None):
1681        """
1682        Parse a todo value (right of the colon) and return the node or
1683        return None if value couldn't be parsed
1684        """
1685        baseDict = _buildBaseDict(wikiDocument=wikiDocument)
1686        try:
1687            t = todoContent.parseString(todoValue, parseAll=True,
1688                    baseDict=baseDict)
1689            return t[0]
1690        except:
1691            return None
1692
1693
1694    @staticmethod
1695    def parseTodoEntry(entry, wikiDocument=None):
1696        """
1697        Parse a complete todo entry (without end-token) and return the node or
1698        return None if value couldn't be parsed
1699        """
1700        baseDict = _buildBaseDict(wikiDocument=wikiDocument)
1701        try:
1702            t = todoAsWhole.parseString(entry, parseAll=True,
1703                    baseDict=baseDict)
1704            return t[0]
1705        except:
1706            traceback.print_exc()
1707            return None
1708
1709
1710    @staticmethod
1711    def _createAutoLinkRelaxWordEntryRE(word):
1712        """
1713        Get compiled regular expression for one word in autoLink "relax"
1714        mode.
1715
1716        Not part of public API.
1717        """
1718        # Split into parts of contiguous alphanumeric characters
1719        parts = AutoLinkRelaxSplitRE.split(word)
1720        # Filter empty parts
1721        parts = [p for p in parts if p != u""]
1722
1723        # Instead of original non-alphanum characters allow arbitrary
1724        # non-alphanum characters
1725        pat = ur"\b" + (AutoLinkRelaxJoinPAT.join(parts)) + ur"\b"
1726        regex = re.compile(pat, AutoLinkRelaxJoinFlags)
1727
1728        return regex
1729
1730
1731    @staticmethod
1732    def buildAutoLinkRelaxInfo(wikiDocument):
1733        """
1734        Build some cache info needed to process auto-links in "relax" mode.
1735        This info will be given back in the formatDetails when calling
1736        _TheParser.parse().
1737        The implementation for this plugin creates a list of regular
1738        expressions and the related wiki words, but this is not mandatory.
1739        """
1740        # Build up regular expression
1741        # First fetch all wiki words
1742        words = wikiDocument.getWikiData().getAllProducedWikiLinks()
1743
1744        # Sort longest words first
1745        words.sort(key=lambda w: len(w), reverse=True)
1746       
1747        return [(_TheHelper._createAutoLinkRelaxWordEntryRE(w), w)
1748                for w in words if w != u""]
1749
1750
1751    @staticmethod
1752    def createWikiLinkPathObject(*args, **kwargs):
1753        return _WikiLinkPath(*args, **kwargs)
1754
1755
1756    @staticmethod
1757    def isAbsoluteLinkCore(linkCore):
1758        return _WikiLinkPath.isAbsoluteLinkCore(linkCore)
1759
1760
1761    @staticmethod
1762    def createLinkFromWikiWord(word, wikiPage, forceAbsolute=False):    # normalizeWikiWord
1763        """
1764        Create a link from word which should be put on wikiPage.
1765        """
1766        wikiDocument = wikiPage.getWikiDocument()
1767       
1768        targetPath = _WikiLinkPath(pageName=word)
1769
1770        if forceAbsolute:
1771            return BracketStart + targetPath.getLinkCore() + BracketEnd
1772
1773
1774#         basePath = _WikiLinkPath(wikiWord=wikiPage.getWikiWord())
1775
1776        linkCore = _TheHelper.createRelativeLinkFromWikiWord(
1777                word, wikiPage.getWikiWord(), downwardOnly=False)
1778               
1779        if _TheHelper.isCcWikiWord(word) and _TheHelper.isCcWikiWord(linkCore):
1780            wikiFormatDetails = wikiPage.getFormatDetails()
1781            if wikiFormatDetails.withCamelCase:
1782               
1783                ccBlacklist = wikiDocument.getCcWordBlacklist()
1784                if not word in ccBlacklist:
1785                    return linkCore
1786       
1787        return BracketStart + linkCore + BracketEnd
1788
1789
1790
1791    @staticmethod
1792    def createAbsoluteLinksFromWikiWords(words, wikiPage=None):
1793        """
1794        Create particularly stable links from a list of words which should be
1795        put on wikiPage.
1796        """
1797        return u"\n".join([u"%s//%s%s" % (BracketStart, w, BracketEnd)
1798                for w in words])
1799               
1800    # For compatibility. TODO: Remove
1801    createStableLinksFromWikiWords = createAbsoluteLinksFromWikiWords
1802
1803    @staticmethod
1804    def createRelativeLinkFromWikiWord(word, baseWord, downwardOnly=True):
1805        """
1806        Create a link to wikiword word relative to baseWord.
1807        If downwardOnly is False, the link may contain parts to go to parents
1808            or siblings
1809        in path (in this wiki language, ".." are used for this).
1810        If downwardOnly is True, the function may return None if a relative
1811        link can't be constructed.
1812        """
1813       
1814        relPath = _WikiLinkPath.getRelativePathByAbsPaths(_WikiLinkPath(
1815                pageName=word), _WikiLinkPath(pageName=baseWord),
1816                downwardOnly=downwardOnly)
1817       
1818        if relPath is None:
1819            return None
1820       
1821        return relPath.getLinkCore()
1822
1823
1824
1825    @staticmethod
1826    def createAttributeFromComponents(key, value, wikiPage=None):
1827        """
1828        Build an attribute from key and value.
1829        TODO: Check for necessary escaping
1830        """
1831        return u"%s%s: %s%s\n" % (BracketStart, key, value, BracketEnd)
1832
1833
1834    @staticmethod
1835    def isCcWikiWord(word):
1836        return bool(wikiWordCcRE.match(word))
1837
1838
1839    @staticmethod
1840    def findNextWordForSpellcheck(text, startPos, wikiPage):
1841        """
1842        Find in text next word to spellcheck, beginning at position startPos
1843       
1844        Returns tuple (start, end, spWord) which is either (None, None, None)
1845        if no more word can be found or returns start and after-end of the
1846        spWord to spellcheck.
1847       
1848        TODO: Move away because this is specific to human language,
1849            not wiki language.
1850        """
1851        while True:
1852            mat = TextWordRE.search(text, startPos)
1853            if mat is None:
1854                # No further word
1855                return (None, None, None)
1856
1857            if mat.group("negative") is not None:
1858                startPos = mat.end()
1859                continue
1860
1861            start, end = mat.span()
1862            spWord = mat.group()
1863
1864            return (start, end, spWord)
1865
1866
1867    @staticmethod
1868    def prepareAutoComplete(editor, text, charPos, lineStartCharPos,
1869            wikiDocument, docPage, settings):
1870        """
1871        Called when user wants autocompletion.
1872        text -- Whole text of page
1873        charPos -- Cursor position in characters
1874        lineStartCharPos -- For convenience and speed, position of the
1875                start of text line in which cursor is.
1876        wikiDocument -- wiki document object
1877        docPage -- DocPage object on which autocompletion is done
1878        closingBracket -- boolean iff a closing bracket should be suggested
1879                for bracket wikiwords and attributes
1880
1881        returns -- a list of tuples (sortKey, entry, backStepChars) where
1882            sortKey -- unistring to use for sorting entries alphabetically
1883                using right collator
1884            entry -- actual unistring entry to show and to insert if
1885                selected
1886            backStepChars -- numbers of chars to delete to the left of cursor
1887                before inserting entry
1888        """
1889        line = text[lineStartCharPos:charPos]
1890        rline = revStr(line)
1891        backStepMap = {}
1892        closingBracket = settings.get("closingBracket", False)
1893        builtinAttribs = settings.get("builtinAttribs", False)
1894
1895        # TODO Sort entries appropriately (whatever this means)
1896
1897        wikiData = wikiDocument.getWikiData()
1898        baseWordSegments = docPage.getWikiWord().split(u"/")
1899
1900        mat1 = RevWikiWordRE.match(rline)
1901        if mat1:
1902            # may be CamelCase word
1903            tofind = line[-mat1.end():]
1904            backstep = len(tofind)
1905            prefix, silence, tofind = _TheHelper.resolvePrefixSilenceAndWikiWordLink(
1906                    tofind, docPage)
1907           
1908            # We don't want prefixes here
1909            if prefix == u"":
1910                ccBlacklist = wikiDocument.getCcWordBlacklist()
1911                for word in wikiData.getWikiLinksStartingWith(tofind, True, True):
1912                    if not _TheHelper.isCcWikiWord(word[silence:]) or word in ccBlacklist:
1913                        continue
1914
1915                    backStepMap[word[silence:]] = backstep
1916
1917        mat2 = RevWikiWordRE2.match(rline)
1918        mat3 = RevAttributeValue.match(rline)
1919        if mat2:
1920            # may be not-CamelCase word or in an attribute name
1921            tofind = line[-mat2.end():]
1922
1923            # Should a closing bracket be appended to suggested words?
1924            if closingBracket:
1925                wordBracketEnd = BracketEnd
1926            else:
1927                wordBracketEnd = u""
1928           
1929            backstep = len(tofind)
1930
1931            prefix, silence, link = _TheHelper.resolvePrefixSilenceAndWikiWordLink(
1932                    tofind[len(BracketStart):], docPage)
1933           
1934            if prefix is not None:
1935                for word in wikiData.getWikiLinksStartingWith(
1936                        link, True, True):
1937                    backStepMap[BracketStart + prefix + word[silence:] +
1938                            wordBracketEnd] = backstep
1939
1940            for prop in wikiDocument.getAttributeNamesStartingWith(
1941                    tofind[len(BracketStart):], builtinAttribs):
1942                backStepMap[BracketStart + prop] = backstep
1943        elif mat3:
1944            # In an attribute value
1945            tofind = line[-mat3.end():]
1946            propkey = revStr(mat3.group(3))
1947            propfill = revStr(mat3.group(2))
1948            propvalpart = revStr(mat3.group(1))
1949            values = filter(lambda pv: pv.startswith(propvalpart),
1950                    wikiDocument.getDistinctAttributeValuesByKey(propkey,
1951                    builtinAttribs))
1952
1953            for v in values:
1954                backStepMap[BracketStart + propkey +
1955                        propfill + v + BracketEnd] = len(tofind)
1956
1957        mat = RevTodoKeyRE.match(rline)
1958        if mat:
1959            # Might be todo entry
1960            tofind = line[-mat.end():]
1961            for t in wikiData.getTodos():
1962                td = t[1]
1963                if not td.startswith(tofind):
1964                    continue
1965
1966#                 tdmat = ToDoREWithCapturing.match(td)
1967#                 key = tdmat.group(1) + u":"
1968                key = td + u":"
1969                backStepMap[key] = len(tofind)
1970
1971        mat = RevTodoValueRE.match(rline)
1972        if mat:
1973            # Might be todo entry
1974            tofind = line[-mat.end():]
1975            combinedTodos = [t[1] + ":" + t[2] for t in wikiData.getTodos()]
1976#             todos = [t[1] for t in wikiData.getTodos() if t[1].startswith(tofind)]
1977            todos = [t for t in combinedTodos if t.startswith(tofind)]
1978            for t in todos:
1979                backStepMap[t] = len(tofind)
1980
1981        mat = RevWikiWordAnchorRE2.match(rline)
1982        if mat:
1983            # In an anchor of a possible bracketed wiki word
1984            tofind = line[-mat.end():]
1985            wikiLinkCore = revStr(mat.group("wikiWord"))
1986            wikiWord = _TheHelper.resolvePrefixSilenceAndWikiWordLink(
1987                    wikiLinkCore, docPage)[2]
1988
1989            anchorBegin = revStr(mat.group("anchorBegin"))
1990
1991            try:
1992                page = wikiDocument.getWikiPage(wikiWord) # May throw exception
1993                anchors = [a for a in page.getAnchors()
1994                        if a.startswith(anchorBegin)]
1995
1996                for a in anchors:
1997                    backStepMap[BracketStart + wikiLinkCore +
1998                            BracketEnd +
1999                            WikiWordAnchorStart + a] = len(tofind)
2000            except WikiWordNotFoundException:
2001                # wikiWord isn't a wiki word
2002                pass
2003
2004        mat = RevWikiWordAnchorRE.match(rline)
2005        if mat:
2006            # In an anchor of a possible camel case word
2007            tofind = line[-mat.end():]
2008            wikiLinkCore = revStr(mat.group("wikiWord"))
2009            wikiWord = _TheHelper.resolvePrefixSilenceAndWikiWordLink(
2010                    wikiLinkCore, docPage)[2]
2011
2012            anchorBegin = revStr(mat.group("anchorBegin"))
2013
2014            try:
2015                page = wikiDocument.getWikiPage(wikiWord) # May throw exception
2016                anchors = [a for a in page.getAnchors()
2017                        if a.startswith(anchorBegin)]
2018                       
2019                for a in anchors:
2020                    backStepMap[wikiWord + wikiLinkCore +
2021                            a] = len(tofind)
2022            except WikiWordNotFoundException:
2023                # wikiWord isn't a wiki word
2024                pass
2025
2026
2027        acresult = backStepMap.keys()
2028       
2029        if len(acresult) > 0:
2030            # formatting.BracketEnd
2031            acresultTuples = []
2032            for r in acresult:
2033                if r.endswith(BracketEnd):
2034                    rc = r[: -len(BracketEnd)]
2035                else:
2036                    rc = r
2037                acresultTuples.append((rc, r, backStepMap[r]))
2038
2039            return acresultTuples
2040        else:
2041            return []
2042
2043
2044    @staticmethod
2045    def handleNewLineBeforeEditor(editor, text, charPos, lineStartCharPos,
2046            wikiDocument, settings):
2047        """
2048        Processes pressing of a newline in editor before editor processes it.
2049        Returns True iff the actual newline should be processed by
2050            editor yet.
2051        """
2052        # autoIndent, autoBullet, autoUnbullet
2053       
2054        line = text[lineStartCharPos:charPos]
2055
2056        if settings.get("autoUnbullet", False):
2057            # Check for lonely bullet or number
2058            mat = BulletRE.match(line)
2059            if mat and mat.end(0) == len(line):
2060                editor.SetSelectionByCharPos(lineStartCharPos, charPos)
2061                editor.ReplaceSelection(mat.group("indentBullet"))
2062                return False
2063
2064            mat = NumericSimpleBulletRE.match(line)
2065            if mat and mat.end(0) == len(line):
2066                editor.SetSelectionByCharPos(lineStartCharPos, charPos)
2067                editor.ReplaceSelection(mat.group("indentBullet"))
2068                return False
2069
2070            mat = NumericBulletRE.match(line)
2071            if mat and mat.end(0) == len(line):
2072                replacement = mat.group("indentNumeric")
2073                if mat.group("preLastNumeric") != u"":
2074                    replacement += mat.group("preLastNumeric") + u" "
2075
2076                editor.SetSelectionByCharPos(lineStartCharPos, charPos)
2077                editor.ReplaceSelection(replacement)
2078                return False
2079       
2080        return True
2081
2082
2083    @staticmethod
2084    def handleNewLineAfterEditor(editor, text, charPos, lineStartCharPos,
2085            wikiDocument, settings):
2086        """
2087        Processes pressing of a newline after editor processed it (if
2088        handleNewLineBeforeEditor returned True).
2089        """
2090        # autoIndent, autoBullet, autoUnbullet
2091
2092        currentLine = editor.GetCurrentLine()
2093
2094        if currentLine > 0:
2095            previousLine = editor.GetLine(currentLine - 1)
2096            indent = _RE_LINE_INDENT.match(previousLine).group(0)
2097   
2098            # check if the prev level was a bullet level
2099            if settings.get("autoBullets", False):
2100                match = BulletRE.match(previousLine)
2101                if match:
2102                    editor.AddText(indent + match.group("actualBullet"))
2103                    return
2104
2105                match = NumericSimpleBulletRE.match(previousLine)
2106                if match:
2107                    editor.AddText(indent + match.group("actualBullet"))
2108                    return False
2109
2110                match = NumericBulletRE.search(previousLine)
2111                if match:
2112                    prevNumStr = match.group(3)
2113                    prevNum = int(prevNumStr)
2114                    nextNum = prevNum+1
2115#                     adjustment = len(str(nextNum)) - len(prevNumStr)
2116#                     if adjustment == 0:
2117                    editor.AddText(u"%s%s%d. " % (indent,
2118                            match.group(2), int(prevNum)+1))
2119#                     else:
2120#                         editor.AddText(u"%s%s%d. " % (u" " *
2121#                                 (editor.GetLineIndentation(currentLine - 1) - adjustment),
2122#                                 match.group(2), int(prevNum)+1))
2123                    return
2124
2125            if settings.get("autoIndent", False):
2126                editor.AddText(indent)
2127                return
2128
2129
2130    @staticmethod
2131    def handleRewrapText(editor, settings):
2132        curPos = editor.GetCurrentPos()
2133
2134        # search back for start of the para
2135        curLineNum = editor.GetCurrentLine()
2136        curLine = editor.GetLine(curLineNum)
2137        while curLineNum > 0:
2138            # don't wrap previous bullets with this bullet
2139            if (BulletRE.match(curLine) or NumericBulletRE.match(curLine)):
2140                break
2141
2142            if EmptyLineRE.match(curLine):
2143                curLineNum = curLineNum + 1
2144                break
2145
2146            curLineNum = curLineNum - 1
2147            curLine = editor.GetLine(curLineNum)
2148        startLine = curLineNum
2149
2150        # search forward for end of the para
2151        curLineNum = editor.GetCurrentLine()
2152        curLine = editor.GetLine(curLineNum)
2153        while curLineNum <= editor.GetLineCount():
2154            # don't wrap the next bullet with this bullet
2155            if curLineNum > startLine:
2156                if (BulletRE.match(curLine) or NumericBulletRE.match(curLine)):
2157                    curLineNum = curLineNum - 1
2158                    break
2159
2160            if EmptyLineRE.match(curLine):
2161                curLineNum = curLineNum - 1
2162                break
2163
2164            curLineNum = curLineNum + 1
2165            curLine = editor.GetLine(curLineNum)
2166        endLine = curLineNum
2167       
2168        if (startLine <= endLine):
2169            # get the start and end of the lines
2170            startPos = editor.PositionFromLine(startLine)
2171            endPos = editor.GetLineEndPosition(endLine)
2172
2173            # get the indentation for rewrapping
2174            indent = _RE_LINE_INDENT.match(editor.GetLine(startLine)).group(0)
2175            subIndent = indent
2176
2177            # if the start of the para is a bullet the subIndent has to change
2178            if BulletRE.match(editor.GetLine(startLine)):
2179                subIndent = indent + u"  "
2180            else:
2181                match = NumericBulletRE.match(editor.GetLine(startLine))
2182                if match:
2183                    subIndent = indent + u" " * (len(match.group(2)) + 2)
2184
2185            # get the text that will be wrapped
2186            text = editor.GetTextRange(startPos, endPos)
2187            # remove spaces, newlines, etc
2188            text = re.sub("[\s\r\n]+", " ", text)
2189
2190            # wrap the text
2191            wrapPosition = 70
2192            try:
2193                wrapPosition = int(
2194                        editor.getLoadedDocPage().getAttributeOrGlobal(
2195                        "wrap", "70"))
2196            except:
2197                pass
2198
2199            # make the min wrapPosition 5
2200            if wrapPosition < 5:
2201                wrapPosition = 5
2202
2203            filledText = fill(text, width=wrapPosition,
2204                    initial_indent=indent,
2205                    subsequent_indent=subIndent)
2206
2207            # replace the text based on targetting
2208            editor.SetTargetStart(startPos)
2209            editor.SetTargetEnd(endPos)
2210            editor.ReplaceTarget(filledText)
2211            editor.GotoPos(curPos)
2212
2213
2214    @staticmethod
2215    def getNewDefaultWikiSettingsPage(mainControl):
2216        """
2217        Return default text of the "WikiSettings" page for a new wiki.
2218        """
2219        return _(u"""++ Wiki Settings
2220
2221These are your default global settings.
2222
2223[global.importance.low.color: grey]
2224[global.importance.high.bold: true]
2225[global.contact.icon: contact]
2226[global.wrap: 70]
2227
2228[icon: cog]
2229""")  # TODO Localize differently?
2230
2231
2232    @staticmethod
2233    def createWikiLanguageDetails(wikiDocument, docPage):
2234        """
2235        Returns a new WikiLanguageDetails object based on current configuration
2236        """
2237        return WikiLanguageDetails(wikiDocument, docPage)
2238
2239
2240THE_LANGUAGE_HELPER = _TheHelper()
2241
2242
2243
2244def describeWikiLanguage(ver, app):
2245    """
2246    API function for "WikiParser" plugins
2247    Returns a sequence of tuples describing the supported
2248    insertion keys. Each tuple has the form (intLanguageName, hrLanguageName,
2249            parserFactory, parserIsThreadsafe, editHelperFactory,
2250            editHelperIsThreadsafe)
2251    Where the items mean:
2252        intLanguageName -- internal unique name (should be ascii only) to
2253            identify wiki language processed by parser
2254        hrLanguageName -- human readable language name, unistring
2255            (TODO: localization)
2256        parserFactory -- factory function to create parser object(s) fulfilling
2257
2258        parserIsThreadsafe -- boolean if parser is threadsafe. If not this
2259            will currently lead to a very inefficient operation
2260        processHelperFactory -- factory for helper object containing further
2261            functions needed for editing, tree presentation and so on.
2262        editHelperIsThreadsafe -- boolean if edit helper functions are
2263            threadsafe.
2264
2265    Parameters:
2266
2267    ver -- API version (can only be 1 currently)
2268    app -- wxApp object
2269    """
2270
2271    return (("wikidpad_default_2_0", u"WikidPad default 2.0", parserFactory,
2272             True, languageHelperFactory, True),)
2273
2274
2275
2276
2277def parserFactory(intLanguageName, debugMode):
2278    """
2279    Builds up a parser object. If the parser is threadsafe this function is
2280    allowed to return the same object multiple times (currently it should do
2281    so for efficiency).
2282    For seldom needed parsers it is recommended to put the actual parser
2283    construction as singleton in this function to reduce startup time of WikidPad.
2284    For non-threadsafe parsers it is required to create one inside this
2285    function at each call.
2286
2287    intLanguageName -- internal unique name (should be ascii only) to
2288        identify wiki language to process by parser
2289    """
2290    if text.getDebug() != debugMode:
2291        text.setDebugRecurs(debugMode)
2292
2293    return THE_PARSER
2294
2295
2296def languageHelperFactory(intLanguageName, debugMode):
2297    """
2298    Builds up a language helper object. If the object is threadsafe this function is
2299    allowed to return the same object multiple times (currently it should do
2300    so for efficiency).
2301
2302    intLanguageName -- internal unique name (should be ascii only) to
2303        identify wiki language to process by helper
2304    """
2305    return THE_LANGUAGE_HELPER
2306
Note: See TracBrowser for help on using the browser.