Skip to content
This repository has been archived by the owner on Mar 24, 2022. It is now read-only.

Commit

Permalink
Add scriptingEnabled flag to serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
fb55 committed Feb 7, 2022
1 parent 677d83a commit 18f170c
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 26 deletions.
12 changes: 12 additions & 0 deletions packages/parse5/lib/serializer/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,16 @@ describe('serializer', () => {
parse5.serialize(document, { treeAdapter });
});
});

describe('Scripting flag (GH-332)', () => {
it('should serialize with the scripting flag', () => {
const document = parse5.parse('&amp;<noscript>&amp;</noscript>');
expect(parse5.serialize(document, { scriptingEnabled: false })).toBe(
'<html><head></head><body>&amp;<noscript>&amp;amp;</noscript></body></html>'
);
expect(parse5.serialize(document, { scriptingEnabled: true })).toBe(
'<html><head></head><body>&amp;<noscript>&amp;</noscript></body></html>'
);
});
});
});
78 changes: 52 additions & 26 deletions packages/parse5/lib/serializer/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TAG_NAMES as $, NAMESPACES as NS } from '../common/html.js';
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface';
import * as DefaultTreeAdapter from '../tree-adapters/default.js';

//Escaping regexes
const AMP_REGEX = /&/g;
Expand Down Expand Up @@ -29,30 +30,59 @@ const VOID_ELEMENTS = new Set<string>([
$.TRACK,
$.WBR,
]);
const UNESCAPED_TEXT = new Set<string>([
$.STYLE,
$.SCRIPT,
$.XMP,
$.IFRAME,
$.NOEMBED,
$.NOFRAMES,
$.PLAINTEXT,
$.NOSCRIPT,
]);
const UNESCAPED_TEXT = new Set<string>([$.STYLE, $.SCRIPT, $.XMP, $.IFRAME, $.NOEMBED, $.NOFRAMES, $.PLAINTEXT]);

export interface SerializerOptions<T extends TreeAdapterTypeMap> {
/**
* Specifies input tree format.
*
* @default `treeAdapters.default`
*/
treeAdapter: TreeAdapter<T>;
treeAdapter?: TreeAdapter<T>;
/**
* The [scripting flag](https://html.spec.whatwg.org/multipage/parsing.html#scripting-flag). If set
* to `true`, `noscript` element content will not be escaped.
*
* @default `true`
*/
scriptingEnabled?: boolean;
}

//Serializer
export function serializeChildNodes<T extends TreeAdapterTypeMap>(
parentNode: T['parentNode'],
type InternalOptions<T extends TreeAdapterTypeMap> = Required<SerializerOptions<T>>;

/**
* Serializes an AST node to an HTML string.
*
* @example
*
* ```js
* const parse5 = require('parse5');
*
* const document = parse5.parse('<!DOCTYPE html><html><head></head><body>Hi there!</body></html>');
*
* // Serializes a document.
* const html = parse5.serialize(document);
*
* // Serializes the <html> element content.
* const str = parse5.serialize(document.childNodes[1]);
*
* console.log(str); //> '<head></head><body>Hi there!</body>'
* ```
*
* @param node Node to serialize.
* @param options Serialization options.
*/
export function serialize<T extends TreeAdapterTypeMap = DefaultTreeAdapter.DefaultTreeAdapterMap>(
node: T['parentNode'],
options: SerializerOptions<T>
): string {
const opts = { treeAdapter: DefaultTreeAdapter, scriptingEnabled: true, ...options };
return serializeChildNodes(node, opts);
}

function serializeChildNodes<T extends TreeAdapterTypeMap>(
parentNode: T['parentNode'],
options: InternalOptions<T>
): string {
let html = '';
const childNodes = options.treeAdapter.getChildNodes(parentNode);
Expand All @@ -74,10 +104,7 @@ export function serializeChildNodes<T extends TreeAdapterTypeMap>(
return html;
}

export function serializeElement<T extends TreeAdapterTypeMap>(
node: T['element'],
options: SerializerOptions<T>
): string {
function serializeElement<T extends TreeAdapterTypeMap>(node: T['element'], options: InternalOptions<T>): string {
const tn = options.treeAdapter.getTagName(node);

return `<${tn}${serializeAttributes(node, options)}>${
Expand All @@ -95,7 +122,7 @@ export function serializeElement<T extends TreeAdapterTypeMap>(

function serializeAttributes<T extends TreeAdapterTypeMap>(
node: T['element'],
{ treeAdapter }: SerializerOptions<T>
{ treeAdapter }: InternalOptions<T>
): string {
let html = '';
for (const attr of treeAdapter.getAttrList(node)) {
Expand Down Expand Up @@ -132,28 +159,27 @@ function serializeAttributes<T extends TreeAdapterTypeMap>(
return html;
}

function serializeTextNode<T extends TreeAdapterTypeMap>(
node: T['textNode'],
{ treeAdapter }: SerializerOptions<T>
): string {
function serializeTextNode<T extends TreeAdapterTypeMap>(node: T['textNode'], options: InternalOptions<T>): string {
const { treeAdapter } = options;
const content = treeAdapter.getTextNodeContent(node);
const parent = treeAdapter.getParentNode(node);
const parentTn = parent && treeAdapter.isElementNode(parent) && treeAdapter.getTagName(parent);

return parent && treeAdapter.isElementNode(parent) && UNESCAPED_TEXT.has(treeAdapter.getTagName(parent))
return parentTn && (UNESCAPED_TEXT.has(parentTn) || (options.scriptingEnabled && parentTn === $.NOSCRIPT))
? content
: escapeString(content, false);
}

function serializeCommentNode<T extends TreeAdapterTypeMap>(
node: T['commentNode'],
{ treeAdapter }: SerializerOptions<T>
{ treeAdapter }: InternalOptions<T>
): string {
return `<!--${treeAdapter.getCommentNodeContent(node)}-->`;
}

function serializeDocumentTypeNode<T extends TreeAdapterTypeMap>(
node: T['documentType'],
{ treeAdapter }: SerializerOptions<T>
{ treeAdapter }: InternalOptions<T>
): string {
return `<!DOCTYPE ${treeAdapter.getDocumentTypeNodeName(node)}>`;
}
Expand Down

0 comments on commit 18f170c

Please sign in to comment.