diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2418563 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "env": { + "commonjs": true, + "es2020": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 12 + }, + "rules": { + "space-before-function-paren": ["warn", "always"], + "space-before-blocks": ["warn", "always"], + "func-call-spacing": ["warn", "always"], + "object-curly-spacing": ["warn", "never"], + "quotes": ["warn", "double"], + "indent": ["warn", "tab"] + } +} \ No newline at end of file diff --git a/README.md b/README.md index 42d07f7..976f33b 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,91 @@ xlsxChart.generate (opts, function (err, data) { }); ``` +## Available chart options +```js +var opts = { + file: "chart.xlsx", // exported file + type: "nodebuffer", // optional: used by JSZip library + charts: [ + { + chart: "column", // pie, doughnut, line, area, bar + grouping: "clustered", // optional: stacked, standard - allow to create stacked bar or line chart + titles: [ + "title1", // list of chart titles + "title2", // list of chart titles + ], + fields: [ + "field1", // list of chart fields + "field2", + ], + data: { + // chart: "column", // could be used to create multi-layer charts with e.g. column + bar + line + area chart in the same object + // grouping: "clustered", // same as common grouping option, but only for current layer; + "title1": { + "field1": 123, // structured data + "field2": 321, + }, + "title2": { + "field1": 456, + "field2": 654, + }, + }, + chartTitle: "Title", + position: { // optional: chart position + fromColumn: 0, // chart top left x coordinate in columns + fromColumnOffset: 0, // chart top left x coordinate in pixels + fromRow: n * 20, // chart top left y coordinate in columns + fromRowOffset: 0, // chart top left y coordinate in pixels + toColumn: 10, // chart bottom right x coordinate in columns + toColumnOffset: 0, // chart bottom right x coordinate in pixels + toRow: (n + 1) * 20, // chart bottom right y coordinate in columns + toRowOffset: 0, // chart bottom right y coordinate in pixels + }, + customColors: { // optional: chart colors + points: { + "title1": { + "field1": "FF0000", // colors in the same structure as data; not all points required + "field1": { + fill: "FF0000", // separate colors for fill and border could be set + line: "FF0000", + }, + }, + }, + series: { + "title1": "FF0000", // whole series color could be also set + "title2": { + fill: "FF0000", // separate colors for fill and border could be set + line: "FF0000", + markerColor: "FF0000", // different marker color also could be set for line chart + }, + }, + }, + manualLayout: { // optional blocks position on the chart canvas + plotArea: { // chart position + x: 0.5, // x and y of top left corner in proportion value 0..1 + y: 0.5, + w: 0.5, // width and height of block point in proportion value 0..1 + h: 0.5, + }, + title: { + x: 0.5, // x and y of top left corner in proportion value 0..1 + y: 0.5, + }, + }, + firstSliceAng: 270, // optional: first slice angle for pie and doughnut chart in degrees + holeSize: 50, // optional: hole size of doughnut chart in percent + legendPos: "r", // optional: 'l', 't', 'r', 'b', null - legend position; null to hide; right is default + deleteEmptyCells: false, // optional: allow to will empty values with 0 or leave as gaps + }, + ], +}; + +xlsxChart.writeFile (opts, function (err) { + console.log ("File: ", opts.file); +}); + +``` + ## Examples column.js diff --git a/chart/base.js b/chart/base.js index c780587..52d0aaf 100644 --- a/chart/base.js +++ b/chart/base.js @@ -5,6 +5,47 @@ var xml2js = require ("xml2js"); var VError = require ("verror"); var fs = require ("fs"); var async = require ("async"); + +const CHART_TAG_BY_CHART_NAME = { + bar: "c:barChart", + column: "c:barChart", + line: "c:lineChart", + radar: "c:radarChart", + area: "c:areaChart", + scatter: "c:scatterChart", + pie: "c:pieChart", + doughnut: "c:doughnutChart", +}; + +const CHART_GROUPING_BY_CHART_NAME = { + bar: "clustered", + column: "clustered", + line: "standard", + radar: undefined, // radar, scatter, pie and doughnut charts should not have "c:grouping" tag, otherwise rest of xml is ignored + area: "standard", + scatter: undefined, + pie: undefined, + doughnut: undefined, +}; + +const CHART_TYPES = ["bar", "column", "line", "radar", "area", "scatter", "pie", "doughnut"]; + +/** + * + * @param {number | null | undefined} value + * @param {{ deleteEmptyCells?: boolean }} opts + * @returns + */ +function normalizeValue (value, opts) { + if (!opts.deleteEmptyCells) { + return value || 0; + } + if (value || value === 0) { + return value; + } + return ""; +} + var Chart = Backbone.Model.extend ({ /* Read XML file from xlsx as object @@ -18,7 +59,7 @@ var Chart = Backbone.Model.extend ({ return new VError (err, "getXML"); } cb (err, o); - }); + }); }, /* Build XML from object and write to zip @@ -27,18 +68,18 @@ var Chart = Backbone.Model.extend ({ var me = this; var builder = new xml2js.Builder (); var xml = builder.buildObject (opts.object); - me.zip.file (opts.file, new Buffer (xml), {base64: true}); + me.zip.file (opts.file, Buffer.from (xml), {base64: true}); }, /* Get column name */ getColName: function (n) { var abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - n --; + n--; if (n < 26) { - return abc [n]; + return abc[n]; } else { - return abc [(n / 26 - 1) | 0] + abc [n % 26]; + return abc[(n / 26 - 1) | 0] + abc[n % 26]; } }, /* @@ -49,7 +90,7 @@ var Chart = Backbone.Model.extend ({ if (!me.str.hasOwnProperty (s)) { throw new VError ("getStr: Unknown string: " + s); } - return me.str [s]; + return me.str[s]; }, /* Write table @@ -95,7 +136,7 @@ var Chart = Backbone.Model.extend ({ $: { r: me.getColName (x + 2) + (y + 2) }, - v: me.data [t][f] + v: me.data[t][f] }); }); r.c = c; @@ -111,13 +152,13 @@ var Chart = Backbone.Model.extend ({ */ writeMultTable: function (row, cb) { let me = this; - + me.read ({file: "xl/worksheets/sheet2.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeMultTable")); } o.worksheet.dimension.$.ref = `A${row}:${me.getColName (me.titles.length + 1)}${me.fields.length + row + 1}`; - + let rows = [{ $: { r: row, @@ -164,7 +205,7 @@ var Chart = Backbone.Model.extend ({ $: { r: me.getColName (x + 2) + (y + 2 + row) }, - v: me.data [t][f] + v: me.data[t][f] }); }); r.c = c; @@ -184,16 +225,16 @@ var Chart = Backbone.Model.extend ({ */ writeStrings: function (cb) { let me = this; - + me.read ({file: "xl/sharedStrings.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeStrings")); } o.sst.$.count = me.titles.length + me.fields.length; o.sst.$.uniqueCount = o.sst.$.count; - + let si = []; - + _.each (me.titles, function (t) { si.push ({t: t}); }); @@ -201,9 +242,9 @@ var Chart = Backbone.Model.extend ({ si.push ({t: t}); }); me.str = {}; - + _.each (si, function (o, i) { - me.str [o.t] = i; + me.str[o.t] = i; }); o.sst.si = si; me.write ({file: "xl/sharedStrings.xml", object: o}); @@ -215,57 +256,71 @@ var Chart = Backbone.Model.extend ({ */ removeUnusedCharts: function (o) { var me = this; - if (me.tplName != "charts") { - return; - }; + + // axis ids that are used in chart layers var axId = []; function addId (o) { + if (!o ["c:axId"]) { + return; + } _.each (o ["c:axId"], function (o) { axId.push (o.$.val); }); }; - _.each (["line", "radar", "area", "scatter", "pie"], function (chart) { - if (!me.charts [chart]) { - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]; + + // remove chart layers that are not used in chart + _.each (CHART_TYPES, function (chart) { + if (chart == "column") { + chart = "bar"; + } + + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]) { + return; + } + + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"].length) { + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"], addId); } else { addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]); - }; + } }); - if (!me.charts ["column"] && !me.charts ["bar"]) { - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; - } else - if (me.charts ["column"] && !me.charts ["bar"]) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]; - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); - } else - if (!me.charts ["column"] && me.charts ["bar"]) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]; - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); - } else { - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]); - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]); - }; - var catAx = []; - _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"], function (o) { - if (axId.indexOf (o ["c:axId"].$.val) > -1) { - catAx.push (o); - }; - }); - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = catAx; + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"]) { + var catAx = []; + // if category axes is single - it's an object, otherwise it's an array of objects + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"].length) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = [o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"]]; + } - var valAx = []; - _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"], function (o) { - if (axId.indexOf (o ["c:axId"].$.val) > -1) { - valAx.push (o); - }; - }); - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = valAx; + // leave only category axes that are used in chart + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"], function (o) { + if (axId.indexOf (o ["c:axId"].$.val) > -1) { + catAx.push (o); + }; + }); + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = catAx; + } + + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"]) { + var valAx = []; + // if value axes is single - it's an object, otherwise it's an array of objects + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"].length) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = [o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"]]; + } + + // leave only value axes that are used in chart + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"], function (o) { + if (axId.indexOf (o ["c:axId"].$.val) > -1) { + valAx.push (o); + }; + }); + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = valAx; + } }, /* Write chart */ -/* + /* writeChart: function (chartN, cb) { var me = this; @@ -391,18 +446,123 @@ var Chart = Backbone.Model.extend ({ cb (); }); }, -*/ + */ writeChart: function (chartN, row, cb) { var me = this; - + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeChart")); } - var ser = {}; + var seriesByChart = {}; + const chartOpts = me.charts[chartN - 1]; _.each (me.titles, function (t, i) { - var chart = me.data [t].chart || me.chart; - var r = { + var chart = me.data[t].chart || me.chart; + var grouping = me.data[t].grouping || me.grouping || CHART_GROUPING_BY_CHART_NAME[chart]; + + var customColorsPoints = { + "c:dPt": [], + }; + var customColorsSeries = {}; + + if (chartOpts.customColors) { + const customColors = chartOpts.customColors; + + if (customColors.points) { + customColorsPoints["c:dPt"] = chartOpts.fields.map (function (field, i) { + const color = _.chain (customColors).get ("points").get (t).get (field, null).value (); + + if (!color) { + return null; + } + if (color === "noFill") { + return { + "c:idx": { + $: { + val: i, + }, + }, + "c:spPr": { + "a:noFill": "" + } + } + } + let fillColor = color; + let lineColor = color; + if (typeof color === "object") { + fillColor = color.fill; + lineColor = color.line; + } + return { + "c:idx": { + $: { + val: i, + }, + }, + "c:spPr": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: fillColor, + }, + }, + }, + "a:ln": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: lineColor, + }, + }, + }, + }, + }, + } + }).filter (Boolean); + } + + if (customColors.series && customColors.series[t]) { + let fillColor = customColors.series[t]; + let lineColor = customColors.series[t]; + let markerColor = customColors.series[t]; + if (typeof customColors.series === "object") { + fillColor = customColors.series[t].fill; + lineColor = customColors.series[t].line; + markerColor = customColors.series[t].marker; + } + customColorsSeries["c:spPr"] = { + "a:solidFill": { + "a:srgbClr": { + $: { + val: fillColor, + }, + }, + }, + "a:ln": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: lineColor, + }, + }, + }, + }, + "c:marker": { + "c:spPr": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: markerColor, + }, + }, + }, + }, + }, + }; + } + } + + var ser = { "c:idx": { $: { val: i @@ -431,6 +591,8 @@ var Chart = Backbone.Model.extend ({ } } }, + ...customColorsPoints, + ...customColorsSeries, "c:cat": { "c:strRef": { "c:f": "Table!$A$" + (row + 1) + ":$A$" + (me.fields.length + row), @@ -466,7 +628,7 @@ var Chart = Backbone.Model.extend ({ $: { idx: j }, - "c:v": me.data [t][f] + "c:v": me.data[t][f] }; }) } @@ -474,11 +636,11 @@ var Chart = Backbone.Model.extend ({ } }; if (chart == "scatter") { - r ["c:xVal"] = r ["c:cat"]; - delete r ["c:cat"]; - r ["c:yVal"] = r ["c:val"]; - delete r ["c:val"]; - r ["c:spPr"] = { + ser["c:xVal"] = ser["c:cat"]; + delete ser["c:cat"]; + ser["c:yVal"] = ser["c:val"]; + delete ser["c:val"]; + ser["c:spPr"] = { "a:ln": { $: { w: 28575 @@ -487,31 +649,176 @@ var Chart = Backbone.Model.extend ({ } }; }; - ser [chart] = ser [chart] || []; - ser [chart].push (r); + const seriesKey = `${chart}\r\r${grouping}`; + seriesByChart[seriesKey] = seriesByChart[seriesKey] || []; + seriesByChart[seriesKey].push (ser); }); - _.each (ser, function (ser, chart) { - if (chart == "column") { - if (me.tplName == "charts") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]["c:ser"] = ser; - } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; - }; - } else - if (chart == "bar") { - if (me.tplName == "charts") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]["c:ser"] = ser; - } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; + + const templateChartName = "c:" + CHART_TYPES.find ((chartType) => me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chartType}Chart`]) + "Chart"; + + // remove template barChart from the XML object; + o ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName] = []; + + _.each (seriesByChart, function (ser, chart) { + + var [chart, grouping] = chart.split ("\r\r"); + + const chartTagName = CHART_TAG_BY_CHART_NAME[chart]; + + if (!chartTagName) { + return cb (new VError (new Error (`Chart type '${chart}' is not supported`), "writeChart")); + } + + o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName] = o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName] || []; + // minimal chart config + let newChart = {}; + + if (chart == "column" || chart == "bar") { + // clone barChart from template + newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); + newChart["c:barDir"] = { + $: { + val: chart.substr (0, 3), + }, }; + + } else if (chart == "line" || chart == "area" || chart == "radar" || chart == "scatter") { + newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); + delete newChart["c:barDir"]; } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:ser"] = ser; + newChart["c:varyColors"] = { + $: { + val: 1, + }, + }; + + newChart["c:ser"] = ser; + if (chartOpts.firstSliceAng) { + newChart["c:firstSliceAng"] = { + $: { + val: chartOpts.firstSliceAng, + }, + }; + } + if (chartOpts.holeSize) { + newChart["c:holeSize"] = { + $: { + val: chartOpts.holeSize, + }, + }; + } + // if (chartOpts.showLabels) { + // newChart["c:dLbls"] = { + // "c:showLegendKey": { + // $: { + // val: 1, + // }, + // }, + // "c:showVal": { + // $: { + // val: 0, + // }, + // }, + // "c:showCatName": { + // $: { + // val: 0, + // }, + // }, + // "c:showSerName": { + // $: { + // val: 0, + // }, + // }, + // "c:showPercent": { + // $: { + // val: 0, + // }, + // }, + // "c:showBubbleSize": { + // $: { + // val: 0, + // }, + // }, + // "c:showLeaderLines": { + // $: { + // val: 1, + // }, + // }, + // }; + // } }; + + newChart["c:ser"] = ser; + + if (grouping != "undefined") { + newChart["c:grouping"] = { + $: { + val: grouping || CHART_GROUPING_BY_CHART_NAME[chart], + }, + }; + if (grouping == "stacked") { + newChart["c:overlap"] = { + $: { + val: 100, // usually stacked expected to be seen with overlap 100% + }, + }; + } + } + + o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName].push (newChart); + + if (chartOpts.legendPos === undefined || chartOpts.legendPos) { + o ["c:chartSpace"]["c:chart"]["c:legend"] = { + "c:legendPos": { + $: { + val: chartOpts.legendPos || "r", + }, + }, + }; + } else if (chartOpts.legendPos === null) { + delete o ["c:chartSpace"]["c:chart"]["c:legend"]; + } + + if (chartOpts.manualLayout && chartOpts.manualLayout.plotArea) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"] || {}; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"]["c:manualLayout"] = { + "c:xMode": { + $: { + val: "edge", + }, + }, + "c:yMode": { + $: { + val: "edge", + }, + }, + "c:x": { + $: { + val: chartOpts.manualLayout.plotArea.x, + }, + }, + "c:y": { + $: { + val: chartOpts.manualLayout.plotArea.y, + }, + }, + "c:w": { + $: { + val: chartOpts.manualLayout.plotArea.w, + }, + }, + "c:h": { + $: { + val: chartOpts.manualLayout.plotArea.h, + }, + }, + }; + } }); me.removeUnusedCharts (o); - + if (me.chartTitle) { - me.writeTitle (o, me.chartTitle); + me.writeTitle (o, me.chartTitle, chartOpts); }; me.write ({file: `xl/charts/chart${chartN}.xml`, object: o}); cb (); @@ -520,9 +827,36 @@ var Chart = Backbone.Model.extend ({ /* Chart title */ - writeTitle: function (chart, title) { + writeTitle: function (o, title, chartOpts = {}) { var me = this; - chart ["c:chartSpace"]["c:chart"]["c:title"] = { + const layout = {}; + + if (chartOpts.manualLayout && chartOpts.manualLayout.title) { + layout["c:manualLayout"] = { + "c:xMode": { + $: { + val: "edge", + }, + }, + "c:yMode": { + $: { + val: "edge", + }, + }, + "c:x": { + $: { + val: chartOpts.manualLayout.title.x, + }, + }, + "c:y": { + $: { + val: chartOpts.manualLayout.title.y, + }, + }, + }; + } + + o ["c:chartSpace"]["c:chart"]["c:title"] = { "c:tx": { "c:rich": { "a:bodyPr": {}, @@ -542,14 +876,14 @@ var Chart = Backbone.Model.extend ({ } } }, - "c:layout": {}, + "c:layout": layout, "c:overlay": { $: { val: "0" } } }; - chart ["c:chartSpace"]["c:chart"]["c:autoTitleDeleted"] = { + o ["c:chartSpace"]["c:chart"]["c:autoTitleDeleted"] = { $: { val: "0" } @@ -558,30 +892,39 @@ var Chart = Backbone.Model.extend ({ /* Set template name */ - setTemplateName: function () { + setTemplateName: function (opts) { var me = this; - var charts = {}; - _.each (me.data, function (o) { - charts [o.chart || me.chart] = true; + var chartTypes = { + ...getChartTypes (me), + }; + + _.each (opts.charts, function (chart) { + chartTypes = { + ...chartTypes, + ...getChartTypes (chart), + }; }); - me.charts = charts; - if (charts ["radar"]) { + me.charts = opts.charts || [opts]; + + me.chartTypes = chartTypes; + + if (chartTypes ["radar"]) { me.tplName = "radar"; return; }; - if (charts ["scatter"]) { + if (chartTypes["scatter"]) { me.tplName = "scatter"; return; }; - if (charts ["pie"]) { + if (chartTypes["pie"]) { me.tplName = "pie"; return; }; - if (_.keys (charts).length == 1) { - me.tplName = _.keys (charts) [0]; + if (_.keys (chartTypes).length == 1) { + me.tplName = _.keys (chartTypes)[0]; return; }; - me.tplName = "charts"; + me.tplName = "bar"; }, /* Generate XLSX with chart @@ -597,25 +940,36 @@ var Chart = Backbone.Model.extend ({ async.series ([ function (cb) { me.zip = new JSZip (); - me.setTemplateName (); + me.setTemplateName (opts); let path = me.templatePath ? me.templatePath : (__dirname + "/../template/" + me.tplName + ".xlsx"); - fs.readFile(path, function (err, data) { + fs.readFile (path, function (err, data) { if (err) { - console.error(`Template ${path} not read: ${err}`); + console.error (`Template ${path} not read: ${err}`); return cb (err); }; me.zip.load (data); cb (); }); }, + function (cb) { + // save template chart so it could be easily removed before adding charts according to config + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { + if (err) { + return cb (new VError (err, "writeChart")); + } + me.chartTemplate = o; + cb (); + }); + }, function (cb) { me.writeStrings (cb); }, function (cb) { _.each (me.titles, function (t) { - me.data [t] = me.data [t] || {}; + me.data[t] = me.data[t] || {}; _.each (me.fields, function (f) { - me.data [t][f] = me.data [t][f] || (me.deleteEmptyCells ? '' : 0); //deleteEmptyCells - don't display missing values as 0 + const value = normalizeValue (me.data[t][f], me); + me.data[t][f] = value; }); }); me.writeTable (cb); @@ -633,19 +987,21 @@ var Chart = Backbone.Model.extend ({ }, generateMult: function (opts, cb) { let me = this; - + opts.type = opts.type || "nodebuffer"; _.extend (me, opts); + + me.setTemplateName (opts); async.series ([ function (cb) { me.zip = new JSZip (); - + let path = me.templatePath || (__dirname + "/../template/mult.xlsx"); - + fs.readFile (path, function (err, data) { if (err) { - console.error(`Template ${path} not read: ${err}`); + console.error (`Template ${path} not read: ${err}`); return cb (err); } me.zip.load (data); @@ -655,7 +1011,7 @@ var Chart = Backbone.Model.extend ({ function (cb) { me.titles = []; me.fields = []; - + me.charts.forEach (chart => { me.titles = [...me.titles, ...chart.titles]; me.fields = [...me.fields, ...chart.fields]; @@ -665,15 +1021,18 @@ var Chart = Backbone.Model.extend ({ }, function (cb) { let row = 1; - + async.eachSeries (me.charts, (chart, cb) => { - ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me [a] = chart [a]); - + ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me[a] = chart[a]); + + const deleteEmptyCells = me.deleteEmptyCells || chart.deleteEmptyCells; + _.each (me.titles, function (t) { - me.data [t] = me.data [t] || {}; - + me.data[t] = me.data[t] || {}; + _.each (me.fields, function (f) { - me.data [t][f] = me.data [t][f] || 0; + const value = normalizeValue (me.data[t][f], {deleteEmptyCells}); + me.data[t][f] = value; }); }); me.writeMultTable (row, () => { @@ -685,17 +1044,40 @@ var Chart = Backbone.Model.extend ({ function (cb) { let n = 0; let row = 2; - + async.eachSeries (me.charts, (chart, cb) => { - ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me [a] = chart [a]); - + ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me[a] = chart[a]); + + const position = Object.assign ({ + fromColumn: 0, + fromColumnOffset: 0, + fromRow: n * 20, + fromRowOffset: 0, + toColumn: 10, + toColumnOffset: 0, + toRow: (n + 1) * 20, + toRowOffset: 0, + }, + chart.position + ); + async.series ([ function (cb) { - me.writeChart (++ n, row, cb); + // save template chart so it could be easily removed before adding charts according to config + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { + if (err) { + return cb (new VError (err, "writeChart")); + } + me.chartTemplate = o; + cb (); + }); + }, + function (cb) { + me.writeChart (++n, row, cb); }, function (cb) { row += 3 + me.fields.length; - + if (n == 1) { return cb (); } @@ -709,7 +1091,7 @@ var Chart = Backbone.Model.extend ({ "PartName": `/xl/charts/chart${n}.xml` } }); - me.write ({file: `[Content_Types].xml`, object: o}); + me.write ({file: "[Content_Types].xml", object: o}); cb (); }); }, @@ -731,33 +1113,42 @@ var Chart = Backbone.Model.extend ({ "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" } }); - me.write ({file: `xl/drawings/_rels/drawing1.xml.rels`, object: o}); + me.write ({file: "xl/drawings/_rels/drawing1.xml.rels", object: o}); cb (); }); }, function (cb) { - if (n == 1) { - return cb (); - } me.read ({file: "xl/drawings/drawing1.xml"}, function (err, o) { if (err) { return cb (new VError (err, "generateMult")); } + if (n == 1) { + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:col"] = position.fromColumn; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:colOff"] = position.fromColumnOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:row"] = position.fromRow; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:rowOff"] = position.fromRowOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:col"] = position.toColumn; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:colOff"] = position.toColumnOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:row"] = position.toRow; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:rowOff"] = position.toRowOffset; + me.write ({file: "xl/drawings/drawing1.xml", object: o}); + return cb (); + } if (n == 2) { o ["xdr:wsDr"]["xdr:twoCellAnchor"] = [o ["xdr:wsDr"]["xdr:twoCellAnchor"]]; } o ["xdr:wsDr"]["xdr:twoCellAnchor"].push ({ "xdr:from": { - "xdr:col": 0, - "xdr:colOff": 0, - "xdr:row": (n - 1) * 20, - "xdr:rowOff": 0 + "xdr:col": position.fromColumn, + "xdr:colOff": position.fromColumnOffset, + "xdr:row": position.fromRow, + "xdr:rowOff": position.fromRowOffset }, "xdr:to": { - "xdr:col": 10, - "xdr:colOff": 0, - "xdr:row": n * 20, - "xdr:rowOff": 0 + "xdr:col": position.toColumn, + "xdr:colOff": position.toColumnOffset, + "xdr:row": position.toRow, + "xdr:rowOff": position.toRowOffset }, "xdr:graphicFrame": { "$": { @@ -767,7 +1158,7 @@ var Chart = Backbone.Model.extend ({ "xdr:cNvPr": { "$": { "id": `${n + 1}`, - "name": `Диаграмма ${n}` + "name": `Diagram ${n}` } }, "xdr:cNvGraphicFramePr": {} @@ -803,7 +1194,7 @@ var Chart = Backbone.Model.extend ({ }, "xdr:clientData": {} }); - me.write ({file: `xl/drawings/drawing1.xml`, object: o}); + me.write ({file: "xl/drawings/drawing1.xml", object: o}); cb (); }); } @@ -815,9 +1206,27 @@ var Chart = Backbone.Model.extend ({ return cb (new VError (err, "build")); } let result = me.zip.generate ({type: me.type}); - + cb (null, result); }); } }); module.exports = Chart; + +/** + * + * @param {Object} chart + * @returns {Object.} + */ +const getChartTypes = (chart) => { + const chartTypes = {}; + if (chart.chart) { + chartTypes[chart.chart] = true; + } + _.each (chart.data, function (series) { + if (series.chart) { + chartTypes[series.chart] = true; + } + }); + return chartTypes; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..210704c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1056 @@ +{ + "name": "xlsx-chart", + "version": "0.4.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "requires": { + "underscore": ">=1.8.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "extsprintf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jszip": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", + "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", + "requires": { + "pako": "~1.0.2" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "underscore": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 159cdbb..61f9d15 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,13 @@ "verror": "^1.6.0", "xml2js": "^0.4.13" }, - "devDependencies": {}, + "devDependencies": { + "eslint": "^7.30.0" + }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix" }, "repository": { "type": "git",