diff options
| author | Michael Williamson <mike@zwobble.org> | 2015-02-04 18:41:12 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2015-02-04 18:41:12 +0000 |
| commit | b2ccfa5cc2cd4d7f8f80f0555e2586387375a98e (patch) | |
| tree | e4ce2260bf93a03865400adb4c389720140cf3d1 /lib/writers | |
| parent | 774ff6722f9df9fec9f350288023aa500f49df61 (diff) | |
Move writers into a separate directory
Diffstat (limited to 'lib/writers')
| -rw-r--r-- | lib/writers/html-writer.js | 156 | ||||
| -rw-r--r-- | lib/writers/index.js | 1 | ||||
| -rw-r--r-- | lib/writers/markdown-writer.js | 176 |
3 files changed, 333 insertions, 0 deletions
diff --git a/lib/writers/html-writer.js b/lib/writers/html-writer.js new file mode 100644 index 0000000..33441cb --- /dev/null +++ b/lib/writers/html-writer.js @@ -0,0 +1,156 @@ +var util = require("util"); +var _ = require("underscore"); + +exports.writer = writer; + +function writer(options) { + options = options || {}; + if(options.prettyPrint) { + return prettyWriter(); + } + else if(options.markdown) { + var markdown = require("./markdown-writer"); + + return markdown.writer(); + } + else { + return simpleWriter(); + } +} + + +var indentedElements = { + div: true, + p: true, + ul: true +}; + + +function prettyWriter() { + var indentationLevel = 0; + var indentation = " "; + var stack = []; + var start = true; + var inText = false; + + var writer = simpleWriter(); + + function open(tagName, attributes) { + indent(); + stack.push(tagName); + writer.open(tagName, attributes); + if (indentedElements[tagName]) { + indentationLevel++; + } + start = false; + } + + function close(tagName) { + if (indentedElements[tagName]) { + indentationLevel--; + } + indent(); + stack.pop(); + writer.close(tagName); + } + + function text(value) { + startText(); + writer.text(value.replace("\n", "\n" + indentation)); + } + + function selfClosing(tagName, attributes) { + indent(); + writer.selfClosing(tagName, attributes); + } + + function append(html) { + startText(); + writer.append(html.replace("\n", "\n" + indentation)); + } + + function insideIndentedElement() { + return stack.length === 0 || indentedElements[stack[stack.length - 1]]; + } + + function startText() { + if (!inText) { + indent(); + inText = true; + } + } + + function indent() { + inText = false; + if (!start && insideIndentedElement()) { + writer.append("\n"); + for (var i = 0; i < indentationLevel; i++) { + writer.append(indentation); + } + } + } + + return { + asString: writer.asString, + open: open, + close: close, + text: text, + selfClosing: selfClosing, + append: append + }; +} + + +function simpleWriter() { + var fragments = []; + + function open(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push(util.format("<%s%s>", tagName, attributeString)); + } + + function close(tagName) { + fragments.push(util.format("</%s>", tagName)); + } + + function selfClosing(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push(util.format("<%s%s />", tagName, attributeString)); + } + + function generateAttributeString(attributes) { + return _.map(attributes, function(value, key) { + return util.format(' %s="%s"', key, escapeHtml(value)); + }).join(""); + } + + function text(value) { + fragments.push(escapeHtml(value)); + } + + function append(html) { + fragments.push(html); + } + + function asString() { + return fragments.join(""); + } + + return { + asString: asString, + open: open, + close: close, + text: text, + selfClosing: selfClosing, + append: append + }; +} + + +function escapeHtml(value) { + return value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); +} diff --git a/lib/writers/index.js b/lib/writers/index.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/writers/index.js @@ -0,0 +1 @@ + diff --git a/lib/writers/markdown-writer.js b/lib/writers/markdown-writer.js new file mode 100644 index 0000000..b63d104 --- /dev/null +++ b/lib/writers/markdown-writer.js @@ -0,0 +1,176 @@ + +var _ = require("underscore"); + +function mdElement(start, end) { + if (end === undefined) + end = start; + + this.open = function(attrs) { + return start; + }; + + this.close = function() { + return end; + }; + + this.empty = function(attrs) { + return this.open(attrs) + this.close(); + }; +} +function mdLink(link, text) { + var attrs = {}; + + this.open = function(attributes) { + attrs = attributes || {}; + + return "["; + }; + + this.close = function() { + return (attrs[text] || "") + "](" + (attrs[link] || "") + ")"; + }; + + this.empty = function(attrs) { + // don't display empty content (this is mainly to remove images with no src data) + var content = this.open(attrs) + this.close(); + + if(content === "[]()") { + return ""; + } + + return content; + }; +} +function mdList() { + this.open = function(attrs) { + return attrs.indent > -1 ? "\n" : ""; + }; + + this.close = function(attrs) { + return attrs.indent > -1 ? "" : "\n"; + }; + + this.empty = function(attrs) { + return this.open(attrs) + this.close(); + }; +} +function mdListItem() { + this.open = function(attrs) { + var output = ""; + + for(var i = 0; i < attrs.indent; ++i) { + output += "\t"; + } + + return output + (attrs.type === "ul" ? "-" : attrs.count + ".") + " "; + }; + + this.close = function() { + return "\n"; + }; + + this.empty = function(attrs) { + return this.open(attrs) + this.close(); + }; +} + +// map HTML tags to Markdown syntax +var htmlmd = { + // paragraphs elements use three line feeds to close + // this prevents them being caught by collapsing list elements in `asString` + // while still allowing them to be collapsed when multiple empty lines are removed + "h1": new mdElement("# ", "\n\n\n"), + "h2": new mdElement("## ", "\n\n\n"), + "h3": new mdElement("### ", "\n\n\n"), + "h4": new mdElement("#### ", "\n\n\n"), + "h5": new mdElement("##### ", "\n\n\n"), + "h6": new mdElement("###### ", "\n\n\n"), + "p": new mdElement("", "\n\n\n"), + "br": new mdElement("", " \n"), + "ul": new mdList(), + "ol": new mdList(), + "li": new mdListItem(), + "strong": new mdElement("**"), + "em": new mdElement("*"), + "a": new mdLink("href"), + "img": new mdLink("src", "alt"), +}; + +function mdWriter() { + var fragments = []; + var listStack = []; + var list = { + indent: -1, + type: "ul", + count: 0 + }; + + function isList(tagName) { + return tagName === "ul" || tagName === "ol"; + } + + function open(tagName, attributes) { + if(htmlmd[tagName]) { + attributes = attributes || {}; + + if(tagName === "li") { + attributes.indent = list.indent; + attributes.type = list.type; + attributes.count = ++list.count; + } + else if(isList(tagName)) { + attributes.indent = list.indent; + } + + fragments.push(htmlmd[tagName].open(attributes)); + + if(isList(tagName)) { + listStack.push(_.clone(list)); + + list.type = tagName; + list.indent++; + list.count = 0; + } + } + } + + function close(tagName) { + if(htmlmd[tagName]) { + if(isList(tagName)) { + list = listStack.pop(); + } + + fragments.push(htmlmd[tagName].close(list)); + } + } + + function selfClosing(tagName, attributes) { + if(htmlmd[tagName]) { + fragments.push(htmlmd[tagName].empty(attributes)); + } + } + + function text(value) { + fragments.push(value); + } + + function append(html) { + fragments.push(html); + } + + function asString() { + // massage the output to collapse mulitple empty lines and empty lines between nested lists + return fragments.join("").replace(/\n{2}(\t*)([2-9]\d*\.|- )/g, "\n$1$2").replace(/\n{3,}/g, "\n\n"); + } + + return { + asString: asString, + open: open, + close: close, + text: text, + selfClosing: selfClosing, + append: append + }; +} + +exports.writer = mdWriter;
\ No newline at end of file |
