From 71f36067973b4172b8db8ddef82c1e32ec5946d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Mon, 3 Jun 2024 09:32:20 +0800 Subject: [PATCH] Increase the probability that removeUnnecessarySpan and mergeNode will be triggered. --- .../lib/modelToDom/optimizers/optimize.ts | 20 +++++- .../optimizers/removeUnnecessarySpan.ts | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts index eed720aa157..f4903239a02 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts @@ -1,22 +1,36 @@ +import { enumerateComputedStyle, removeUnnecessaryAttribute, removeUnnecessarySpan, removeUnnecessaryStyle } from './removeUnnecessarySpan'; import { isEntityElement } from '../../domUtils/entityUtils'; import { mergeNode } from './mergeNode'; -import { removeUnnecessarySpan } from './removeUnnecessarySpan'; /** * @internal */ -export function optimize(root: Node) { +export function optimize(root: Node, isRecursive = false) { /** * Do no do any optimization to entity */ if (isEntityElement(root)) { return; } + if (!isRecursive && root.parentElement != null) { + let computedAttributes = {} as Record; + // html doesn't provide computed attributes, use parent's attributes directly + Array.from(root.parentElement.attributes).forEach((attr) => { + computedAttributes[attr.name] = attr; + }); + removeUnnecessaryAttribute(root, computedAttributes); + + let computedStyle = {} as Record>; + enumerateComputedStyle(root.parentElement, (key, values) => { + computedStyle[key] = values; + }); + removeUnnecessaryStyle(root, computedStyle); + } removeUnnecessarySpan(root); mergeNode(root); for (let child = root.firstChild; child; child = child.nextSibling) { - optimize(child); + optimize(child, true); } } diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts index 3b0ccb11b88..0f0e93d34fc 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts @@ -1,5 +1,72 @@ import { isNodeOfType } from '../../domUtils/isNodeOfType'; +/** + * @internal + */ +export function removeUnnecessaryAttribute(root: Node, computedAttributes: Record) { + if (!isNodeOfType(root, 'ELEMENT_NODE')) { + return + } + let newComputedAttributes = { + ...computedAttributes, + }; + for (let i = root.attributes.length - 1; i >= 0; i--) { + const attr = root.attributes[i]; + if (newComputedAttributes[attr.name]?.isEqualNode(attr) ?? false) { + root.removeAttribute(attr.name); + } else { + newComputedAttributes[attr.name] = attr; + } + } + + for (let child = root.firstChild; child; child = child.nextSibling) { + removeUnnecessaryAttribute(child, newComputedAttributes); + } +} + +/** + * @internal + */ +export function enumerateComputedStyle(element: HTMLElement, handler: (key: string, value: Set) => void) { + element.style.cssText.split(";").forEach((value) => { + let [key, valueText] = value.split(":"); + if (!key || !valueText) { + return; + } + key = key.trim(); + let values = new Set(valueText.split(",").map((value) => value.trim())); + + handler(key, values); + }); +} + +/** + * @internal + */ +export function removeUnnecessaryStyle(root: Node, computedCSS: Record>) { + if (!isNodeOfType(root, 'ELEMENT_NODE')) { + return + } + let newComputedCSS = { + ...computedCSS + } + enumerateComputedStyle(root, (key, values) => { + if (computedCSS[key]?.size === values.size && [...computedCSS[key]].every((value) => values.has(value))) { + root.style.removeProperty(key); + } else { + newComputedCSS[key] = values; + } + }); + + if (root.style.cssText === "") { + root.removeAttribute("style"); + } + + for (let child = root.firstChild; child; child = child.nextSibling) { + removeUnnecessaryStyle(child, newComputedCSS); + } +} + /** * @internal */