(function () { /*! * arborator script for dependency drawing * version 2.0 * http://arborator.ilpga.fr/ * * Copyright 2010-2020, Kim Gerdes & Gaël Guibon * * This program is free software: * Licensed under version 3 of the GNU Affero General Public License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.gnu.org/licenses/agpl-3.0.html * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. * */ // global variables: fontSize = 0; // computed from css value for .token in arborator-draft.css svgDefaultHeight = 350;//500; el=10; // type of conll (10, 14, or 4), computed in conllNodesToTree trees=[]; // list of tree objects uextras=[]; // list of comments. each comment is a hashtable position(=line)->comment conlltrees=[]; // list of conll strings sentences=[]; // list of sentence strings theshownfeatures=["FORM", "UPOS", "LEMMA", "MISC.Gloss"]; // recomputed in readConll shownmetas=['text_en']; showAllConllButton = false; progressiveLoading = true; // false to make it load all trees at once (may overload the browser) reverseMode = false; // set true for right to left conll conlls = { 10: {"ID": 0, "FORM":1, "LEMMA": 2, "UPOS": 3, "XPOS":4, "FEATS":5, "HEAD":6, "DEPREL":7, "DEPS":8, "MISC":9}, 14: {"ID": 0, "FORM":1, "LEMMA": 3, "UPOS": 5, "HEAD":9, "DEPREL":11}, 4: {"FORM":0, "LEMMA": 0, "UPOS": 1, "HEAD":2, "DEPREL":3} }; isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; // needed for bezier bounding box bug svgIdIndex = 0; // debug: log = console.log.bind(console); arborator = ` `; const defaultcss = ` .FORM { font: 18px DejaVu Sans; fill: black; font-family:sans-serif; text-align: center; } .LEMMA { font: 15px DejaVu Sans; fill: black; font-family:sans-serif; text-align: center; font-style: italic; } .UPOS { font: 11px DejaVu Sans; fill: rgb(80, 29, 125); text-align: center; } .MISC-Gloss {font: 15px DejaVu Sans; fill: rgb(124, 96, 86); font-family:sans-serif; text-align: center; font-style: italic;} .DEPREL { font: 12px Arial; fill: #1d2ec1; font-style: oblique; font-family:sans-serif; } .arrowhead { fill: white; stroke: black; stroke-width:.8;} .arrowheadxdep { fill: white;stroke: #21ba45;stroke-width:.8;} .xdep { stroke: #21ba45; stroke-width: 1; fill: none;} .curve { stroke: black; stroke-width: 1; fill: none;} .xdeprel { font: 12px Arial; font-style: oblique; fill: #21ba45; } .svgbox { overflow-x: auto; }`; // public initialisation function this.ArboratorDraft = function(shownfeatures=false, reverse = false, visuMode = 0) { // main function called from html file if (shownfeatures) {theshownfeatures = shownfeatures;} if(reverse) reverseMode = true; if(visuMode==0){ $( ".expander" ).click(function(){ $(this).next('conll').toggle();}); readConll(); } else{ // log('[ArboratorDraft] visumode'); trees=[]; uextras=[]; conlltrees=[]; sentences=[]; $('conll').hide(); refresh( $('#conllarea').text() ); } } // public function ArboratorDraft.prototype.emptyThenRefresh = function(content, reverse = false, toggle = false) { if(reverse) reverseMode = true; // to set reverse or not if(toggle) reverseMode = !reverseMode; // to toggle reverse empty().done( refresh( content ) ); } function refresh(content) { $('#svgwell').html(''); $('#svgwell').append( $("").attr('id', 'transformhere').text( content ) ); var conll = d3.selectAll('#transformhere')['_groups'][0][0]; drawConll(conll); return; } function empty() { var def = new jQuery.Deferred(); clearTimeout(readInsideConllTimeout); treelines = []; $('#svgwell').html(''); def.resolve(); return def.promise(); } function progressiveReadConll() { // draw each conll tags progressively (only conll tags) var conllLoop = d3.selectAll('conll')['_groups'][0]; // need to go out d3js to load it progressively var i = 0; function waitBetweenElements(range) { readConllTimeout = setTimeout(function () { drawConll(conllLoop[i]); i++; if (i < range) { waitBetweenElements(range); } }, 10) } waitBetweenElements(conllLoop.length); } function progressiveReadInsideConll(trees, pnode) { // draw each tree INSIDE a conll progressively var iWait = 0; function waitBetweenElements(range) { readInsideConllTimeout = setTimeout(function () { pushAndDrawSVG(trees[iWait], pnode); iWait++; if (iWait < range) waitBetweenElements(range); }, 10) } waitBetweenElements(trees.length); } function pushAndDrawSVG(element, pnode) { conlltrees.push(element); var data=conllNodesToTree(element); trees.push(data.tree); uextras.push(data.uextra) sentences.push(data.sentence) var toggle = true; var divsvgbox = pnode.insert('div').attr("class", 'svgbox'); var divsentbox = divsvgbox.insert('div').attr("class", 'sentencebox') divsentbox.insert('nav').html(` `); $("#pnglink"+svgIdIndex).click(function(){savePng(this.id);}); $("#svglink"+svgIdIndex).click(function(){saveSvg(this.id);}); divsentbox.insert('span').html(data.sentence); for (shom of shownmetas) { if (shom in data.metas) if (shom.startsWith("text")) divsentbox.insert('div').html("‘"+data.metas[shom]+"’") else divsentbox.insert('div').html(data.metas[shom]) } var thisconll = divsentbox.insert('conll').html(element).attr('class', 'conll'); $("#showconll"+svgIdIndex).click(function(){ toggle = !toggle; thisconll.style("display", toggle ? "none" : "block"); $(this.childNodes[0]).html(toggle ? "🗏 Show CoNLL": "🗏 Hide CoNLL") // }); thisconll.style("display", toggle ? "none" : "block"); draw(divsvgbox, data.tree); svgIdIndex=svgIdIndex +1; } function readConll() { // reads the conll representation of the whole treebank that is in the conll field trees=[]; // list of tree objects uextras=[]; // list of comments. each comment is a hashtable position(=line)->comment conlltrees=[]; // list of conll strings, one string per tree sentences=[]; // list of sentence strings, one string per tree $('conll').hide(); // to hide the huge conll data if(progressiveLoading) { progressiveReadConll(); // progressive draw } else { d3.selectAll('conll').each(function(d){ drawConll(this); }); // all at once } } function drawConll(conllElement) { // for each section: var pnode = d3.select(conllElement.parentNode); if(showAllConllButton){ var conll = d3.select(conllElement).attr("class", 'conll'); conll.html(conll.html().trim()) var toggle = false; var showHideConll = pnode.insert('div').html('
VIEW CONLL
').attr("class", 'button raised'); var conllContent = conll.html().trim(); conll.remove(); // remove the old conll because its place wasn't good conll = pnode.insert('conll').html(conllContent).attr('class', 'conll'); //re insert to bind the content showHideConll.on("click", ()=>{ conll.style("display", toggle ? "none" : "block"); showHideConll.html(toggle ? '
VIEW CONLL
': '
HIDE CONLL
'); toggle = !toggle; }); } treelines = conllElement.textContent.trim().split(/\n\s*\n\s*\n*/); if(progressiveLoading) { progressiveReadInsideConll(treelines, pnode); } else { for (let singleConll of treelines) { // for each conll tree at once, can block the browser pushAndDrawSVG(singleConll, pnode); } } } function conllNodesToTree(treeline) { var nodes = treeline.split('\n'); if(reverseMode) nodes.reverse(); var tree={}; var META={}; // sentence features before the actual conll var addLines={}; // node position to additional line var uextra={}; // todo: check this for reconstruction of conllu var lastid=0; var skipuntil=0; var words=[] nodes.forEach(function (nodeline, id) { // for each conll line: nodeline=nodeline.trim().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); if (nodeline.charAt(0) == "#") // META { var [a,v] = nodeline.substring(1).trim().split("=") if (v!=null) var [a,v]=[a.trim(),v.trim()] META[a]=v return true; } var elements = nodeline.split('\t'); el=elements.length; if(el > 1) { if (!(el in conlls) && el>10) el=10; if (el > 4) id=elements[conlls[el]["ID"]]; else if (elements[conlls[el]['FORM']] != "_") id++; var form=elements[conlls[el]['FORM']]; var tokids=id.split("-") if (tokids.length == 1) { // simple token tree[id]={}; tree[id]["ID"]=id; tree[id]['FORM']=form; tree[id]["LEMMA"]=elements[conlls[el]["LEMMA"]]; tree[id]['UPOS']=elements[conlls[el]['UPOS']]; tree[id]['HEAD']=parseInt(elements[conlls[el]['HEAD']]); tree[id]['DEPREL']=elements[conlls[el]['DEPREL']]; tree[id]["FEATS"]={}; tree[id]["DEPS"]={}; tree[id]["MISC"]={}; if (id>skipuntil) words.push(form); if (el==10) { tree[id]["XPOS"] =elements[conlls[el]["XPOS"]]; tree[id]["FEATS"] = analyzeFeaturestring(elements[conlls[el]["FEATS"]], '=', '|'); tree[id]["DEPS"] = analyzeFeaturestring(elements[conlls[el]["DEPS"]], ':', '|'); tree[id]["MISC"] = analyzeFeaturestring(elements[conlls[el]["MISC"]], '=', '|'); } } else if (tokids.length == 2){ // n-m type multi-word encoding skipuntil = parseInt(tokids[1]) words.push(elements[conlls[el]['FORM']]); if (!(lastid in uextra)) uextra[lastid]=[]; uextra[lastid].push(nodeline); } else { if (!(lastid in uextra)) uextra[lastid]=[]; uextra[lastid].push(nodeline); } }//end if el >1 else { // bizarre line if (!(lastid in addLines)) addLines[lastid]=[]; addLines[lastid].push(nodeline); if (tokids.length == 2){ // n-m type multi-word encoding skipuntil = parseInt(tokids[1]) words.push(elements[conlls[el]['FORM']]); } tree[id]['HEAD']=parseInt(elements[conlls[el]['HEAD']]); // todo: think about this tree[id]['DEPREL']=elements[conlls[el]['DEPREL']]; } lastid=id; }); // now always correcting the META.text feature. if this is not desired, uncomment this line: // if {tree:tree, uextra:uextra, sentence:META['text']}; // got to contstruct the sentence var sentence = []; words.forEach(function (word, i) { sentence.push(word); if (!("SpaceAfter" in tree[i+1]["MISC"] && tree[i+1]["MISC"]["SpaceAfter"]=="No" )) sentence.push(" "); // if(!reverseMode){ // if (i+1 in tree && !(("NoSpaceAfter" in tree[i+1]) && tree[i+1]["NoSpaceAfter"]==false)) sentence.push(" "); // } else{ sentence.push(word); } }); META['text'] = sentence.join('') if ('shownfeatures' in META) { theshownfeatures = META['shownfeatures'].split(/,\s*/); console.log(theshownfeatures); } return {tree:tree, uextra:uextra, sentence:META['text'], metas:META}; } function analyzeFeaturestring(featstr, eq, spl) { o={} if (featstr.indexOf(eq) > -1){ featstr.split(spl).forEach(function (f) // for each feature: { var fs=f.split(eq) if (fs.length >=2) {// if it's not just _ o[fs[0]]=fs.slice(1).join(eq); } }); } return o; } function getSVGPath(startPoint,endPoint,computedStyle,ymove) { // startPoint and endPoint are objects for the corresponding nodes var tokDepDist = parseInt(computedStyle.getPropertyValue('--tokDepDist')); var depMinHeight = parseInt(computedStyle.getPropertyValue('--depMinHeight')); var wordDistanceFactor = parseInt(computedStyle.getPropertyValue('--wordDistanceFactor')); if(reverseMode) wordDistanceFactor = -Math.abs(wordDistanceFactor); var startOffset = parseInt(computedStyle.getPropertyValue('--startOffset')); if(reverseMode) startOffset = -Math.abs(startOffset); var startOff=(startPoint['ID']-endPoint['ID']>0)?-startOffset:startOffset var x1 = startPoint['x']+startPoint['w']/2+startOff; var x2 = endPoint['x']+endPoint['w']/2; var y1 = svgDefaultHeight-fontSize*2; var y2 = svgDefaultHeight-fontSize*2; var x1x2=Math.abs(x1-x2)/2; var yy = Math.max(y1-x1x2-wordDistanceFactor*Math.abs(endPoint['ID']-startPoint['ID']),-tokDepDist); var yy = Math.min(yy,y1)-depMinHeight; var cstr="M"+x1+","+y1+" C"+x1+","+yy+" "+x2+","+yy+" "+x2+","+(y2-2+ymove); // -2 so that the arrow is really pointed //(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point. return {cstr:cstr, x0:x1,y0:y1, x1:x1,y1:yy, x2:x2,y2:yy,x3:x2,y3:(y2-2)}; } function arrowhead(x,y) { // gives path for arrowhead x,y startpoint (end of arrow) var size = 5; var startpoint = x+","+y; // to move the arrowhead lower: (y+size/3); var lefttop = "0,0" +(-size/2)+","+(-size*1.5)+" "+(-size/2)+","+(-size*1.5); var righttop = (size/2)+"," +(size/2)+" "+(size/2)+"," +(size/2)+ " "+(size)+",0"; var arrowPath = "M"+ startpoint+"c"+lefttop+ "c"+righttop+ "z"; return arrowPath; } function drawDep(tree, id, govid, label, group, smallestY, curvestyle, relstyle, arrowstyle, rootlabels, ymove) { var x = tree[id]['x'] + tree[id]['w'] / 2; var depLine = group.append("path") .attr("class", curvestyle) .attr("d", function (dd) { if (govid == 0) // sentence root: { var y = svgDefaultHeight - fontSize * 2; return "M" + x + "," + (y - 2 + ymove) + "L" + x + "," + 0; } else // normal link: { pathInfo = getSVGPath(tree[govid], tree[id], getComputedStyle(this), ymove); return pathInfo.cstr; } }); group.append("path") .attr("class", arrowstyle) .attr("d", function (dd) { return arrowhead(x, svgDefaultHeight - fontSize * 2 +ymove); }); var depLineBbox = depLine.node().getBBox(); if (govid == 0) // root link: { rootlabels.push( group.append('text') .attr("class", relstyle) .text(label) .attr("x", x+2) .attr("y", 0) ); } else // normal link: { group.append('text') .attr("class", relstyle) .text(label) .attr("x", function (d) { relFontSize = parseInt(getComputedStyle(this).fontSize, 10); var w = this.getComputedTextLength(); //<-- length of this node return depLineBbox.x + depLineBbox.width / 2 - w / 2; }) .attr("y", function (d) { funcCurveDist = parseInt(getComputedStyle(this).getPropertyValue('--funcCurveDist')); if (isFirefox) { // firefox needs: stackoverflow.com/questions/24809978/calculating-the-bounding-box-of-cubic-bezier-curve const { x0, y0, x1, y1, x2, y2, x3, y3 } = pathInfo; // firefox y = bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3).min.y - funcCurveDist ; return y; // firefox } else { // standard Chrome etc y = depLineBbox.y - funcCurveDist; return y; } }); // if not root, check how high we got: var smallY = y - relFontSize; smallestY = smallY < smallestY ? smallY : smallestY; } return 3//smallestY } function draw(div, tree) { // draws json tree on svg in div var runningWidth = 0; var smallestY = svgDefaultHeight; var treeArray = $.map(tree, function(value) {return [value];}); var svg = div.append("svg:svg") .attr("width", 1000) .attr("height", svgDefaultHeight); var group = svg.append("g"); var rootlabels = []; // write tokens: if(reverseMode) treeArray.reverse(); var eachTexts = group.selectAll("text") .data(treeArray) .enter(); eachTexts.append('text') // FORM .attr("class", "FORM") .text(function(d){ return d["FORM"]; } ) .attr("ID",function(d) { return d["ID"]; } ) .attr("x", function(d) { var w = this.getComputedTextLength(); wordDistance = parseInt(getComputedStyle(this).getPropertyValue('--wordDistance')); var x = runningWidth; //<-- previous length to return runningWidth += w + wordDistance; //<-- total tree[d["ID"]]["x"]=x; tree[d["ID"]]["w"]=w; fontSize = parseInt(getComputedStyle(this).fontSize, 10); return x; }) .attr("y", svgDefaultHeight-fontSize); svg.attr("width", runningWidth); // adapt svg width // draw dependency links: group.selectAll("text").each(function(d) { // for each token: var id = d3.select(this).attr("ID"); var govid = tree[id]["HEAD"]; var label = tree[id]["DEPREL"]; smallestY = drawDep(tree, id, govid, label, group, smallestY, "curve", "DEPREL","arrowhead", rootlabels, 0); // standard governor relation for (var headid in tree[id]["DEPS"]) { // for each governor of extended dependency smallestY = drawDep(tree, id, headid, tree[id]["DEPS"][headid], group, smallestY, "xdep", "xdeprel", "arrowheadxdep", rootlabels, -fontSize/2); } }); var lastheight = 0; var h=0; for (var fea of theshownfeatures) { if (fea=="FORM") continue; somet=false; eachTexts.append('text') .attr("class", fea.replace('.', '-')) .text(function(d) {var t=fea.split('.').reduce((value,el) => value[el], d); if(!somet && t) somet=true; return t; } ) .attr("ID",function(d) {return d["ID"];}) .attr("x", function(d) { // var lemmaLength = this.getComputedTextLength(); // var w = tree[d["ID"]]["w"]; wordDistance = parseInt(getComputedStyle(this).getPropertyValue('--wordDistance')); h = parseInt(getComputedStyle(this).fontSize, 20); return tree[d["ID"]]["x"]; //<-- previous length to return }) .attr("y", svgDefaultHeight-fontSize+lastheight+h) .append('title').text(function(d) {return fea.split('.').slice(-1)[0]+": "+fea.split('.').reduce((value,el) => value[el], d); }); if (somet) lastheight+=h; // found at least once the feature } // final adjustments: for (rl of rootlabels) rl.attr("y",smallestY+fontSize) group.attr("transform", "translate(" + 0 + "," + (-smallestY) + ")"); svg.attr("height", svgDefaultHeight-smallestY+lastheight+fontSize); // adapt svg height svg.attr("id", "svg"+svgIdIndex); svg.attr("version", '1.1'); // to prepare ddl of svg svg.attr("xmlns", "http://www.w3.org/2000/svg"); // to prepare ddl of svg } function bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3) { //(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point. var tvalues = [], xvalues = [], yvalues = [], a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (Math.abs(a) < 1e-12) { if (Math.abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; if (b2ac < 0) { continue; } sqrtb2ac = Math.sqrt(b2ac); t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var j = tvalues.length, mt; while (j--) { t = tvalues[j]; mt = 1 - t; xvalues[j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); yvalues[j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); } xvalues.push(x0,x3); yvalues.push(y0,y3); return { min: {x: Math.min.apply(0, xvalues), y: Math.min.apply(0, yvalues)}, max: {x: Math.max.apply(0, xvalues), y: Math.max.apply(0, yvalues)} }; } function svg2Data(svg) { var tempCSS = document.createElement("style"); tempCSS.innerHTML = defaultcss; svg.insertBefore(tempCSS, svg.firstChild); // svg.appendChild(tempCSS); return {svg:svg, tempCSS:tempCSS}; } function png2Data(svg) { var canvas = document.createElement('canvas'); canvas.height = svg.height.baseVal.value*5; canvas.width = svg.width.baseVal.value*5; var tempCSS = document.createElement("style"); tempCSS.innerHTML = defaultcss; svg.insertBefore(tempCSS, svg.firstChild); // === the missing prepend option var data = (new XMLSerializer()).serializeToString(svg); canvg(canvas, data, {'scaleWidth':canvas.width,'canvas.height':canvas.height,'ignoreDimensions':true}); return canvas.toDataURL().replace('data:image/png;base64,',''); } function savePng(btnId) { var id = btnId.replace("pnglink",""); var canvas = document.createElement('canvas'); var svg = document.getElementById("svg"+id); var sdat = svg2Data(svg); canvas.height = svg.height.baseVal.value*5; canvas.width = svg.width.baseVal.value*5; var ctx = canvas.getContext('2d'); var data = (new XMLSerializer()).serializeToString(svg); var DOMURL = window.URL || window.webkitURL || window; var img = new Image(); var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}); var url = DOMURL.createObjectURL(svgBlob); img.onload = function () { ctx.drawImage(img, 0, 0, canvas.width, canvas.height);//, 0, 0, ctx.width, ctx.height); DOMURL.revokeObjectURL(url); var imgURI = canvas .toDataURL('image/png') .replace('image/png', 'image/octet-stream'); var evt = new MouseEvent('click', { view: window, bubbles: false, cancelable: true }); var a = document.createElement('a'); a.setAttribute('download', sentences[id].match(/[a-zA-Z\u00c0-\u024f\u1e00-\u1eff]+/g).join(' ').split(" ").slice(0,5).join('_').slice(0,50)+'.png'); a.setAttribute('href', imgURI ); a.setAttribute('target', '_blank'); a.dispatchEvent(evt); }; img.src = url; svg.removeChild(sdat.tempCSS); } function saveSvg(btnId) { var id = btnId.replace("svglink",""); var svg = document.getElementById("svg"+id); sdat = svg2Data(svg); var svgData = svg.outerHTML; var svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}); var svgUrl = URL.createObjectURL(svgBlob); var downloadLink = document.createElement("a"); downloadLink.href = svgUrl; downloadLink.download = sentences[id].match(/[a-zA-Z\u00c0-\u024f\u1e00-\u1eff]+/g).join(' ').split(" ").slice(0,5).join('_').slice(0,50)+".svg"; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); svg.removeChild(sdat.tempCSS); } function exportPngZip() { var filename = document.URL? document.URL.substring(document.URL.lastIndexOf('/')+1, document.URL.lastIndexOf('.')):'trees' var zip = new JSZip(); var folder = zip.folder(filename+".PNGfiles"); var svgList = $('svg'); $.each( svgList, function( index, svg ){ if (svg.id) { var pngData = png2Data(svg); var sent = sentences[svg.id.replace('svg','')].match(/[a-zA-Z\u00c0-\u024f\u1e00-\u1eff]+/g).join(' ').split(" ").slice(0,5).join('_').slice(0,50) folder.file('tree'+svg.id.replace('svg','')+'.'+sent+'.png', pngData, {base64: true}); } }); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, filename + ".png.export.zip"); }); } function exportSvgZip() { var zip = new JSZip(); var folder = zip.folder("svgfiles"); var svgList = $('svg'); $.each( svgList, function( index, svg ){ if (svg.id) { var svgData = svg2Data(svg); var sent = sentences[svg.id.replace('svg','')].match(/[a-zA-Z\u00c0-\u024f\u1e00-\u1eff]+/g).join(' ').split(" ").slice(0,5).join('_').slice(0,50) folder.file('tree'+svg.id.replace('svg','')+'.'+sent+'.svg', svgData.svg.outerHTML); } }); // TODO: // Add an top-level, arbitrary text file with contents // zip.file("Hello.txt", "Hello World\n"); // Generate a directory within the Zip file structure // var img = zip.folder("images"); // Add a file to the directory, in this case an image with data URI as contents // img.file("smile.gif", imgData, {base64: true}); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, (document.URL? document.URL.substring(document.URL.lastIndexOf('/')+1, document.URL.lastIndexOf('.')):'trees') + ".svg.export.zip"); }); } $("#btnSvgZip").on("click", function (){ exportSvgZip(); }); $("#btnPngZip").on("click", function (){ exportPngZip(); }); }());