From 28cce8b2697eb22847ebf4a4be8e02e4d5a583df Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 3 Dec 2011 16:13:35 +0000 Subject: [PATCH] Brought in the TiddlyWiki wikifier code This is pretty much the raw TiddlyWiki code. Formatter.js incorporates FormatterHelper.js, too. --- js/Formatter.js | 625 ++++++++++++++++++++++++++++++++++++++++++++++++ js/Wikifier.js | 211 ++++++++++++++++ 2 files changed, 836 insertions(+) create mode 100755 js/Formatter.js create mode 100755 js/Wikifier.js diff --git a/js/Formatter.js b/js/Formatter.js new file mode 100755 index 000000000..f1d6e29b6 --- /dev/null +++ b/js/Formatter.js @@ -0,0 +1,625 @@ +/*global require: false, exports: false, process: false */ +"use strict"; + +var Tiddler = require("./Tiddler.js").Tiddler, + TiddlyWiki = require("./TiddlyWiki.js").TiddlyWiki, + utils = require("./Utils.js"), + util = require("util"); + + +function Formatter(formatters) +{ + var n; + this.formatters = []; + var pattern = []; + for(n=0; n 1) { + last.element.setAttribute("colspan",colSpanCount); + last.element.setAttribute("colSpan",colSpanCount); // Needed for IE + colSpanCount = 1; + } + } + w.nextMatch = this.cellRegExp.lastIndex-1; + } else if(cellMatch[1] == ">") { + // Colspan + colSpanCount++; + w.nextMatch = this.cellRegExp.lastIndex-1; + } else if(cellMatch[2]) { + // End of row + if(prevCell && colSpanCount > 1) { + prevCell.setAttribute("colspan",colSpanCount); + prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE + } + w.nextMatch = this.cellRegExp.lastIndex; + break; + } else { + // Cell + w.nextMatch++; + var styles = config.formatterHelpers.inlineCssHelper(w); + var spaceLeft = false; + var chr = w.source.substr(w.nextMatch,1); + while(chr == " ") { + spaceLeft = true; + w.nextMatch++; + chr = w.source.substr(w.nextMatch,1); + } + var cell; + if(chr == "!") { + cell = createTiddlyElement(e,"th"); + w.nextMatch++; + } else { + cell = createTiddlyElement(e,"td"); + } + prevCell = cell; + prevColumns[col] = {rowSpanCount:1,element:cell}; + if(colSpanCount > 1) { + cell.setAttribute("colspan",colSpanCount); + cell.setAttribute("colSpan",colSpanCount); // Needed for IE + colSpanCount = 1; + } + config.formatterHelpers.applyCssHelper(cell,styles); + w.subWikifyTerm(cell,this.cellTermRegExp); + if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight + cell.align = spaceLeft ? "center" : "left"; + else if(spaceLeft) + cell.align = "right"; + w.nextMatch--; + } + col++; + this.cellRegExp.lastIndex = w.nextMatch; + cellMatch = this.cellRegExp.exec(w.source); + } + } +}, + +{ + name: "heading", + match: "^!{1,6}", + termRegExp: /(\n)/mg, + handler: function(w) + { + w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp); + } +}, + +{ + name: "list", + match: "^(?:[\\*#;:]+)", + lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg, + termRegExp: /(\n)/mg, + handler: function(w) + { + var stack = [w.output]; + var currLevel = 0, currType = null; + var listLevel, listType, itemType, baseType; + w.nextMatch = w.matchStart; + this.lookaheadRegExp.lastIndex = w.nextMatch; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) { + if(lookaheadMatch[1]) { + listType = "ul"; + itemType = "li"; + } else if(lookaheadMatch[2]) { + listType = "ol"; + itemType = "li"; + } else if(lookaheadMatch[3]) { + listType = "dl"; + itemType = "dt"; + } else if(lookaheadMatch[4]) { + listType = "dl"; + itemType = "dd"; + } + if(!baseType) + baseType = listType; + listLevel = lookaheadMatch[0].length; + w.nextMatch += lookaheadMatch[0].length; + var t; + if(listLevel > currLevel) { + for(t=currLevel; tlistLevel; t--) + stack.pop(); + } else if(listLevel == currLevel && listType != currType) { + stack.pop(); + stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType)); + } + currLevel = listLevel; + currType = listType; + var e = createTiddlyElement(stack[stack.length-1],itemType); + w.subWikifyTerm(e,this.termRegExp); + this.lookaheadRegExp.lastIndex = w.nextMatch; + lookaheadMatch = this.lookaheadRegExp.exec(w.source); + } + } +}, + +{ + name: "quoteByBlock", + match: "^<<<\\n", + termRegExp: /(^<<<(\n|$))/mg, + element: "blockquote", + handler: config.formatterHelpers.createElementAndWikify +}, + +{ + name: "quoteByLine", + match: "^>+", + lookaheadRegExp: /^>+/mg, + termRegExp: /(\n)/mg, + element: "blockquote", + handler: function(w) + { + var stack = [w.output]; + var currLevel = 0; + var newLevel = w.matchLength; + var t,matched; + do { + if(newLevel > currLevel) { + for(t=currLevel; tnewLevel; t--) + stack.pop(); + } + currLevel = newLevel; + w.subWikifyTerm(stack[stack.length-1],this.termRegExp); + createTiddlyElement(stack[stack.length-1],"br"); + this.lookaheadRegExp.lastIndex = w.nextMatch; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch; + if(matched) { + newLevel = lookaheadMatch[0].length; + w.nextMatch += lookaheadMatch[0].length; + } + } while(matched); + } +}, + +{ + name: "rule", + match: "^----+$\\n?|
\\n?", + handler: function(w) + { + createTiddlyElement(w.output,"hr"); + } +}, + +{ + name: "monospacedByLine", + match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|)\\n", + element: "pre", + handler: function(w) + { + switch(w.matchText) { + case "/*{{{*/\n": // CSS + this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\*\}\}\}\*\/$\n?)/mg; + break; + case "{{{\n": // monospaced block + this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\f*\}\}\}$\n?)/mg; + break; + case "//{{{\n": // plugin + this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\/\}\}\}$\n?)/mg; + break; + case "\n": //template + this.lookaheadRegExp = /\n*((?:^[^\n]*\n)+?)(\n*^\f*$\n?)/mg; + break; + default: + break; + } + config.formatterHelpers.enclosedTextHelper.call(this,w); + } +}, + +{ + name: "wikifyComment", + match: "^(?:/\\*\\*\\*|\n)/mg); + w.subWikifyTerm(w.output,termRegExp); + } +}, + +{ + name: "macro", + match: "<<", + lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) { + w.nextMatch = this.lookaheadRegExp.lastIndex; + invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler); + } + } +}, + +{ + name: "prettyLink", + match: "\\[\\[", + lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { + var e; + var text = lookaheadMatch[1]; + if(lookaheadMatch[3]) { + // Pretty bracketted link + var link = lookaheadMatch[3]; + e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ? + createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler); + } else { + // Simple bracketted link + e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler); + } + createTiddlyText(e,text); + w.nextMatch = this.lookaheadRegExp.lastIndex; + } + } +}, + +{ + name: "wikiLink", + match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink, + handler: function(w) + { + if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) { + w.outputText(w.output,w.matchStart+1,w.nextMatch); + return; + } + if(w.matchStart > 0) { + var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg"); + preRegExp.lastIndex = w.matchStart-1; + var preMatch = preRegExp.exec(w.source); + if(preMatch.index == w.matchStart-1) { + w.outputText(w.output,w.matchStart,w.nextMatch); + return; + } + } + if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) { + var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler); + w.outputText(link,w.matchStart,w.nextMatch); + } else { + w.outputText(w.output,w.matchStart,w.nextMatch); + } + } +}, + +{ + name: "urlLink", + match: config.textPrimitives.urlPattern, + handler: function(w) + { + w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch); + } +}, + +{ + name: "image", + match: "\\[[<>]?[Ii][Mm][Gg]\\[", + //# [<] sequence below is to avoid lessThan-questionMark sequence so TiddlyWikis can be included in PHP files + lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { + var e = w.output; + if(lookaheadMatch[5]) { + var link = lookaheadMatch[5]; + e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler); + jQuery(e).addClass("imageLink"); + } + var img = createTiddlyElement(e,"img"); + if(lookaheadMatch[1]) + img.align = "left"; + else if(lookaheadMatch[2]) + img.align = "right"; + if(lookaheadMatch[3]) { + img.title = lookaheadMatch[3]; + img.setAttribute("alt",lookaheadMatch[3]); + } + img.src = lookaheadMatch[4]; + w.nextMatch = this.lookaheadRegExp.lastIndex; + } + } +}, + +{ + name: "html", + match: "<[Hh][Tt][Mm][Ll]>", + lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { + createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1]; + w.nextMatch = this.lookaheadRegExp.lastIndex; + } + } +}, + +{ + name: "commentByBlock", + match: "/%", + lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) + w.nextMatch = this.lookaheadRegExp.lastIndex; + } +}, + +{ + name: "characterFormat", + match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{", + handler: function(w) + { + switch(w.matchText) { + case "''": + w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg); + break; + case "//": + w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg); + break; + case "__": + w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg); + break; + case "^^": + w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg); + break; + case "~~": + w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg); + break; + case "--": + w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg); + break; + case "{{{": + var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg; + lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { + createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]); + w.nextMatch = lookaheadRegExp.lastIndex; + } + break; + } + } +}, + +{ + name: "customFormat", + match: "@@|\\{\\{", + handler: function(w) + { + switch(w.matchText) { + case "@@": + var e = createTiddlyElement(w.output,"span"); + var styles = config.formatterHelpers.inlineCssHelper(w); + if(styles.length == 0) + e.className = "marked"; + else + config.formatterHelpers.applyCssHelper(e,styles); + w.subWikifyTerm(e,/(@@)/mg); + break; + case "{{": + var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg; + lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = lookaheadRegExp.exec(w.source); + if(lookaheadMatch) { + w.nextMatch = lookaheadRegExp.lastIndex; + e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]); + w.subWikifyTerm(e,/(\}\}\})/mg); + } + break; + } + } +}, + +{ + name: "mdash", + match: "--", + handler: function(w) + { + createTiddlyElement(w.output,"span").innerHTML = "—"; + } +}, + +{ + name: "lineBreak", + match: "\\n|
", + handler: function(w) + { + createTiddlyElement(w.output,"br"); + } +}, + +{ + name: "rawText", + match: "\"{3}|", + lookaheadRegExp: /(?:\"{3}|)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg, + handler: function(w) + { + this.lookaheadRegExp.lastIndex = w.matchStart; + var lookaheadMatch = this.lookaheadRegExp.exec(w.source); + if(lookaheadMatch && lookaheadMatch.index == w.matchStart) { + createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]); + w.nextMatch = this.lookaheadRegExp.lastIndex; + } + } +}, + +{ + name: "htmlEntitiesEncoding", + match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)", + handler: function(w) + { + createTiddlyElement(w.output,"span").innerHTML = w.matchText; + } +} + +]; + +exports.Formatter = Formatter; diff --git a/js/Wikifier.js b/js/Wikifier.js new file mode 100755 index 000000000..45d10bdd0 --- /dev/null +++ b/js/Wikifier.js @@ -0,0 +1,211 @@ +/*global require: false, exports: false, process: false */ +"use strict"; + +var Tiddler = require("./Tiddler.js").Tiddler, + TiddlyWiki = require("./TiddlyWiki.js").TiddlyWiki, + utils = require("./Utils.js"), + util = require("util"); + +//# Construct a wikifier object +//# source - source string that's going to be wikified +//# formatter - Formatter() object containing the list of formatters to be used +//# highlightRegExp - regular expression of the text string to highlight +//# tiddler - reference to the tiddler that's taken to be the container for this wikification +var Wikifier = function(source,formatter,highlightRegExp,tiddler) +{ + this.source = source; + this.output = null; + this.formatter = formatter; + this.nextMatch = 0; + this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true; + this.highlightRegExp = highlightRegExp; + this.highlightMatch = null; + this.isStatic = false; + if(highlightRegExp) { + highlightRegExp.lastIndex = 0; + this.highlightMatch = highlightRegExp.exec(source); + } + this.tiddler = tiddler; +} + +Wikifier.prototype.wikifyPlain = function() +{ + var e = createTiddlyElement(document.body,"div"); + e.style.display = "none"; + this.subWikify(e); + var text = jQuery(e).text(); + jQuery(e).remove(); + return text; +}; + +Wikifier.prototype.subWikify = function(output,terminator) +{ + //# Handle the terminated and unterminated cases separately, this speeds up wikifikation by about 30% + try { + if(terminator) + this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg")); + else + this.subWikifyUnterm(output); + } catch(ex) { + showException(ex); + } +}; + +Wikifier.prototype.subWikifyUnterm = function(output) +{ + //# subWikify can be indirectly recursive, so we need to save the old output pointer + var oldOutput = this.output; + this.output = output; + //# Get the first match + this.formatter.formatterRegExp.lastIndex = this.nextMatch; + var formatterMatch = this.formatter.formatterRegExp.exec(this.source); + while(formatterMatch) { + // Output any text before the match + if(formatterMatch.index > this.nextMatch) + this.outputText(this.output,this.nextMatch,formatterMatch.index); + // Set the match parameters for the handler + this.matchStart = formatterMatch.index; + this.matchLength = formatterMatch[0].length; + this.matchText = formatterMatch[0]; + this.nextMatch = this.formatter.formatterRegExp.lastIndex; + //# Figure out which formatter matched and call its handler + var t; + for(t=1; t this.nextMatch) + this.outputText(this.output,this.nextMatch,terminatorMatch.index); + //# Set the match parameters + this.matchText = terminatorMatch[1]; + this.matchLength = terminatorMatch[1].length; + this.matchStart = terminatorMatch.index; + this.nextMatch = this.matchStart + this.matchLength; + //# Restore the output pointer + this.output = oldOutput; + return; + } + //# It must be a formatter match; output any text before the match + if(formatterMatch.index > this.nextMatch) + this.outputText(this.output,this.nextMatch,formatterMatch.index); + //# Set the match parameters + this.matchStart = formatterMatch.index; + this.matchLength = formatterMatch[0].length; + this.matchText = formatterMatch[0]; + this.nextMatch = this.formatter.formatterRegExp.lastIndex; + //# Figure out which formatter matched and call its handler + var t; + for(t=1; t startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) { + //# Deal with any plain text before the highlight + if(this.highlightMatch.index > startPos) { + createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index)); + startPos = this.highlightMatch.index; + } + //# Deal with the highlight + var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos); + createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd)); + startPos = highlightEnd; + //# Nudge along to the next highlight if we're done with this one + if(startPos >= this.highlightRegExp.lastIndex) + this.highlightMatch = this.highlightRegExp.exec(this.source); + } + //# Do the unhighlighted text left over + if(startPos < endPos) { + createTiddlyText(place,this.source.substring(startPos,endPos)); + } +}; + +Wikifier.wikify = function(source,output,highlightRegExp,tiddler) +{ + if(source) { + var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler); + var t0 = new Date(); + wikifier.subWikify(output); + if(tiddler && config.options.chkDisplayInstrumentation) + displayMessage("wikify:" +tiddler.title+ " in " + (new Date()-t0) + " ms"); + } +}; + +Wikifier.wikifyStatic = function(source,highlightRegExp,tiddler,format) +{ + var e = createTiddlyElement(document.body,"pre"); + e.style.display = "none"; + var html = ""; + if(source && source != "") { + if(!tiddler) + tiddler = new Tiddler("temp"); + var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler); + wikifier.isStatic = true; + wikifier.subWikify(e); + html = e.innerHTML; + jQuery(e).remove(); + } + return html; +}; + +//# Wikify a string to plain text +//# text - text to wikify +//# limit - maximum number of characters to generate +//# tiddler - optional reference to the tiddler containing this text +Wikifier.wikifyPlainText = function(text,limit,tiddler) +{ + if(limit > 0) + text = text.substr(0,limit); + var wikifier = new Wikifier(text,formatter,null,tiddler); + return wikifier.wikifyPlain(); +}; + +exports.Wikifier = Wikifier;