From d586339cdba48f364feb29e5df6ab0bbdffe4391 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 31 Mar 2021 19:35:27 -0400 Subject: [PATCH 1/3] Add ability to serialize annotation-xml content --- ts/adaptors/HTMLAdaptor.ts | 21 +++++++++++++++++++-- ts/adaptors/browserAdaptor.ts | 1 + ts/adaptors/lite/Parser.ts | 7 ++++--- ts/adaptors/liteAdaptor.ts | 7 +++++++ ts/core/DOMAdaptor.ts | 11 +++++++++++ ts/core/MmlTree/MmlNode.ts | 2 +- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/ts/adaptors/HTMLAdaptor.ts b/ts/adaptors/HTMLAdaptor.ts index f740b2a71..984eaa2e1 100644 --- a/ts/adaptors/HTMLAdaptor.ts +++ b/ts/adaptors/HTMLAdaptor.ts @@ -111,13 +111,22 @@ export interface MinText { /** * The minimum fields needed for a DOMParser * - * @template N The HTMLElement node class - * @template T The Text node class + * @template D The Document class */ export interface MinDOMParser { parseFromString(text: string, format?: string): D; } +/*****************************************************************/ +/** + * The minimum fields needed for a DOMParser + * + * @template N The HTMLElement node class + */ +export interface MinXMLSerializer { + serializeToString(node: N): string; +} + /*****************************************************************/ /** * The minimum fields needed for a Window @@ -130,6 +139,9 @@ export interface MinWindow { DOMParser: { new(): MinDOMParser }; + XMLSerializer: { + new(): MinXMLSerializer; + }; NodeList: any; HTMLCollection: any; HTMLElement: any; @@ -396,6 +408,11 @@ AbstractDOMAdaptor implements MinHTMLAdaptor { return node.outerHTML; } + public serializeXML(node: N) { + const serializer = new this.window.XMLSerializer(); + return serializer.serializeToString(node) as string; + } + /** * @override */ diff --git a/ts/adaptors/browserAdaptor.ts b/ts/adaptors/browserAdaptor.ts index 3be113bb1..b86a914a9 100644 --- a/ts/adaptors/browserAdaptor.ts +++ b/ts/adaptors/browserAdaptor.ts @@ -30,6 +30,7 @@ declare global { interface Window { Document: typeof Document; DOMParser: typeof DOMParser; + XMLSerializer: typeof XMLSerializer; HTMLElement: typeof HTMLElement; HTMLCollection: typeof HTMLCollection; NodeList: typeof NodeList; diff --git a/ts/adaptors/lite/Parser.ts b/ts/adaptors/lite/Parser.ts index 90ac57310..b7a443803 100644 --- a/ts/adaptors/lite/Parser.ts +++ b/ts/adaptors/lite/Parser.ts @@ -345,9 +345,10 @@ export class LiteParser implements MinDOMParser { /** * @param {LiteAdaptor} adaptor The adaptor for managing nodes * @param {LiteElement} node The node to serialize + * @param {boolean} xml True when producing XML, false for HTML * @return {string} The serialized element (like outerHTML) */ - public serialize(adaptor: LiteAdaptor, node: LiteElement): string { + public serialize(adaptor: LiteAdaptor, node: LiteElement, xml: boolean = false): string { const SELF_CLOSING = (this.constructor as typeof LiteParser).SELF_CLOSING; const CDATA = (this.constructor as typeof LiteParser).CDATA_ATTR; const tag = adaptor.kind(node); @@ -355,8 +356,8 @@ export class LiteParser implements MinDOMParser { (x: AttributeData) => x.name + '="' + (CDATA[x.name] ? x.value : this.protectAttribute(x.value)) + '"' ).join(' '); const html = - '<' + tag + (attributes ? ' ' + attributes : '') + '>' - + (SELF_CLOSING[tag] ? '' : adaptor.innerHTML(node) + ''); + '<' + tag + (attributes ? ' ' + attributes : '') + + (SELF_CLOSING[tag] ? (xml ? ' />' : '>') : adaptor.innerHTML(node) + ''); return html; } diff --git a/ts/adaptors/liteAdaptor.ts b/ts/adaptors/liteAdaptor.ts index ed02de3af..ed828f1ff 100644 --- a/ts/adaptors/liteAdaptor.ts +++ b/ts/adaptors/liteAdaptor.ts @@ -463,6 +463,13 @@ export class LiteAdaptor extends AbstractDOMAdaptor { */ outerHTML(node: N): string; + /** + * @param {N} node The HTML node whose serialized string is to be obtained + * @return {string} The serialized node and its content + */ + serializeXML(node: N): string; + /** * @param {N} node The HTML node whose attribute is to be set * @param {string|number} name The name of the attribute to set @@ -558,6 +564,11 @@ export abstract class AbstractDOMAdaptor implements DOMAdaptor */ public abstract outerHTML(node: N): string; + /** + * @override + */ + public abstract serializeXML(node: N): string; + /** * @override */ diff --git a/ts/core/MmlTree/MmlNode.ts b/ts/core/MmlTree/MmlNode.ts index 46d88963c..8bdda1168 100644 --- a/ts/core/MmlTree/MmlNode.ts +++ b/ts/core/MmlTree/MmlNode.ts @@ -1231,7 +1231,7 @@ export class XMLNode extends AbstractMmlEmptyNode { * @return {string} The serialized XML content */ public getSerializedXML(): string { - return this.adaptor.outerHTML(this.xml); + return this.adaptor.serializeXML(this.xml); } /** From d78d1ab2b46d9035e5db6770af592fac4596f79d Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 5 Apr 2021 16:43:10 -0400 Subject: [PATCH 2/3] Fix missing > from previous commit --- ts/adaptors/lite/Parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/adaptors/lite/Parser.ts b/ts/adaptors/lite/Parser.ts index b7a443803..c5a49be86 100644 --- a/ts/adaptors/lite/Parser.ts +++ b/ts/adaptors/lite/Parser.ts @@ -357,7 +357,7 @@ export class LiteParser implements MinDOMParser { ).join(' '); const html = '<' + tag + (attributes ? ' ' + attributes : '') - + (SELF_CLOSING[tag] ? (xml ? ' />' : '>') : adaptor.innerHTML(node) + ''); + + (SELF_CLOSING[tag] ? (xml ? ' />' : '>') : '>' + adaptor.innerHTML(node) + ''); return html; } From 3abb3436ea194f9e639aa2bf7fdb2cbe5e39987e Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 14 Apr 2021 10:57:16 -0400 Subject: [PATCH 3/3] Use serializeInner rather than innerHTML to serialize, and do xml self-closing tags, not HTML ones --- ts/adaptors/lite/Parser.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ts/adaptors/lite/Parser.ts b/ts/adaptors/lite/Parser.ts index c5a49be86..e47412f85 100644 --- a/ts/adaptors/lite/Parser.ts +++ b/ts/adaptors/lite/Parser.ts @@ -355,9 +355,10 @@ export class LiteParser implements MinDOMParser { const attributes = adaptor.allAttributes(node).map( (x: AttributeData) => x.name + '="' + (CDATA[x.name] ? x.value : this.protectAttribute(x.value)) + '"' ).join(' '); + const content = this.serializeInner(adaptor, node, xml); const html = '<' + tag + (attributes ? ' ' + attributes : '') - + (SELF_CLOSING[tag] ? (xml ? ' />' : '>') : '>' + adaptor.innerHTML(node) + ''); + + (content && !SELF_CLOSING[tag] ? `>${content}` : xml ? '/>' : '>'); return html; } @@ -366,7 +367,7 @@ export class LiteParser implements MinDOMParser { * @param {LiteElement} node The node whose innerHTML is needed * @return {string} The serialized element (like innerHTML) */ - public serializeInner(adaptor: LiteAdaptor, node: LiteElement): string { + public serializeInner(adaptor: LiteAdaptor, node: LiteElement, xml: boolean = false): string { const PCDATA = (this.constructor as typeof LiteParser).PCDATA; if (PCDATA.hasOwnProperty(node.kind)) { return adaptor.childNodes(node).map(x => adaptor.value(x)).join(''); @@ -375,7 +376,7 @@ export class LiteParser implements MinDOMParser { const kind = adaptor.kind(x); return (kind === '#text' ? this.protectHTML(adaptor.value(x)) : kind === '#comment' ? (x as LiteComment).value : - this.serialize(adaptor, x as LiteElement)); + this.serialize(adaptor, x as LiteElement, xml)); }).join(''); }