Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 4.0.3

_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.2

_Hotfix_:
Expand Down
2 changes: 0 additions & 2 deletions dist/index.cjs

This file was deleted.

2 changes: 0 additions & 2 deletions dist/index.esm.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/index.esm.js.map

This file was deleted.

6 changes: 1 addition & 5 deletions lib/highcharts.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,7 @@ export async function triggerExport(chartOptions, options, displayErrors) {
let constr = options.export.constr || 'chart';
constr = typeof Highcharts[constr] !== 'undefined' ? constr : 'chart';

Highcharts[constr](
'container',
finalOptions,
finalCallback
);
Highcharts[constr]('container', finalOptions, finalCallback);

// Get the current global options
const defaultOptions = getOptions();
Expand Down
15 changes: 14 additions & 1 deletion lib/server/routes/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
isObjectEmpty,
isPrivateRangeUrlFound,
optionsStringify,
measureTime
measureTime,
addXlinkNamespace
} from '../../utils.js';

import HttpError from '../../errors/HttpError.js';
Expand Down Expand Up @@ -262,6 +263,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, as 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
Expand Down
34 changes: 34 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,39 @@ const MAX_BACKOFF_ATTEMPTS = 6;

export const __dirname = fileURLToPath(new URL('../.', import.meta.url));

/**
* 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 <svg> tag
const svgTagEnd = svgString.indexOf('>');

// If <svg> 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;
};

/**
* Clears and standardizes text by replacing multiple consecutive whitespace
* characters with a single space and trimming any leading or trailing
Expand Down Expand Up @@ -450,6 +483,7 @@ export const measureTime = () => {

export default {
__dirname,
addXlinkNamespace,
clearText,
expBackoff,
fixType,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Highsoft AS <[email protected]> (http://www.highcharts.com/about)",
"license": "MIT",
"type": "module",
"version": "4.0.2",
"version": "4.0.3",
"main": "./dist/index.esm.js",
"engines": {
"node": ">=18.12.0"
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
addXlinkNamespace,
clearText,
fixType,
roundNumber,
Expand All @@ -9,6 +10,32 @@ import {
isPrivateRangeUrlFound
} from '../../lib/utils';

describe('addXlinkNamespace', () => {
it('adds the xlink namespace to an SVG string', () => {
const svg = '<svg><image xlink:href="http://localhost/image.jpg"/></svg>';
const expected =
'<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="http://localhost/image.jpg"/></svg>';

expect(addXlinkNamespace(svg)).toBe(expected);
});

it('does not add the xlink namespace if it already exists', () => {
const svg =
'<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="http://localhost/image.jpg"/></svg>';
expect(addXlinkNamespace(svg)).toBe(svg);
});

it('does add the xlink namespace properly for SVG tag with multiple attributes', () => {
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" custom_attr="0" width="200" height="200"><image xlink:href="http://localhost/image.jpg" width="100" height="100"/></svg>';

const expected =
'<svg xmlns="http://www.w3.org/2000/svg" custom_attr="0" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="http://localhost/image.jpg" width="100" height="100"/></svg>';

expect(addXlinkNamespace(svg)).toBe(expected);
});
});

describe('clearText', () => {
it('replaces multiple spaces with a single space and trims the text', () => {
const input = ' This is a test ';
Expand Down
Loading