From af24b2733660200f1bb9f87393be25926ed43d95 Mon Sep 17 00:00:00 2001 From: jszuminski Date: Wed, 31 Jul 2024 14:31:41 +0200 Subject: [PATCH 1/3] Fixed #547, add XLink namespace for SVG results. --- lib/server/routes/export.js | 15 ++++++++++++++- lib/utils.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/server/routes/export.js b/lib/server/routes/export.js index 4895a25b..7d20f731 100644 --- a/lib/server/routes/export.js +++ b/lib/server/routes/export.js @@ -23,7 +23,8 @@ import { isObjectEmpty, isPrivateRangeUrlFound, optionsStringify, - measureTime + measureTime, + addXlinkNamespace } from '../../utils.js'; import HttpError from '../../errors/HttpError.js'; @@ -249,6 +250,18 @@ const exportHandler = async (request, response, next) => { doCallbacks(afterRequest, request, response, { id, body: info.result }); if (info.result) { + // This exception is a workaround for #547 + // The plainly downloaded SVG is not properly formatted - + // it lacks the xmlns:xlink so images with "xlink:href" cannot be displayed + // and the entire SVG is deemed as incorrect (this may be a Highcharts + // problem as well as they should take care of this). + // A proper SVG has xlmns:xlink defined if they are used + // and Highcharts does not seem to have that for now. + // Once they do, we can get rid of this. + if (type === 'svg') { + info.result = addXlinkNamespace(info.result); + } + // If only base64 is required, return it if (body.b64) { // SVG Exception for the Highcharts 11.3.0 version diff --git a/lib/utils.js b/lib/utils.js index 6823c003..eb3b5205 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -448,6 +448,39 @@ export const measureTime = () => { return () => Number(process.hrtime.bigint() - start) / 1000000; }; +/** + * This method is used to add the xlink namespace to the SVG string. + * This may be a workaround, as Highcharts should take care of this. + * @param {string} svgString + * @returns {string} + */ +export const addXlinkNamespace = (svgString) => { + // Check if the xlink namespace is already present + const xlinkNamespace = 'xmlns:xlink="http://www.w3.org/1999/xlink"'; + if (svgString.includes(xlinkNamespace)) { + // The namespace is already included, no need to add it + return svgString; + } + + // Find the position of the opening tag + const svgTagEnd = svgString.indexOf('>'); + + // If is self-closing, find the position accordingly + const selfClosing = svgString[svgTagEnd - 1] === '/'; + + // Define the insertion point for the namespace attribute + const insertionPoint = selfClosing ? svgTagEnd - 1 : svgTagEnd; + + // Insert the xlink namespace declaration + const modifiedSvgString = + svgString.slice(0, insertionPoint) + + ' ' + + xlinkNamespace + + svgString.slice(insertionPoint); + + return modifiedSvgString; +}; + export default { __dirname, clearText, From 6146037f59bc55ced1e4762db2a8d1d4d97d536e Mon Sep 17 00:00:00 2001 From: jszuminski Date: Wed, 31 Jul 2024 14:41:57 +0200 Subject: [PATCH 2/3] Updated changelog about #547. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cce67244..4f4e0924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 4.0.1 + +_Fixes_: +- Fix the base64 images not working in exported SVGs (Namespace prefix xlink for href on image is not defined, [#547](https://github.com/highcharts/node-export-server/issues/547)). + # 4.0.0 _New Features:_ From ade4cc8a865c2905943fdc83754093e5ea21b58b Mon Sep 17 00:00:00 2001 From: jszuminski Date: Wed, 31 Jul 2024 14:47:13 +0200 Subject: [PATCH 3/3] Added unit tests for the adXlinkNamespace util method. --- tests/unit/utils.test.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/unit/utils.test.js b/tests/unit/utils.test.js index 7c89f52d..77a98e2e 100644 --- a/tests/unit/utils.test.js +++ b/tests/unit/utils.test.js @@ -6,7 +6,8 @@ import { isCorrectJSON, isObject, isObjectEmpty, - isPrivateRangeUrlFound + isPrivateRangeUrlFound, + addXlinkNamespace } from '../../lib/utils'; describe('clearText', () => { @@ -166,3 +167,29 @@ describe('isPrivateRangeUrlFound', () => { }); }); }); + +describe('addXlinkNamespace', () => { + it('adds the xlink namespace to an SVG string', () => { + const svg = ''; + const expected = + ''; + + expect(addXlinkNamespace(svg)).toBe(expected); + }); + + it('does not add the xlink namespace if it already exists', () => { + const svg = + ''; + expect(addXlinkNamespace(svg)).toBe(svg); + }); + + it('does add the xlink namespace properly for SVG tag with multiple attributes', () => { + const svg = + ''; + + const expected = + ''; + + expect(addXlinkNamespace(svg)).toBe(expected); + }); +});