Skip to content

Commit

Permalink
refactor: Replace Renderer
Browse files Browse the repository at this point in the history
To incorporate findings also denoted in:

JiLiZART/BBob#148

exchanged renderer to DOM based rendering to benefit from automatic
proper escaping.

Still using the HTML renderer as optional fallback.
  • Loading branch information
mmichaelis committed Oct 25, 2023
1 parent d7676fc commit 462bbcf
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 4 deletions.
7 changes: 3 additions & 4 deletions packages/ckeditor5-bbcode/src/bbcode2html.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { render } from "@bbob/html/es";
import bbob from "@bbob/core/es";
import { bbCodeLogger } from "./BBCodeLogger";
import { ckeditor5Preset } from "./bbob/ckeditor5Preset";
import { htmlSanitizer } from "./bbob/htmlSanitizer";
import { renderHtmlDom } from "./bbob/renderHtmlDom";

const bbobProcessor = bbob([htmlSanitizer(), ckeditor5Preset()]);
const bbobProcessor = bbob(ckeditor5Preset());

/**
* Parses BBCode to HTML.
*/
export const bbcode2html = (bbcode: string, allowedTags?: string[]): string => {
const logger = bbCodeLogger;
const processed = bbobProcessor.process(bbcode, {
render,
render: renderHtmlDom,
enableEscapeTags: true,
onlyAllowTags: allowedTags,
});
Expand Down
67 changes: 67 additions & 0 deletions packages/ckeditor5-bbcode/src/bbob/renderHtmlDom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render as htmlRender } from "@bbob/html/es";
import { CoreRenderable, CoreRenderer, CoreRenderNode } from "@bbob/core/es";
import { isStringNode, isTagNode } from "@bbob/plugin-helper/es";
import { bbCodeLogger } from "../BBCodeLogger";

const renderDomNode = (node: CoreRenderable, stripTags = false): Node => {
if (typeof node === "number") {
return document.createTextNode(String(node));
}

if (!node || isStringNode(node)) {
return document.createTextNode(node ?? "");
}

if (!isTagNode(node)) {
throw new Error(`Unexpected node. Expected TagNode, but is: ${typeof node}: ${JSON.stringify(node)}`);
}

const { tag, attrs, content } = node;

const renderedContents = renderDomNodes(content ?? [], stripTags);

if (stripTags) {
const fragment = document.createDocumentFragment();
fragment.append(...renderedContents);
return fragment;
}

const element = document.createElement(node.tag);
Object.entries(attrs).forEach(([name, value]) => {
try {
element.setAttribute(name, value);
} catch (e) {
bbCodeLogger.debug(`Ignoring error for setting attribute '${name}' for node ${tag} to value '${value}'.`, e);
}
});

element.append(...renderedContents);

return element;
};

const renderDomNodes = (node: CoreRenderable[], stripTags = false): Node[] =>
node.map((n) => renderDomNode(n, stripTags));

const renderTree = (node: CoreRenderable[], stripTags = false): string => {
const container = document.createElement("div");
container.append(...renderDomNodes(node, stripTags));
return container.innerHTML;
};

export const renderHtmlDom: CoreRenderer = (
node: CoreRenderNode,
options: object & { stripTags?: boolean } = {},
): string => {
const { stripTags = false } = options;

if (Array.isArray(node)) {
// Main purpose is to intervene on render when `.html` is retrieved
// from processed result. Here, we will get the complete tree as input.
return renderTree(node);
} else {
// Some defensive approach to fall back to an alternative rendering for
// in-between processing.
return htmlRender(node, { stripTags });
}
};

0 comments on commit 462bbcf

Please sign in to comment.