diff --git a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts
index 63411d9bf8..2eba697968 100644
--- a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts
+++ b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts
@@ -39,6 +39,7 @@ export class HTMLMarkdownConverter {
this.turndownService.addRule('span', {
filter: 'span',
replacement: function (content, node) {
+ let escapedText = escapeAngleBrackets(node.textContent);
// There are certain properties that either don't have equivalents in markdown or whose transformations
// don't have actions defined in WYSIWYG yet. To unblock users, leaving these elements alone (including their child elements)
// Note: the initial list was generated from our TSG Jupyter Book
@@ -74,7 +75,7 @@ export class HTMLMarkdownConverter {
beginString = '' + beginString;
endString += '';
}
- return beginString + content + endString;
+ return beginString + escapedText + endString;
}
});
this.turndownService.addRule('img', {
@@ -99,6 +100,7 @@ export class HTMLMarkdownConverter {
const notebookLink = node.href ? URI.parse(node.href) : URI.file(node.title);
const notebookFolder = this.notebookUri ? path.join(path.dirname(this.notebookUri.fsPath), path.sep) : '';
let relativePath = findPathRelativeToContent(notebookFolder, notebookLink);
+ node.innerText = escapeAngleBrackets(node.innerText);
if (relativePath) {
return `[${node.innerText}](${relativePath})`;
}
@@ -112,6 +114,7 @@ export class HTMLMarkdownConverter {
.replace(/^\n+/, '') // remove leading newlines
.replace(/\n+$/, '\n') // replace trailing newlines with just a single one
.replace(/\n/gm, '\n '); // indent
+ content = escapeAngleBrackets(content);
let prefix = options.bulletListMarker + ' ';
let parent = node.parentNode;
let nestedCount = 0;
@@ -131,6 +134,72 @@ export class HTMLMarkdownConverter {
);
}
});
+ this.turndownService.addRule('p', {
+ filter: 'p',
+ replacement: function (content, node) {
+ node.childNodes.forEach(c => {
+ if (c.nodeType === Node.TEXT_NODE) {
+ c.nodeValue = escapeAngleBrackets(c.textContent);
+ } else if (c.nodeType === Node.ELEMENT_NODE) {
+ c.innerText = escapeAngleBrackets(c.textContent);
+ }
+ });
+ return '\n\n' + node.innerHTML.replace(/</gi, '<').replace(/>/gi, '>').replace(/ /gi, '') + '\n\n';
+ }
+ });
+ this.turndownService.addRule('heading', {
+ filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ replacement: function (content, node, options) {
+ let hLevel = Number(node.nodeName.charAt(1));
+ let escapedText = escapeAngleBrackets(content);
+ if (options.headingStyle === 'setext' && hLevel < 3) {
+ let underline = '#'.repeat(hLevel);
+ return '\n\n' + escapedText + '\n' + underline + '\n\n';
+ } else {
+ return '\n\n' + '#'.repeat(hLevel) + ' ' + escapedText + '\n\n';
+ }
+ }
+ });
+ this.turndownService.addRule('bold', {
+ filter: ['strong', 'b'],
+ replacement: function (content, node, options) {
+ content = escapeAngleBrackets(content);
+ if (!content.trim()) { return ''; }
+ return options.strongDelimiter + content + options.strongDelimiter;
+ }
+ });
+ this.turndownService.addRule('italicize', {
+ filter: ['em', 'i'],
+ replacement: function (content, node, options) {
+ content = escapeAngleBrackets(content);
+ if (!content.trim()) { return ''; }
+ return options.emDelimiter + content + options.emDelimiter;
+ }
+ });
+ this.turndownService.addRule('code', {
+ filter: function (node) {
+ let hasSiblings = node.previousSibling || node.nextSibling;
+ let isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
+
+ return node.nodeName === 'CODE' && !isCodeBlock;
+ },
+ replacement: function (content) {
+ content = escapeAngleBrackets(content);
+ if (!content.trim()) { return ''; }
+
+ let delimiter = '`';
+ let leadingSpace = '';
+ let trailingSpace = '';
+ let matches = content.match(/`+/gm);
+ if (matches) {
+ if (/^`/.test(content)) { leadingSpace = ' '; }
+ if (/`$/.test(content)) { trailingSpace = ' '; }
+ while (matches.indexOf(delimiter) !== -1) { delimiter = delimiter + '`'; }
+ }
+
+ return delimiter + leadingSpace + content + trailingSpace + delimiter;
+ }
+ });
}
}
@@ -150,3 +219,16 @@ export function findPathRelativeToContent(notebookFolder: string, contentPath: U
}
return '';
}
+
+export function escapeAngleBrackets(textContent: string): string {
+ let text: string = textContent;
+ if (text.includes('') || text.includes('') || (text.includes('style') && !text.includes('