From d302d0755e075811a37268f4cef00e7c54cb409a Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 26 Sep 2026 22:28:05 +0100 Subject: Escape HTML IDs in markdown writer --- NEWS | 2 ++ lib/writers/markdown-writer.js | 28 ++++++++++++++++------------ test/writers/markdown-writer.tests.js | 8 ++++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 3d9324a..a820136 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ * Handle paragraphs and runs that have been moved and tracked as a revision. +* Escape HTML IDs in Markdown writer. + # 1.12.3 * Avoid excessive backtracking when parsing an unterminated string with many diff --git a/lib/writers/markdown-writer.js b/lib/writers/markdown-writer.js index a89ad9f..a5b572e 100644 --- a/lib/writers/markdown-writer.js +++ b/lib/writers/markdown-writer.js @@ -1,5 +1,6 @@ var _ = require("underscore"); +var htmlWriter = require("./html-writer"); function symmetricMarkdownElement(end) { return markdownElement(end, end); @@ -52,10 +53,10 @@ function markdownListItem(attributes, list, listItem) { list = list || {indent: 0, isOrdered: false, count: 0}; list.count++; listItem.hasClosed = false; - + var bullet = list.isOrdered ? list.count + "." : "-"; var start = repeatString("\t", list.indent) + bullet + " "; - + return { start: start, end: function() { @@ -94,20 +95,20 @@ function markdownWriter() { var elementStack = []; var list = null; var listItem = {}; - + function open(tagName, attributes) { attributes = attributes || {}; - + var createElement = htmlToMarkdown[tagName] || function() { return {}; }; var element = createElement(attributes, list, listItem); elementStack.push({end: element.end, list: list}); - + if (element.list) { list = element.list; } - + var anchorBeforeStart = element.anchorPosition === "before"; if (anchorBeforeStart) { writeAnchor(attributes); @@ -118,29 +119,32 @@ function markdownWriter() { writeAnchor(attributes); } } - + function writeAnchor(attributes) { if (attributes.id) { - fragments.push(''); + var writer = htmlWriter.writer(); + writer.open("a", {id: attributes.id}); + writer.close("a"); + fragments.push(writer.asString()); } } - + function close(tagName) { var element = elementStack.pop(); list = element.list; var end = _.isFunction(element.end) ? element.end() : element.end; fragments.push(end || ""); } - + function selfClosing(tagName, attributes) { open(tagName, attributes); close(tagName); } - + function text(value) { fragments.push(escapeMarkdown(value)); } - + function asString() { return fragments.join(""); } diff --git a/test/writers/markdown-writer.tests.js b/test/writers/markdown-writer.tests.js index 6d078c5..e7ab9e1 100644 --- a/test/writers/markdown-writer.tests.js +++ b/test/writers/markdown-writer.tests.js @@ -90,6 +90,14 @@ test('elements with IDs have anchor tags with IDs appended to start of markdown return assert.equal(writer.asString(), '# Hello\n\n'); }); +test('anchor tag IDs are escaped', function() { + var writer = mdWriter.writer(); + writer.open("h1", {id: "\"start\""}); + writer.text("Hello"); + writer.close("h1"); + return assert.equal(writer.asString(), '# Hello\n\n'); +}); + test('links have anchors before opening square bracket', function() { var writer = mdWriter.writer(); writer.open("a", {href: "http://example.com", id: "start"}); -- cgit v1.2.3