diff --git a/bin/specsplit.js b/bin/specsplit.js index a8af764..323437b 100755 --- a/bin/specsplit.js +++ b/bin/specsplit.js @@ -58,7 +58,8 @@ function readFile(filename, encoding, callback) { readFile(options.spec, 'utf8', function (error, input) { var good = [], bad = [], markdown = new Remarkable({ - xhtml: true, // write
instead of
and so on + html: true, + xhtml: true, codeLangPrefix: 'language-' }); diff --git a/lib/common/html_blocks.js b/lib/common/html_blocks.js new file mode 100644 index 0000000..97fb1e6 --- /dev/null +++ b/lib/common/html_blocks.js @@ -0,0 +1,58 @@ +// List of valid html blocks names, accorting to stmd spec +// http://jgm.github.io/stmd/spec.html#html-blocks + +'use strict'; + + +module.exports = [ + 'article', + 'aside', + 'button', + 'blockquote', + 'body', + 'canvas', + 'caption', + 'col', + 'colgroup', + 'dd', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'header', + 'hgroup', + 'hr', + 'iframe', + 'li', + 'map', + 'object', + 'ol', + 'output', + 'p', + 'pre', + 'progress', + 'script', + 'section', + 'style', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'tr', + 'thead', + 'ul', + 'video' +]; diff --git a/lib/lexer_block/html.js b/lib/common/html_re.js similarity index 55% rename from lib/lexer_block/html.js rename to lib/common/html_re.js index e39178c..c58213a 100644 --- a/lib/lexer_block/html.js +++ b/lib/common/html_re.js @@ -1,12 +1,8 @@ -// HTML block +// Regexps to match html elements 'use strict'; -var isEmpty = require('../helpers').isEmpty; -var getLines = require('../helpers').getLines; - - function replace(regex, options) { regex = regex.source; options = options || ''; @@ -16,14 +12,14 @@ function replace(regex, options) { return new RegExp(regex, options); } val = val.source || val; - val = val.replace(/(^|[^\[])\^/g, '$1'); + //val = val.replace(/(^|[^\[])\^/g, '$1'); regex = regex.replace(name, val); return self; }; } -var attr_name = /[a-zA-Z_:][a-zA-Z0-9:._-]*/; +var attr_name = /[a-zA-Z_:][a-zA-Z0-9:._-]*/; var unquoted = /[^"'=<>`\x00-\x20]+/; var single_quoted = /'[^']*'/; @@ -51,7 +47,7 @@ var processing = /<[?].*?[?]>/; var declaration = /]*>/; var cdata = /])*\]\]>/; -var html_tag = replace(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/, 'i') +var HTML_TAG_RE = replace(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/, 'i') ('open_tag', open_tag) ('close_tag', close_tag) ('comment', comment) @@ -61,38 +57,4 @@ var html_tag = replace(/^(?:open_tag|close_tag|comment|processing|declaration|cd (); -module.exports = function html(state, startLine, endLine, silent) { - var nextLine, - pos = state.bMarks[startLine], - max = state.eMarks[startLine], - shift = state.tShift[startLine]; - - pos += shift; - - if (pos + 3 >= max || - shift > 3 || - state.blkLevel > 0) { return false; } - - if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } - - // TODO: (?) optimize check. - - nextLine = startLine + 1; - while (nextLine < state.lineMax && !isEmpty(state, nextLine)) { - nextLine++; - } - - if (!html_tag.test(state.src.slice(pos, state.eMarks[nextLine - 1]).replace(/\n/g,' '))) { - return false; - } - - if (silent) { return true; } - - state.tokens.push({ - type: 'html', - content: getLines(state, startLine, nextLine, 0, true) - }); - - state.line = nextLine; - return true; -}; +module.exports.HTML_TAG_RE = HTML_TAG_RE; diff --git a/lib/common/url_schemas.js b/lib/common/url_schemas.js new file mode 100644 index 0000000..e1c5cca --- /dev/null +++ b/lib/common/url_schemas.js @@ -0,0 +1,172 @@ +// List of valid url schemas, accorting to stmd spec +// http://jgm.github.io/stmd/spec.html#autolinks + +'use strict'; + + +module.exports = [ + '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' +]; diff --git a/lib/lexer_block.js b/lib/lexer_block.js index e39ae5a..3d8536a 100644 --- a/lib/lexer_block.js +++ b/lib/lexer_block.js @@ -19,7 +19,7 @@ rules.push(require('./lexer_block/hr')); rules.push(require('./lexer_block/list')); rules.push(require('./lexer_block/heading')); rules.push(require('./lexer_block/lheading')); -rules.push(require('./lexer_block/html')); +rules.push(require('./lexer_block/htmlblock')); rules.push(require('./lexer_block/table')); rules.push(require('./lexer_block/paragraph')); diff --git a/lib/lexer_block/htmlblock.js b/lib/lexer_block/htmlblock.js new file mode 100644 index 0000000..db095e6 --- /dev/null +++ b/lib/lexer_block/htmlblock.js @@ -0,0 +1,75 @@ +// HTML block + +'use strict'; + + +var isEmpty = require('../helpers').isEmpty; +var getLines = require('../helpers').getLines; + +var block_names = require('../common/html_blocks'); + + +var HTML_TAG_OPEN_RE = /^<([a-zA-Z]{1,15})[\s\/>]/; +var HTML_TAG_CLOSE_RE = /^<\/([a-zA-Z]{1,15})[\s>]/; + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + +module.exports = function htmlblock(state, startLine, endLine, silent) { + var ch, match, nextLine, + pos = state.bMarks[startLine], + max = state.eMarks[startLine], + shift = state.tShift[startLine]; + + pos += shift; + + if (!state.options.html) { return false; } + + if (shift > 3 || pos + 2 >= max || state.blkLevel > 0) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x21/* ! */ || ch === 0x3F/* ? */) { + // Directive start / comment start / processing instruction start + if (silent) { return true; } + + } else if (ch === 0x2F/* / */ || isLetter(ch)) { + + // Probably start or end of tag + if (ch === 0x2F/* \ */) { + // closing tag + match = state.src.slice(pos, max).match(HTML_TAG_CLOSE_RE); + if (!match) { return false; } + } else { + // opening tag + match = state.src.slice(pos, max).match(HTML_TAG_OPEN_RE); + if (!match) { return false; } + } + // Make sure tag name is valid + if (block_names.indexOf(match[1]) < 0) { return false; } + if (silent) { return true; } + + } else { + return false; + } + + // If we are here - we detected HTML block. + // Let's roll down till empty line (block end). + nextLine = startLine + 1; + while (nextLine < state.lineMax && !isEmpty(state, nextLine)) { + nextLine++; + } + + state.tokens.push({ + type: 'html', + content: getLines(state, startLine, nextLine, 0, true) + }); + + state.line = nextLine; + return true; +}; diff --git a/lib/lexer_block/paragraph.js b/lib/lexer_block/paragraph.js index 4f67808..80e5d2f 100644 --- a/lib/lexer_block/paragraph.js +++ b/lib/lexer_block/paragraph.js @@ -28,7 +28,7 @@ module.exports = function paragraph(state, startLine/*, endLine*/) { // setex header can't interrupt paragraph // if (rules_named.lheading(state, nextLine, endLine, true)) { break; } if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } - if (rules_named.html(state, nextLine, endLine, true)) { break; } + if (rules_named.htmlblock(state, nextLine, endLine, true)) { break; } if (rules_named.table(state, nextLine, endLine, true)) { break; } //if (rules_named.tag(state, nextLine, endLine, true)) { break; } //if (rules_named.def(state, nextLine, endLine, true)) { break; } diff --git a/test/stmd.js b/test/stmd.js index 897e1ca..ec405bb 100644 --- a/test/stmd.js +++ b/test/stmd.js @@ -11,6 +11,7 @@ var Remarked = require('../'); describe('stmd', function () { var md = new Remarked({ + html: true, xhtml: true, codeLangPrefix: 'language-' });