drawio/war/plugins/csv.js
Gaudenz Alder f34054f9a6 6.0.2.16 release
Former-commit-id: 3c3a7f8e16
2017-01-30 16:15:20 +01:00

492 lines
16 KiB
JavaScript

/**
* Explore plugin.
*/
Draw.loadPlugin(function(ui)
{
// Adds resource for action
mxResources.parse('fromCsv=From CSV...');
// Return array of string values, or NULL if CSV string not well formed.
function CSVtoArray(text)
{
var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
// Return NULL if input string is not well formed CSV string.
if (!re_valid.test(text)) return null;
var a = []; // Initialize array to receive values.
text.replace(re_value, // "Walk" the string using replace with callback.
function(m0, m1, m2, m3) {
// Remove backslash from \' in single quoted values.
if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
// Remove backslash from \" in double quoted values.
else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
else if (m3 !== undefined) a.push(m3);
return ''; // Return empty string.
});
// Handle special case of empty last value.
if (/,\s*$/.test(text)) a.push('');
return a;
};
var defaultCsvValue = '##\n' +
'## Example CSV import. Use ## for comments and # for configuration. Paste CSV below.\n' +
'##\n' +
'#\n' +
'## Node label with placeholders and HTML.\n' +
'## Default is \'%name_of_first_column%\'.\n' +
'#\n' +
'# label: %name%<br><i style="color:gray;">%position%</i><br><a href="mailto:%email%">Email</a>\n' +
'#\n' +
'## Node style (placeholders are replaced once).\n' +
'## Default is the current style for nodes.\n' +
'#\n' +
'# style: label;image=%image%;whiteSpace=wrap;html=1;rounded=1;fillColor=%fill%;strokeColor=%stroke%;\n' +
'#\n' +
'## Connections between rows ("from": source colum, "to": target column).\n' +
'## Label, style and invert are optional. Defaults are \'\', current style and false.\n' +
'## The target column may contain a comma-separated list of values.\n' +
'## Multiple connect entries are allowed.\n' +
'#\n' +
'# connect: {"from": "manager", "to": "name", "invert": true, "label": "manages", \\\n' +
'# "style": "curved=1;endArrow=blockThin;endFill=1;fontSize=11;"}\n' +
'# connect: {"from": "refs", "to": "id", "style": "curved=1;fontSize=11;"}\n' +
'#\n' +
'## Node width. Possible value are px or auto. Default is auto.\n' +
'#\n' +
'# width: auto\n' +
'#\n' +
'## Node height. Possible value are px or auto. Default is auto.\n' +
'#\n' +
'# height: auto\n' +
'#\n' +
'## Padding for autosize. Default is 0.\n' +
'#\n' +
'# padding: -26\n' +
'#\n' +
'## Comma-separated list of ignored columns for metadata.\n' +
'#\n' +
'# ignore: id,image,fill,stroke\n' +
'#\n' +
'## Column to be renamed to link attribute (used as link).\n' +
'#\n' +
'# link: url\n' +
'#\n' +
'## Spacing between nodes. Default is 40.\n' +
'#\n' +
'# nodespacing: 40\n' +
'#\n' +
'## Spacing between parallel edges. Default is 40.\n' +
'#\n' +
'# edgespacing: 40\n' +
'#\n' +
'## Name of layout. Possible values are auto, none, verticaltree, horizontaltree,\n' +
'## verticalflow, horizontalflow, organic, circle. Default is auto.\n' +
'#\n' +
'# layout: auto\n' +
'#\n' +
'## ---- CSV below this line ----\n' +
'name,position,id,location,manager,email,fill,stroke,refs,url,image\n' +
'Evan Miller,CFO,emi,Office 1,,me@example.com,#dae8fc,#6c8ebf,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-9-2-128.png\n' +
'Edward Morrison,Brand Manager,emo,Office 2,Evan Miller,me@example.com,#d5e8d4,#82b366,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-10-3-128.png\n' +
'Ron Donovan,System Admin,rdo,Office 3,Evan Miller,me@example.com,#d5e8d4,#82b366,"emo,tva",https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-2-128.png\n' +
'Tessa Valet,HR Director,tva,Office 4,Evan Miller,me@example.com,#d5e8d4,#82b366,,https://www.draw.io,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-3-128.png\n';
var dlg = null;
// Adds action
ui.actions.addAction('fromCsv', function()
{
if (dlg == null)
{
dlg = new TextareaDialog(ui, mxResources.get('fromCsv') + ':',
defaultCsvValue, function(newValue)
{
try
{
var lines = newValue.split('\n');
var cells = [];
if (lines.length > 0)
{
// Internal lookup table
var lookups = {};
// Default values
var style = null;
var width = 'auto';
var height = 'auto';
var edgespacing = 40;
var nodespacing = 40;
var padding = 0;
var graph = ui.editor.graph;
var view = graph.view;
var bds = graph.getGraphBounds();
// Delayed after optional layout
var afterInsert = function()
{
graph.setSelectionCells(select);
graph.scrollCellToVisible(graph.getSelectionCell());
};
// Computes unscaled, untranslated graph bounds
var pt = graph.getFreeInsertPoint();
var x0 = pt.x;
var y0 = pt.y;
var y = y0;
// Default label value depends on column names
var label = null;
// Default layout to run.
var layout = 'auto';
// Name of the attribute that contains the parent reference
var parent = null;
// Name of the attribute that contains the references for creating edges
var edges = [];
// Name of the column for hyperlinks
var link = null;
// String array of names to remove from metadata
var ignore = null;
// Read processing instructions first
var index = 0;
while (index < lines.length && lines[index].charAt(0) == '#')
{
var text = lines[index];
index++;
while (index < lines.length && text.charAt(text.length - 1) == '\\' &&
lines[index].charAt(0) == '#')
{
text = text.substring(0, text.length - 1) + mxUtils.trim(lines[index].substring(1));
index++;
}
if (text.charAt(1) != '#')
{
// Processing instruction
var idx = text.indexOf(':');
if (idx > 0)
{
var key = mxUtils.trim(text.substring(1, idx));
var value = mxUtils.trim(text.substring(idx + 1));
if (key == 'label')
{
label = graph.sanitizeHtml(value);
}
else if (key == 'style')
{
style = value;
}
else if (key == 'width')
{
width = value;
}
else if (key == 'height')
{
height = value;
}
else if (key == 'ignore')
{
ignore = value.split(',');
}
else if (key == 'connect')
{
edges.push(JSON.parse(value));
}
else if (key == 'link')
{
link = value;
}
else if (key == 'padding')
{
padding = parseFloat(value);
}
else if (key == 'edgespacing')
{
edgespacing = parseFloat(value);
}
else if (key == 'nodespacing')
{
nodespacing = parseFloat(value);
}
else if (key == 'layout')
{
layout = value;
}
}
}
}
var keys = CSVtoArray(lines[index]);
if (label == null)
{
label = '%' + keys[0] + '%';
}
if (edges != null)
{
for (var e = 0; e < edges.length; e++)
{
if (lookups[edges[e].to] == null)
{
lookups[edges[e].to] = {};
}
}
}
graph.model.beginUpdate();
try
{
for (var i = index + 1; i < lines.length; i++)
{
var values = CSVtoArray(lines[i]);
if (values.length == keys.length)
{
var cell = new mxCell(label, new mxGeometry(x0, y,
0, 0), style || 'whiteSpace=wrap;html=1;');
cell.vertex = true;
for (var j = 0; j < values.length; j++)
{
graph.setAttributeForCell(cell, keys[j], values[j]);
}
graph.setAttributeForCell(cell, 'placeholders', '1');
cell.style = graph.replacePlaceholders(cell, cell.style);
for (var e = 0; e < edges.length; e++)
{
lookups[edges[e].to][cell.getAttribute(edges[e].to)] = cell;
}
if (link != null && link != 'link')
{
graph.setLinkForCell(cell, cell.getAttribute(link));
// Removes attribute
graph.setAttributeForCell(cell, link, null);
}
// Removes ignored attributes after processing above
if (ignore != null)
{
for (var j = 0; j < ignore.length; j++)
{
graph.setAttributeForCell(cell, ignore[j], null);
}
}
// Sets the size
var size = ui.editor.graph.getPreferredSizeForCell(cell);
cell.geometry.width = (width == 'auto') ? size.width + padding : parseFloat(width);
cell.geometry.height = (height == 'auto') ? size.height + padding : parseFloat(height);
y += cell.geometry.height + nodespacing;
cells.push(graph.addCell(cell));
}
}
if (style == null)
{
graph.fireEvent(new mxEventObject('cellsInserted', 'cells', cells));
}
var roots = cells.slice();
var select = cells.slice();
for (var e = 0; e < edges.length; e++)
{
var edge = edges[e];
for (var i = 0; i < cells.length; i++)
{
var cell = cells[i];
var tmp = cell.getAttribute(edge.from);
if (tmp != null)
{
// Removes attribute
graph.setAttributeForCell(cell, edge.from, null);
var refs = tmp.split(',');
for (var j = 0; j < refs.length; j++)
{
var ref = lookups[edge.to][refs[j]];
if (ref != null)
{
select.push(graph.insertEdge(null, null, edge.label || '',
(edge.invert) ? ref : cell, (edge.invert) ? cell : ref,
edge.style || graph.createCurrentEdgeStyle()));
mxUtils.remove((edge.invert) ? cell : ref, roots);
}
}
}
}
}
var edgeLayout = new mxParallelEdgeLayout(graph);
edgeLayout.spacing = edgespacing;
var postProcess = function()
{
edgeLayout.execute(graph.getDefaultParent());
// Aligns cells to grid and/or rounds positions
for (var i = 0; i < cells.length; i++)
{
var geo = graph.getCellGeometry(cells[i]);
geo.x = Math.round(graph.snap(geo.x));
geo.y = Math.round(graph.snap(geo.y));
if (width == 'auto')
{
geo.width = Math.round(graph.snap(geo.width));
}
if (height == 'auto')
{
geo.height = Math.round(graph.snap(geo.height));
}
}
};
if (layout == 'circle')
{
var circleLayout = new mxCircleLayout(graph);
circleLayout.resetEdges = false;
var circleLayoutIsVertexIgnored = circleLayout.isVertexIgnored;
// Ignore other cells
circleLayout.isVertexIgnored = function(vertex)
{
return circleLayoutIsVertexIgnored.apply(this, arguments) ||
mxUtils.indexOf(cells, vertex) < 0;
};
ui.executeLayout(function()
{
circleLayout.execute(graph.getDefaultParent());
postProcess();
}, true, afterInsert);
afterInsert = null;
}
else if (layout == 'horizontaltree' || layout == 'verticaltree' ||
(layout == 'auto' && select.length == 2 * cells.length - 1 && roots.length == 1))
{
// Required for layouts to work with new cells
graph.view.validate();
var treeLayout = new mxCompactTreeLayout(graph, layout == 'horizontaltree');
treeLayout.levelDistance = nodespacing;
treeLayout.edgeRouting = false;
ui.executeLayout(function()
{
treeLayout.execute(graph.getDefaultParent(), (roots.length > 0) ? roots[0] : null);
}, true, afterInsert);
afterInsert = null;
}
else if (layout == 'horizontalflow' || layout == 'verticalflow' ||
(layout == 'auto' && roots.length == 1))
{
// Required for layouts to work with new cells
graph.view.validate();
var flowLayout = new mxHierarchicalLayout(graph,
(layout == 'horizontalflow') ? mxConstants.DIRECTION_WEST : mxConstants.DIRECTION_NORTH);
flowLayout.intraCellSpacing = nodespacing;
ui.executeLayout(function()
{
flowLayout.execute(graph.getDefaultParent(), select);
// Workaround for flow layout moving cells to origin
graph.moveCells(select, x0, y0);
}, true, afterInsert);
afterInsert = null;
}
else if (layout == 'organic' || (layout == 'auto' &&
select.length > cells.length))
{
// Required for layouts to work with new cells
graph.view.validate();
var organicLayout = new mxFastOrganicLayout(graph);
organicLayout.forceConstant = nodespacing * 3;
organicLayout.resetEdges = false;
var organicLayoutIsVertexIgnored = organicLayout.isVertexIgnored;
// Ignore other cells
organicLayout.isVertexIgnored = function(vertex)
{
return organicLayoutIsVertexIgnored.apply(this, arguments) ||
mxUtils.indexOf(cells, vertex) < 0;
};
var edgeLayout = new mxParallelEdgeLayout(graph);
edgeLayout.spacing = edgespacing;
ui.executeLayout(function()
{
organicLayout.execute(graph.getDefaultParent());
postProcess();
}, true, afterInsert);
afterInsert = null;
}
ui.hideDialog();
}
finally
{
graph.model.endUpdate();
}
if (afterInsert != null)
{
afterInsert();
}
}
}
catch (e)
{
ui.handleError(e);
}
}, null, null, 620, 430, null, true, true);
}
ui.showDialog(dlg.container, 640, 520, true, true);
dlg.init();
});
var menu = ui.menus.get('insert');
var oldFunct = menu.funct;
menu.funct = function(menu, parent)
{
oldFunct.apply(this, arguments);
ui.menus.addMenuItems(menu, ['fromCsv'], parent);
};
});