diff options
Diffstat (limited to 'js/lib/inlines.js')
-rw-r--r-- | js/lib/inlines.js | 257 |
1 files changed, 121 insertions, 136 deletions
diff --git a/js/lib/inlines.js b/js/lib/inlines.js index ef7a00f..23b2b1e 100644 --- a/js/lib/inlines.js +++ b/js/lib/inlines.js @@ -85,6 +85,12 @@ var normalizeReference = function(s) { .toUpperCase(); }; +var text = function(s) { + var node = new Node('Text'); + node.c = s; + return node; +} + // INLINE PARSER // These are methods of an InlineParser object, defined below. @@ -123,9 +129,9 @@ var spnl = function() { // in the subject. If they succeed in matching anything, they // return the inline matched, advancing the subject. -// Attempt to parse backticks, returning either a backtick code span or a +// Attempt to parse backticks, adding either a backtick code span or a // literal sequence of backticks. -var parseBackticks = function(inlines) { +var parseBackticks = function(block) { var ticks = this.match(/^`+/); if (!ticks) { return 0; @@ -133,37 +139,42 @@ var parseBackticks = function(inlines) { var afterOpenTicks = this.pos; var foundCode = false; var matched; + var node; while (!foundCode && (matched = this.match(/`+/m))) { if (matched === ticks) { - inlines.push({ t: 'Code', c: this.subject.slice(afterOpenTicks, - this.pos - ticks.length) - .replace(/[ \n]+/g, ' ') - .trim() }); + node = new Node('Code'); + node.c = this.subject.slice(afterOpenTicks, + this.pos - ticks.length) + .replace(/[ \n]+/g, ' ') + .trim(); + block.appendChild(node); return true; } } // If we got here, we didn't match a closing backtick sequence. this.pos = afterOpenTicks; - inlines.push({ t: 'Text', c: ticks }); + block.appendChild(text(ticks)); return true; }; // Parse a backslash-escaped special character, adding either the escaped // character, a hard line break (if the backslash is followed by a newline), -// or a literal backslash to the 'inlines' list. -var parseBackslash = function(inlines) { +// or a literal backslash to the block's children. +var parseBackslash = function(block) { var subj = this.subject, pos = this.pos; + var node; if (subj.charCodeAt(pos) === C_BACKSLASH) { if (subj.charAt(pos + 1) === '\n') { this.pos = this.pos + 2; - inlines.push({ t: 'Hardbreak' }); + node = new Node('Hardbreak'); + block.appendChild(node); } else if (reEscapable.test(subj.charAt(pos + 1))) { this.pos = this.pos + 2; - inlines.push({ t: 'Text', c: subj.charAt(pos + 1) }); + block.appendChild(text(subj.charAt(pos + 1))); } else { this.pos++; - inlines.push({t: 'Text', c: '\\'}); + block.appendChild(text('\\')); } return true; } else { @@ -172,22 +183,23 @@ var parseBackslash = function(inlines) { }; // Attempt to parse an autolink (URL or email in pointy brackets). -var parseAutolink = function(inlines) { +var parseAutolink = function(block) { var m; var dest; + var node; if ((m = this.match(/^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/))) { // email autolink dest = m.slice(1, -1); - inlines.push( - {t: 'Link', - children: [{ t: 'Text', c: dest }], - destination: 'mailto:' + encodeURI(unescape(dest)) }); + node = new Node('Link'); + node.destination = 'mailto:' + encodeURI(unescape(dest)); + node.appendChild(text(dest)); + block.appendChild(node); return true; } else if ((m = this.match(/^<(?:coap|doi|javascript|aaa|aaas|about|acap|cap|cid|crid|data|dav|dict|dns|file|ftp|geo|go|gopher|h323|http|https|iax|icap|im|imap|info|ipp|iris|iris.beep|iris.xpc|iris.xpcs|iris.lwz|ldap|mailto|mid|msrp|msrps|mtqp|mupdate|news|nfs|ni|nih|nntp|opaquelocktoken|pop|pres|rtsp|service|session|shttp|sieve|sip|sips|sms|snmp|soap.beep|soap.beeps|tag|tel|telnet|tftp|thismessage|tn3270|tip|tv|urn|vemmi|ws|wss|xcon|xcon-userid|xmlrpc.beep|xmlrpc.beeps|xmpp|z39.50r|z39.50s|adiumxtra|afp|afs|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|chrome|chrome-extension|com-eventbrite-attendee|content|cvs|dlna-playsingle|dlna-playcontainer|dtn|dvb|ed2k|facetime|feed|finger|fish|gg|git|gizmoproject|gtalk|hcp|icon|ipn|irc|irc6|ircs|itms|jar|jms|keyparc|lastfm|ldaps|magnet|maps|market|message|mms|ms-help|msnim|mumble|mvn|notes|oid|palm|paparazzi|platform|proxy|psyc|query|res|resource|rmi|rsync|rtmp|secondlife|sftp|sgn|skype|smb|soldat|spotify|ssh|steam|svn|teamspeak|things|udp|unreal|ut2004|ventrilo|view-source|webcal|wtai|wyciwyg|xfire|xri|ymsgr):[^<>\x00-\x20]*>/i))) { dest = m.slice(1, -1); - inlines.push({ - t: 'Link', - children: [{ t: 'Text', c: dest }], - destination: encodeURI(unescape(dest)) }); + node = new Node('Link'); + node.destination = encodeURI(unescape(dest)); + node.appendChild(text(dest)); + block.appendChild(node); return true; } else { return false; @@ -195,10 +207,13 @@ var parseAutolink = function(inlines) { }; // Attempt to parse a raw HTML tag. -var parseHtmlTag = function(inlines) { +var parseHtmlTag = function(block) { var m = this.match(reHtmlTag); + var node; if (m) { - inlines.push({ t: 'Html', c: m }); + node = new Node('Html'); + node.c = m; + block.appendChild(node); return true; } else { return false; @@ -247,20 +262,8 @@ var scanDelims = function(cc) { can_close: can_close }; }; -var Emph = function(ils) { - return {t: 'Emph', children: ils}; -}; - -var Strong = function(ils) { - return {t: 'Strong', children: ils}; -}; - -var Str = function(s) { - return {t: 'Text', c: s}; -}; - // Attempt to parse emphasis or strong emphasis. -var parseEmphasis = function(cc, inlines) { +var parseEmphasis = function(cc, block) { var res = this.scanDelims(cc); var numdelims = res.numdelims; @@ -271,12 +274,13 @@ var parseEmphasis = function(cc, inlines) { } this.pos += numdelims; - inlines.push(Str(this.subject.slice(startpos, this.pos))); + var node = text(this.subject.slice(startpos, this.pos)); + block.appendChild(node); // Add entry to stack for this opener this.delimiters = { cc: cc, numdelims: numdelims, - pos: inlines.length - 1, + node: node, previous: this.delimiters, next: null, can_open: res.can_open, @@ -302,28 +306,14 @@ var removeDelimiter = function(delim) { } }; -var removeGaps = function(inlines) { - // remove gaps from inlines - var i, j; - j = 0; - for (i = 0 ; i < inlines.length; i++) { - if (inlines[i] !== null) { - inlines[j] = inlines[i]; - j++; - } - } - inlines.splice(j); -}; - -var processEmphasis = function(inlines, stack_bottom) { +var processEmphasis = function(block, stack_bottom) { "use strict"; var opener, closer; var opener_inl, closer_inl; var nextstack, tempstack; var use_delims; var contents; - var emph; - var i; + var tmp, next; // find first closer above stack_bottom: closer = this.delimiters; @@ -350,8 +340,8 @@ var processEmphasis = function(inlines, stack_bottom) { use_delims = closer.numdelims % 2 === 0 ? 2 : 1; } - opener_inl = inlines[opener.pos]; - closer_inl = inlines[closer.pos]; + opener_inl = opener.node; + closer_inl = closer.node; // remove used delimiters from stack elts and inlines opener.numdelims -= use_delims; @@ -360,17 +350,18 @@ var processEmphasis = function(inlines, stack_bottom) { closer_inl.c = closer_inl.c.slice(0, closer_inl.c.length - use_delims); // build contents for new emph element - contents = inlines.slice(opener.pos + 1, closer.pos); - removeGaps(contents); - - emph = use_delims === 1 ? Emph(contents) : Strong(contents); - - // insert into list of inlines - inlines[opener.pos + 1] = emph; - for (i = opener.pos + 2; i < closer.pos; i++) { - inlines[i] = null; + var emph = new Node(use_delims === 1 ? 'Emph' : 'Strong'); + + tmp = opener_inl.next; + while (tmp && tmp !== closer_inl) { + next = tmp.next; + tmp.unlink(); + emph.appendChild(tmp); + tmp = next; } + opener_inl.insertAfter(emph); + // remove elts btw opener and closer in delimiters stack tempstack = closer.previous; while (tempstack !== null && tempstack !== opener) { @@ -381,18 +372,17 @@ var processEmphasis = function(inlines, stack_bottom) { // if opener has 0 delims, remove it and the inline if (opener.numdelims === 0) { - inlines[opener.pos] = null; + opener_inl.unlink(); this.removeDelimiter(opener); } if (closer.numdelims === 0) { - inlines[closer.pos] = null; + closer_inl.unlink(); tempstack = closer.next; this.removeDelimiter(closer); closer = tempstack; } - } else { closer = closer.next; } @@ -403,8 +393,6 @@ var processEmphasis = function(inlines, stack_bottom) { } - removeGaps(inlines); - // remove all delimiters while (this.delimiters !== stack_bottom) { this.removeDelimiter(this.delimiters); @@ -445,18 +433,19 @@ var parseLinkLabel = function() { return m === null ? 0 : m.length; }; -// Add open bracket to delimiter stack and add a Str to inlines. -var parseOpenBracket = function(inlines) { +// Add open bracket to delimiter stack and add a text node to block's children. +var parseOpenBracket = function(block) { var startpos = this.pos; this.pos += 1; - inlines.push(Str("[")); + var node = text('['); + block.appendChild(node); // Add entry to stack for this opener this.delimiters = { cc: C_OPEN_BRACKET, numdelims: 1, - pos: inlines.length - 1, + node: node, previous: this.delimiters, next: null, can_open: true, @@ -472,19 +461,21 @@ var parseOpenBracket = function(inlines) { }; // IF next character is [, and ! delimiter to delimiter stack and -// add a Str to inlines. Otherwise just add a Str. -var parseBang = function(inlines) { +// add a text node to block's children. Otherwise just add a text node. +var parseBang = function(block) { var startpos = this.pos; this.pos += 1; if (this.peek() === C_OPEN_BRACKET) { this.pos += 1; - inlines.push(Str("![")); + + var node = text('!['); + block.appendChild(node); // Add entry to stack for this opener this.delimiters = { cc: C_BANG, numdelims: 1, - pos: inlines.length - 1, + node: node, previous: this.delimiters, next: null, can_open: true, @@ -495,22 +486,21 @@ var parseBang = function(inlines) { this.delimiters.previous.next = this.delimiters; } } else { - inlines.push(Str("!")); + block.appendChild(text('!')); } return true; }; // Try to match close bracket against an opening in the delimiter // stack. Add either a link or image, or a plain [ character, -// to the inlines stack. If there is a matching delimiter, +// to block's children. If there is a matching delimiter, // remove it from the delimiter stack. -var parseCloseBracket = function(inlines) { +var parseCloseBracket = function(block) { var startpos; var is_image; var dest; var title; var matched = false; - var link_text; var i; var reflabel; var opener; @@ -530,13 +520,13 @@ var parseCloseBracket = function(inlines) { if (opener === null) { // no matched opener, just return a literal - inlines.push(Str("]")); + block.appendChild(text(']')); return true; } if (!opener.active) { // no matched opener, just return a literal - inlines.push(Str("]")); + block.appendChild(text(']')); // take opener off emphasis stack this.removeDelimiter(opener); return true; @@ -544,15 +534,6 @@ var parseCloseBracket = function(inlines) { // If we got here, open is a potential opener is_image = opener.cc === C_BANG; - // instead of copying a slice, we null out the - // parts of inlines that don't correspond to link_text; - // later, we'll collapse them. This is awkward, and could - // be simplified if we made inlines a linked list rather than - // an array: - link_text = inlines.slice(0); - for (i = 0; i < opener.pos + 1; i++) { - link_text[i] = null; - } // Check to see if we have a link/image @@ -597,13 +578,22 @@ var parseCloseBracket = function(inlines) { } if (matched) { - this.processEmphasis(link_text, opener.previous); - - // remove the part of inlines that became link_text. - // see note above on why we need to do this instead of splice: - for (i = opener.pos; i < inlines.length; i++) { - inlines[i] = null; + var node = new Node(is_image ? 'Image' : 'Link'); + node.destination = dest; + node.title = title; + + var tmp, next; + tmp = opener.node.next; + while (tmp) { + next = tmp.next; + tmp.unlink(); + node.appendChild(tmp); + tmp = next; } + block.appendChild(node); + this.processEmphasis(node, opener.previous); + + opener.node.unlink(); // processEmphasis will remove this and later delimiters. // Now, for a link, we also deactivate earlier link openers. @@ -618,27 +608,23 @@ var parseCloseBracket = function(inlines) { } } - inlines.push({t: is_image ? 'Image' : 'Link', - destination: dest, - title: title, - children: link_text}); return true; } else { // no match this.removeDelimiter(opener); // remove this opener from stack this.pos = startpos; - inlines.push(Str("]")); + block.appendChild(text(']')); return true; } }; // Attempt to parse an entity, return Entity object if successful. -var parseEntity = function(inlines) { +var parseEntity = function(block) { var m; if ((m = this.match(reEntityHere))) { - inlines.push({ t: 'Text', c: entityToChar(m) }); + block.appendChild(text(entityToChar(m))); return true; } else { return false; @@ -646,11 +632,11 @@ var parseEntity = function(inlines) { }; // Parse a run of ordinary characters, or a single character with -// a special meaning in markdown, as a plain string, adding to inlines. -var parseString = function(inlines) { +// a special meaning in markdown, as a plain string. +var parseString = function(block) { var m; if ((m = this.match(reMain))) { - inlines.push({ t: 'Text', c: m }); + block.appendChild(text(m)); return true; } else { return false; @@ -659,17 +645,15 @@ var parseString = function(inlines) { // Parse a newline. If it was preceded by two spaces, return a hard // line break; otherwise a soft line break. -var parseNewline = function(inlines) { +var parseNewline = function(block) { var m = this.match(/^ *\n/); if (m) { - if (m.length > 2) { - inlines.push({ t: 'Hardbreak' }); - } else if (m.length > 0) { - inlines.push({ t: 'Softbreak' }); - } + var node = new Node(m.length > 2 ? 'Hardbreak' : 'Softbreak'); + block.appendChild(node); return true; + } else { + return false; } - return false; }; // Attempt to parse a link reference, modifying refmap. @@ -731,9 +715,9 @@ var parseReference = function(s, refmap) { }; // Parse the next inline element in subject, advancing subject position. -// On success, add the result to the inlines list, and return true. +// On success, add the result to block's children and return true. // On failure, return false. -var parseInline = function(inlines) { +var parseInline = function(block) { "use strict"; var c = this.peek(); if (c === -1) { @@ -743,56 +727,57 @@ var parseInline = function(inlines) { switch(c) { case C_NEWLINE: case C_SPACE: - res = this.parseNewline(inlines); + res = this.parseNewline(block); break; case C_BACKSLASH: - res = this.parseBackslash(inlines); + res = this.parseBackslash(block); break; case C_BACKTICK: - res = this.parseBackticks(inlines); + res = this.parseBackticks(block); break; case C_ASTERISK: case C_UNDERSCORE: - res = this.parseEmphasis(c, inlines); + res = this.parseEmphasis(c, block); break; case C_OPEN_BRACKET: - res = this.parseOpenBracket(inlines); + res = this.parseOpenBracket(block); break; case C_BANG: - res = this.parseBang(inlines); + res = this.parseBang(block); break; case C_CLOSE_BRACKET: - res = this.parseCloseBracket(inlines); + res = this.parseCloseBracket(block); break; case C_LESSTHAN: - res = this.parseAutolink(inlines) || this.parseHtmlTag(inlines); + res = this.parseAutolink(block) || this.parseHtmlTag(block); break; case C_AMPERSAND: - res = this.parseEntity(inlines); + res = this.parseEntity(block); break; default: - res = this.parseString(inlines); + res = this.parseString(block); break; } if (!res) { this.pos += 1; - inlines.push({t: 'Text', c: fromCodePoint(c)}); + var textnode = new Node('Text'); + textnode.c = fromCodePoint(c); + block.appendChild(textnode); } return true; }; -// Parse s as a list of inlines, using refmap to resolve references. -var parseInlines = function(s, refmap) { - this.subject = s; +// Parse string_content in block into inline children, +// using refmap to resolve references. +var parseInlines = function(block, refmap) { + this.subject = block.string_content.trim(); this.pos = 0; this.refmap = refmap || {}; this.delimiters = null; - var inlines = []; - while (this.parseInline(inlines)) { + while (this.parseInline(block)) { } - this.processEmphasis(inlines, null); - return inlines; + this.processEmphasis(block, null); }; // The InlineParser object. |