From 578c9732a27183406894960da4cb3330df5346ca Mon Sep 17 00:00:00 2001 From: oysteinmoseng Date: Fri, 18 Nov 2016 12:56:46 +0100 Subject: [PATCH] Fixed #5985, issue with allowHTML in offline exporting. --- js/modules/exporting.src.js | 51 ++++++++----------- js/modules/offline-exporting.src.js | 31 ++++++++--- .../offline-download-usehtml/demo.css | 5 ++ .../offline-download-usehtml/demo.details | 6 +++ .../offline-download-usehtml/demo.html | 5 ++ .../offline-download-usehtml/demo.js | 44 ++++++++++++++++ 6 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 samples/highcharts/exporting/offline-download-usehtml/demo.css create mode 100644 samples/highcharts/exporting/offline-download-usehtml/demo.details create mode 100644 samples/highcharts/exporting/offline-download-usehtml/demo.html create mode 100644 samples/highcharts/exporting/offline-download-usehtml/demo.js diff --git a/js/modules/exporting.src.js b/js/modules/exporting.src.js index b4427c6a612..82bbcd4da52 100644 --- a/js/modules/exporting.src.js +++ b/js/modules/exporting.src.js @@ -195,10 +195,25 @@ H.post = function (url, data, formAttributes) { extend(Chart.prototype, { /** - * A collection of regex fixes on the produces SVG to account for expando properties, + * A collection of fixes on the produced SVG to account for expando properties, * browser bugs, VML problems and other. Returns a cleaned SVG. */ - sanitizeSVG: function (svg) { + sanitizeSVG: function (svg, options) { + // Move HTML into a foreignObject + if (options && options.exporting && options.exporting.allowHTML) { + var html = svg.match(/<\/svg>(.*?$)/); + if (html) { + html = '' + + '' + + html[1] + + '' + + ''; + svg = svg.replace('', html + ''); + } + } + svg = svg .replace(/zIndex="[^"]+"/g, '') .replace(/isShadow="[^"]+"/g, '') @@ -267,9 +282,7 @@ extend(Chart.prototype, { sourceHeight, cssWidth, cssHeight, - html, - options = merge(chart.options, additionalOptions), // copy the options and add extra options - allowHTML = options.exporting.allowHTML; + options = merge(chart.options, additionalOptions); // copy the options and add extra options // IE compatibility hack for generating SVG content that it doesn't really understand @@ -352,36 +365,16 @@ extend(Chart.prototype, { }); }); - // get the SVG from the container's innerHTML + // Get the SVG from the container's innerHTML svg = chartCopy.getChartHTML(); + svg = chart.sanitizeSVG(svg, options); + // free up memory options = null; chartCopy.destroy(); discardElement(sandbox); - - // Move HTML into a foreignObject - if (allowHTML) { - html = svg.match(/<\/svg>(.*?$)/); - if (html) { - html = '' + - '' + - html[1] + - '' + - ''; - svg = svg.replace('', html + ''); - } - } - - // sanitize - svg = this.sanitizeSVG(svg); - - // IE9 beta bugs with innerHTML. Test again with final IE9. - svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1') - .replace(/"/g, '\''); - + return svg; }, diff --git a/js/modules/offline-exporting.src.js b/js/modules/offline-exporting.src.js index 6ad7728663e..747e80dc5a8 100644 --- a/js/modules/offline-exporting.src.js +++ b/js/modules/offline-exporting.src.js @@ -310,9 +310,14 @@ Highcharts.Chart.prototype.getSVGForLocalExport = function (options, chartOption images, imagesEmbedded = 0, chartCopyContainer, + chartCopyOptions, el, i, l, + // After grabbing the SVG of the chart's copy container we need to do sanitation on the SVG + sanitize = function (svg) { + return chart.sanitizeSVG(svg, chartCopyOptions); + }, // Success handler, we converted image to base64! embeddedSuccess = function (imageURL, imageType, callbackArgs) { ++imagesEmbedded; @@ -322,7 +327,7 @@ Highcharts.Chart.prototype.getSVGForLocalExport = function (options, chartOption // When done with last image we have our SVG if (imagesEmbedded === images.length) { - successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML)); + successCallback(sanitize(chartCopyContainer.innerHTML)); } }; @@ -335,6 +340,7 @@ Highcharts.Chart.prototype.getSVGForLocalExport = function (options, chartOption this, Array.prototype.slice.call(arguments, 1) ); + chartCopyOptions = this.options; chartCopyContainer = this.container.cloneNode(true); return ret; } @@ -347,7 +353,7 @@ Highcharts.Chart.prototype.getSVGForLocalExport = function (options, chartOption try { // If there are no images to embed, the SVG is okay now. if (!images.length) { - successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML)); // Use SVG of chart copy + successCallback(sanitize(chartCopyContainer.innerHTML)); // Use SVG of chart copy return; } @@ -387,12 +393,25 @@ Highcharts.Chart.prototype.exportChartLocal = function (exportingOptions, chartO } }, svgSuccess = function (svg) { - Highcharts.downloadSVGLocal(svg, options, fallbackToExportServer); + // If SVG contains foreignObjects all exports except SVG will fail, + // as both CanVG and svg2pdf choke on this. Gracefully fall back. + if ( + svg.indexOf(' -1 && + options.type !== 'image/svg+xml' + ) { + fallbackToExportServer(); + } else { + Highcharts.downloadSVGLocal(svg, options, fallbackToExportServer); + } }; - // If we have embedded images and are exporting to JPEG/PNG, Microsoft browsers won't handle it, so fall back. - // Also fall back for embedded images with PDF. - if ((isMSBrowser && options.type !== 'image/svg+xml' || options.type === 'application/pdf') && chart.container.getElementsByTagName('image').length) { + // If we have embedded images and are exporting to JPEG/PNG, Microsoft + // browsers won't handle it, so fall back. + if ( + (isMSBrowser && options.type !== 'image/svg+xml' || + options.type === 'application/pdf') && + chart.container.getElementsByTagName('image').length + ) { fallbackToExportServer(); return; } diff --git a/samples/highcharts/exporting/offline-download-usehtml/demo.css b/samples/highcharts/exporting/offline-download-usehtml/demo.css new file mode 100644 index 00000000000..8dd694a4599 --- /dev/null +++ b/samples/highcharts/exporting/offline-download-usehtml/demo.css @@ -0,0 +1,5 @@ +#container { + max-width: 800px; + height: 400px; + margin: 1em auto; +} \ No newline at end of file diff --git a/samples/highcharts/exporting/offline-download-usehtml/demo.details b/samples/highcharts/exporting/offline-download-usehtml/demo.details new file mode 100644 index 00000000000..aef6a32969a --- /dev/null +++ b/samples/highcharts/exporting/offline-download-usehtml/demo.details @@ -0,0 +1,6 @@ +--- + name: Highcharts Demo + authors: + - Øystein Moseng + requiresManualTesting: true +... \ No newline at end of file diff --git a/samples/highcharts/exporting/offline-download-usehtml/demo.html b/samples/highcharts/exporting/offline-download-usehtml/demo.html new file mode 100644 index 00000000000..4ee9de7609b --- /dev/null +++ b/samples/highcharts/exporting/offline-download-usehtml/demo.html @@ -0,0 +1,5 @@ + + + + +
diff --git a/samples/highcharts/exporting/offline-download-usehtml/demo.js b/samples/highcharts/exporting/offline-download-usehtml/demo.js new file mode 100644 index 00000000000..925c66e2b40 --- /dev/null +++ b/samples/highcharts/exporting/offline-download-usehtml/demo.js @@ -0,0 +1,44 @@ +$(function () { + + Highcharts.chart('container', { + + exporting: { + chartOptions: { // specific options for the exported image + plotOptions: { + series: { + dataLabels: { + useHTML: true, + enabled: true + } + } + } + }, + scale: 3, + fallbackToExportServer: false, + allowHTML: true + }, + + title: { + text: 'Offline export' + }, + + subtitle: { + text: 'Should fail for all except SVG' + }, + + chart: { + type: 'area' + }, + + xAxis: { + categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + }, + + series: [{ + data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 126.0, 148.5, 216.4, 194.1, 95.6, 54.4] + }] + + }); + +});