markdown-writer.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. var _ = require("underscore");
  2. function symmetricMarkdownElement(end) {
  3. return markdownElement(end, end);
  4. }
  5. function markdownElement(start, end) {
  6. return function() {
  7. return {start: start, end: end};
  8. };
  9. }
  10. function markdownLink(attributes) {
  11. var href = attributes.href || "";
  12. if (href) {
  13. return {
  14. start: "[",
  15. end: "](" + href + ")",
  16. anchorPosition: "before"
  17. };
  18. } else {
  19. return {};
  20. }
  21. }
  22. function markdownImage(attributes) {
  23. var src = attributes.src || "";
  24. var altText = attributes.alt || "";
  25. if (src || altText) {
  26. return {start: "![" + altText + "](" + src + ")"};
  27. } else {
  28. return {};
  29. }
  30. }
  31. function markdownList(options) {
  32. return function(attributes, list) {
  33. return {
  34. start: list ? "\n" : "",
  35. end: list ? "" : "\n",
  36. list: {
  37. isOrdered: options.isOrdered,
  38. indent: list ? list.indent + 1 : 0,
  39. count: 0
  40. }
  41. };
  42. };
  43. }
  44. function markdownListItem(attributes, list, listItem) {
  45. list = list || {indent: 0, isOrdered: false, count: 0};
  46. list.count++;
  47. listItem.hasClosed = false;
  48. var bullet = list.isOrdered ? list.count + "." : "-";
  49. var start = repeatString("\t", list.indent) + bullet + " ";
  50. return {
  51. start: start,
  52. end: function() {
  53. if (!listItem.hasClosed) {
  54. listItem.hasClosed = true;
  55. return "\n";
  56. }
  57. }
  58. };
  59. }
  60. var htmlToMarkdown = {
  61. "p": markdownElement("", "\n\n"),
  62. "br": markdownElement("", " \n"),
  63. "ul": markdownList({isOrdered: false}),
  64. "ol": markdownList({isOrdered: true}),
  65. "li": markdownListItem,
  66. "strong": symmetricMarkdownElement("__"),
  67. "em": symmetricMarkdownElement("*"),
  68. "a": markdownLink,
  69. "img": markdownImage
  70. };
  71. (function() {
  72. for (var i = 1; i <= 6; i++) {
  73. htmlToMarkdown["h" + i] = markdownElement(repeatString("#", i) + " ", "\n\n");
  74. }
  75. })();
  76. function repeatString(value, count) {
  77. return new Array(count + 1).join(value);
  78. }
  79. function markdownWriter() {
  80. var fragments = [];
  81. var elementStack = [];
  82. var list = null;
  83. var listItem = {};
  84. function open(tagName, attributes) {
  85. attributes = attributes || {};
  86. var createElement = htmlToMarkdown[tagName] || function() {
  87. return {};
  88. };
  89. var element = createElement(attributes, list, listItem);
  90. elementStack.push({end: element.end, list: list});
  91. if (element.list) {
  92. list = element.list;
  93. }
  94. var anchorBeforeStart = element.anchorPosition === "before";
  95. if (anchorBeforeStart) {
  96. writeAnchor(attributes);
  97. }
  98. fragments.push(element.start || "");
  99. if (!anchorBeforeStart) {
  100. writeAnchor(attributes);
  101. }
  102. }
  103. function writeAnchor(attributes) {
  104. if (attributes.id) {
  105. fragments.push('<a id="' + attributes.id + '"></a>');
  106. }
  107. }
  108. function close(tagName) {
  109. var element = elementStack.pop();
  110. list = element.list;
  111. var end = _.isFunction(element.end) ? element.end() : element.end;
  112. fragments.push(end || "");
  113. }
  114. function selfClosing(tagName, attributes) {
  115. open(tagName, attributes);
  116. close(tagName);
  117. }
  118. function text(value) {
  119. fragments.push(escapeMarkdown(value));
  120. }
  121. function asString() {
  122. return fragments.join("");
  123. }
  124. return {
  125. asString: asString,
  126. open: open,
  127. close: close,
  128. text: text,
  129. selfClosing: selfClosing
  130. };
  131. }
  132. exports.writer = markdownWriter;
  133. function escapeMarkdown(value) {
  134. return value
  135. .replace(/\\/g, '\\\\')
  136. .replace(/([\`\*_\{\}\[\]\(\)\#\+\-\.\!])/g, '\\$1');
  137. }