diff --git a/package.json b/package.json index 7f765a74..5d4ef0d2 100644 --- a/package.json +++ b/package.json @@ -191,7 +191,6 @@ "version": "0.54.3" }, "dependencies": { - "@xmldom/xmldom": "^0.8.6", - "entities": "^4.4.0" + "@xmldom/xmldom": "^0.8.6" } } diff --git a/src/svg/SVGCore.ts b/src/svg/SVGCore.ts index 1fd4d6c3..3998c9b3 100644 --- a/src/svg/SVGCore.ts +++ b/src/svg/SVGCore.ts @@ -1,7 +1,6 @@ // source from https://github.com/ecomfe/zrender/blob/master/src/svg/core.ts // under BSD-3-Clause license // add some patch for skia -import { encodeXML } from 'entities'; export type SVGVNodeAttrs = Record< string, @@ -18,6 +17,65 @@ export interface SVGVNode { key: string; } +// Custom implementation of encodeXML to avoid issues with deprecated methods in entities package +// This is based on the entities package implementation but uses substring instead of substr +const xmlReplacer = /["&'<>$\x80-\uFFFF]/g; +const xmlCodeMap = new Map([ + [34, '"'], + [38, '&'], + [39, '''], + [60, '<'], + [62, '>'], +]); + +/** + * Gets the Unicode code point at the specified index in a string. + * Provides a fallback for environments that don't support String.prototype.codePointAt. + * Handles surrogate pairs correctly to get the full Unicode code point. + */ +const getCodePoint = + String.prototype.codePointAt != null + ? (str: string, index: number) => str.codePointAt(index)! + : // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + (str: string, index: number) => + // eslint-disable-next-line no-bitwise + (str.charCodeAt(index) & 0xfc00) === 0xd800 + ? (str.charCodeAt(index) - 0xd800) * 0x400 + + str.charCodeAt(index + 1) - + 0xdc00 + + 0x10000 + : str.charCodeAt(index); + +/** + * Encodes all non-ASCII characters and special XML characters using XML entities. + * Special characters (<, >, &, ", ') are encoded as named entities. + * Non-ASCII characters are encoded as numeric hexadecimal references (e.g., ü). + * + * @param str - The string to encode + * @returns The encoded string with XML entities + */ +function encodeXML(str: string): string { + let ret = ''; + let lastIdx = 0; + let match; + while ((match = xmlReplacer.exec(str)) !== null) { + const i = match.index; + const char = str.charCodeAt(i); + const next = xmlCodeMap.get(char); + if (next !== undefined) { + ret += str.substring(lastIdx, i) + next; + lastIdx = i + 1; + } else { + ret += `${str.substring(lastIdx, i)}&#x${getCodePoint(str, i).toString(16)};`; + // Increase by 1 if we have a surrogate pair + // eslint-disable-next-line no-bitwise + lastIdx = xmlReplacer.lastIndex += Number((char & 0xfc00) === 0xd800); + } + } + // Use substring instead of deprecated substr + return ret + str.substring(lastIdx); +} + function createElementOpen(name: string, attrs?: SVGVNodeAttrs) { const attrsStr: string[] = []; if (attrs) {