diff options
| -rwxr-xr-x | js/bin/commonmark | 4 | ||||
| -rwxr-xr-x | js/lib/index.js | 1 | ||||
| -rw-r--r-- | js/lib/xml.js | 275 | 
3 files changed, 280 insertions, 0 deletions
diff --git a/js/bin/commonmark b/js/bin/commonmark index 8f1210b..6570ef6 100755 --- a/js/bin/commonmark +++ b/js/bin/commonmark @@ -15,6 +15,8 @@ for (i = 2; i < process.argv.length; i++) {      var arg = process.argv[i];      if (arg === '--ast') {          format = 'ast'; +    } else if (arg === '--xml') { +        format = 'xml';      } else if (arg === '--time') {          options.time = true;      } else if (arg === '--sourcepos') { @@ -32,6 +34,8 @@ var renderer;  if (format === 'html') {      renderer = new commonmark.HtmlRenderer(options); +} else if (format === 'xml') { +    renderer = new commonmark.XMLRenderer(options);  } else if (format === 'ast') {      renderer = new commonmark.ASTRenderer(options);      renderer.options.colors = true; diff --git a/js/lib/index.js b/js/lib/index.js index 22a2184..b309311 100755 --- a/js/lib/index.js +++ b/js/lib/index.js @@ -16,6 +16,7 @@ var util = require('util');  module.exports.Node = require('./node');  module.exports.DocParser = require('./blocks');  module.exports.HtmlRenderer = require('./html'); +module.exports.XMLRenderer = require('./xml');  module.exports.ASTRenderer = function(options) {      return {          render: function(tree) { diff --git a/js/lib/xml.js b/js/lib/xml.js new file mode 100644 index 0000000..7168ffa --- /dev/null +++ b/js/lib/xml.js @@ -0,0 +1,275 @@ +"use strict"; + +// Helper function to produce an XML tag. +var tag = function(name, attrs, selfclosing) { +    var result = '<' + name; +    if (attrs && attrs.length > 0) { +        var i = 0; +        var attrib; +        while ((attrib = attrs[i]) !== undefined) { +            result = result.concat(' ', attrib[0], '="', attrib[1], '"'); +            i++; +        } +    } +    if (selfclosing) { +        result += ' /'; +    } + +    result += '>'; +    return result; +}; + +var reXMLTag = /\<[^>]*\>/; + +var renderNodes = function(block) { + +    var attrs; +    var info_words; +    var tagname; +    var walker = block.walker(); +    var event, node, entering; +    var buffer = ""; +    var lastOut = "\n"; +    var disableTags = 0; +    var indentLevel = 0; +    var indent = '  '; +    var grandparent; +    var out = function(s) { +        if (disableTags > 0) { +            buffer += s.replace(reXMLTag, ''); +        } else { +            buffer += s; +        } +        lastOut = s; +    }; +    var esc = this.escape; +    var cr = function() { +        if (lastOut !== '\n') { +            buffer += '\n'; +            lastOut = '\n'; +            for (var i = indentLevel; i--;) { +                buffer += indent; +            } +        } +    }; + +    var options = this.options; + +    if (options.time) { console.time("rendering"); } + +    while ((event = walker.next())) { +        entering = event.entering; +        node = event.node; + +        attrs = []; +        if (options.sourcepos) { +            var pos = node.sourcepos; +            if (pos) { +                attrs.push(['data-sourcepos', String(pos[0][0]) + ':' + +                            String(pos[0][1]) + '-' + String(pos[1][0]) + ':' + +                            String(pos[1][1])]); +            } +        } + +        switch (node.t) { +        case 'Text': +            out(esc(node.literal)); +            break; + +        case 'Softbreak': +            out(this.softbreak); +            break; + +        case 'Hardbreak': +            out(tag('br', [], true)); +            cr(); +            break; + +        case 'Emph': +            out(tag(entering ? 'em' : '/em')); +            break; + +        case 'Strong': +            out(tag(entering ? 'strong' : '/strong')); +            break; + +        case 'XML': +            out(node.literal); +            break; + +        case 'Link': +            if (entering) { +                attrs.push(['href', esc(node.destination, true)]); +                if (node.title) { +                    attrs.push(['title', esc(node.title, true)]); +                } +                out(tag('a', attrs)); +            } else { +                out(tag('/a')); +            } +            break; + +        case 'Image': +            if (entering) { +                if (disableTags === 0) { +                    out('<img src="' + esc(node.destination, true) + +                        '" alt="'); +                } +                disableTags += 1; +            } else { +                disableTags -= 1; +                if (disableTags === 0) { +                    if (node.title) { +                        out('" title="' + esc(node.title, true)); +                    } +                    out('" />'); +                } +            } +            break; + +        case 'Code': +            out(tag('code') + esc(node.literal) + tag('/code')); +            break; + +        case 'Document': +            if (!entering) { +                cr(); +            } +            break; + +        case 'Paragraph': +            grandparent = node.parent.parent; +            if (grandparent !== null && +                grandparent.t === 'List') { +                if (grandparent.list_data.tight) { +                    break; +                } +            } +            if (entering) { +                cr(); +                out(tag('p', attrs)); +            } else { +                out(tag('/p')); +            } +            break; + +        case 'BlockQuote': +            if (entering) { +                cr(); +                out(tag('blockquote', attrs)); +                indentLevel += 1; +            } else { +                indentLevel -= 1; +                cr(); +                out(tag('/blockquote')); +            } +            break; + +        case 'ListItem': +            if (entering) { +                cr(); +                out(tag('li', attrs)); +            } else { +                out(tag('/li')); +            } +            break; + +        case 'List': +            tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol'; +            if (entering) { +                if (node.list_data.start && node.list_data.start > 1) { +                    attrs.push(['start', node.list_data.start.toString()]); +                } +                cr(); +                out(tag(tagname, attrs)); +            } else { +                cr(); +                out(tag('/' + tagname)); +            } +            break; + +        case 'Header': +            tagname = 'h' + node.level; +            if (entering) { +                cr(); +                out(tag(tagname, attrs)); +            } else { +                out(tag('/' + tagname)); +            } +            break; + +        case 'CodeBlock': +            info_words = node.info ? node.info.split(/ +/) : []; +            if (info_words.length > 0 && info_words[0].length > 0) { +                attrs.push(['class', 'language-' + esc(info_words[0], true)]); +            } +            cr(); +            out(tag('pre') + tag('code', attrs)); +            out(esc(node.literal)); +            out(tag('/code') + tag('/pre')); +            break; + +        case 'XMLBlock': +            cr(); +            out(node.literal); +            break; + +        case 'HorizontalRule': +            cr(); +            out(tag('hr', attrs, true)); +            break; + + +        case 'ReferenceDef': +            break; + +        default: +            throw("Unknown node type " + node.t); +        } + +    } +    if (options.time) { console.timeEnd("rendering"); } +    return buffer; +}; + +var replaceUnsafeChar = function(s) { +    switch (s) { +    case '&': +        return '&'; +    case '<': +        return '<'; +    case '>': +        return '>'; +    case '"': +        return '"'; +    default: +        return s; +    } +}; + +var reNeedsEscaping = /[&<>"]/; + +// The XMLRenderer object. +function XMLRenderer(options){ +    return { +        // default options: +        softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML +        // set to "<br />" to make them hard breaks +        // set to " " if you want to ignore line wrapping in source +        escape: function(s, preserve_entities) { +            if (reNeedsEscaping.test(s)) { +                if (preserve_entities) { +                    return s.replace(/[&](?:[#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)|[&<>"]/gi, replaceUnsafeChar); +                } else { +                    return s.replace(/[&<>"]/g, replaceUnsafeChar); +                } +            } else { +                return s; +            } +        }, +        options: options || {}, +        render: renderNodes +    }; +} + +module.exports = XMLRenderer;  | 
