aboutsummaryrefslogtreecommitdiff
path: root/lib/writers
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2015-02-04 18:41:12 +0000
committerMichael Williamson <mike@zwobble.org>2015-02-04 18:41:12 +0000
commitb2ccfa5cc2cd4d7f8f80f0555e2586387375a98e (patch)
treee4ce2260bf93a03865400adb4c389720140cf3d1 /lib/writers
parent774ff6722f9df9fec9f350288023aa500f49df61 (diff)
Move writers into a separate directory
Diffstat (limited to 'lib/writers')
-rw-r--r--lib/writers/html-writer.js156
-rw-r--r--lib/writers/index.js1
-rw-r--r--lib/writers/markdown-writer.js176
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, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
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