All files index.js

94.32% Statements 83/88
84.38% Branches 27/32
88.89% Functions 16/18
96.25% Lines 77/80

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190                  1x 28x                         28x   28x 52x 16x     36x 36x       28x     1x     34x 20x       14x     14x 84x   84x 28x 28x             1x 8x 8x 6x   6x 18x 18x 18x       8x     1x 24x 24x   24x         134x           1x 8x 8x 8x   8x   8x 6x   2x   8x   8x 8x   8x 8x 34x     8x     8x   8x             1x   1x 16x 86x   16x 86x 98x       16x 86x 86x     86x   26x   26x 24x 24x   1x       26x 60x 60x           16x 16x       1x             1x 6x   6x       6x 4x   4x          
import React, {useEffect} from 'react';
//import { create, all } from 'mathjs/number';
 
//const math = create(all);
 
//import * as math from 'mathjs';
 
import * as math from 'mathjs/number';
 
const processExpressionTerm = (term, parameters) => {
    const expression = term
        .replace(/{/g, '')
        .replace(/\(/g, '')
        .replace(/\)/g, '')
        .replace(/}/g, '')
        .replace(/\+/g, ' ')
        .replace(/-/g, ' ')
        .replace(/\^/g, ' ')
        .replace(/\//g, ' ')
        .replace(/\*/g, ' ')
        .replace(/,/g, ' ')
        .replace(/'/g, ' ');
 
    const terms = expression.split(" ");
 
    for (let term in terms) {
        if (!terms[term] || !isNaN(terms[term])) {
            continue;
        }
 
        Eif (!parameters[terms[term]]) {
            parameters[terms[term]] = 0;
        }
    }
 
    return parameters;
}
 
const getParameters = (attributes, parameters) => {
    // Given a NamedNodeMap or Array of attributes for a tag, identify any parameters not recorded in the parameters array
 
    if (!attributes) {
        return;
    }
 
    // process only the contents of {}
    const re = /\{(.+?)\}/g;
 
 
    for (let j = 0; j < attributes.length; j++) {
        const attribute = attributes[j];
 
        if (attribute.name.startsWith("parametric:")) {
            attribute.nodeValue.replace(re, (match, g1, g2) => {
                parameters = processExpressionTerm(g1, parameters);
            });
        }
    }
};
 
 
const getDefaultParamValues = (svg, defaultParameters) => {
    const defaultAttribute = svg.attributes["parametric:defaults"];
    if (defaultAttribute) {
        let assignments = defaultAttribute.nodeValue.split(";");
        let param, value;
        for (let i in assignments) {
            [param, value] = assignments[i].split("=");
            param = param.trim();
            defaultParameters[param] = value;
        }
    }
 
    return defaultParameters;
}
 
const getTree = (parentNode) => {
    const childrenArray = [].slice.call(parentNode.children)
    const children = childrenArray.map(child => getTree(child));
 
    return {
        tagName: parentNode.tagName,
        id: parentNode.id,
        className: parentNode.className,
        attributes: [...parentNode.attributes].map(d =>
            ({name: d.name, value: d.value})),
        children: children
    };
}
 
// This is the intended entrypoint for the parametric SVG editor
const parseParametricSVG = (svgString) => {
    var virtualParent = document.createElement('div'); // can't set outerHTML of an element with no parent
    var virtualSVG = document.createElement('svg');
    virtualSVG.id = 'VIRTUAL-SVG';
 
    virtualParent.appendChild(virtualSVG);
 
    if (svgString.includes("<svg")) {
        virtualSVG.outerHTML = svgString;
    } else {
        virtualSVG.innerHTML = svgString;
    }
    virtualSVG = virtualParent.lastChild;
 
    let parameters = {};
    let defaultParameters = {};
 
    let children = virtualSVG.childNodes;
    for (let i = 0; i < children.length; i++) {
        getParameters(children[i].attributes, parameters);
    }
 
    defaultParameters = getDefaultParamValues(virtualSVG, defaultParameters);
 
    // Construct tree in JSON reflecting tree in SVG
    const tree = getTree(virtualSVG);
 
    return {parameters, defaultParameters, tree};
}
 
 
///
 
 
const re = /\{(.+?)\}/g;
 
const Element = ({tree, params, defaultParams}) => {
    var attributes = {};
    const attribute_names = tree.attributes.map(d => d.name);
 
    const camelCase = (name) => (
        name.split('-')
             .map((item, index) => index ? item.charAt(0).toUpperCase() + item.slice(1).toLowerCase() : item)
             .join("")
    );
 
    for (let i in tree.attributes) {
        let name = tree.attributes[i].name;
        let value = tree.attributes[i].value;
 
 
        if (name && name.startsWith("parametric:")) {
 
            attributes[name] = value; // save the parametric version
 
            value = value.replace(re, function (match, g1, g2) {
                try {
                    return math.evaluate(g1, params)
                } catch (err) {
                    return null;
                }
 
            });
            attributes[camelCase(name.replace('parametric:', ''))] = value; // save version with paramater values substituted
        } else Eif (!attribute_names.includes('parametric:' + name)) {
            attributes[camelCase(name)] = value;
        } else {
            // this is the non-parametric version of an attribute: don't replace value substituted above
        }
    }
 
    const childTags = tree.children.map( (child, i) => <Element tree={child} params={params} defaultParams={defaultParams} key={i} />);
    return React.createElement(tree.tagName, attributes, childTags);
};
 
 
const PreviewSVG = ({params, defaultParams, tree, updateSVGString}) => {
    useEffect(() => {if (updateSVGString){ updateSVGString()}});
    return <Element tree={tree} params={params} defaultParams={defaultParams}/>;
};
 
 
// This is the entrypoint if you just want to render a parametric SVG
const ParametricSVG = ({svgString, params, onParse, innerOnly}) => {
    const {parameters, defaultParameters: defaultParams, tree} = parseParametricSVG(svgString);
 
    Iif (onParse){
        onParse(parameters, defaultParams);
    }
 
    if (innerOnly){
        return <>{tree.children.map( (child, i) => <Element tree={child} params={{...defaultParams, ...(params || {})}} defaultParams={defaultParams} key={i} />)}</>
    } else {
        return <Element tree={tree} params={{...defaultParams, ...(params || {})}} defaultParams={defaultParams}/>;
    }
}
 
export {ParametricSVG, parseParametricSVG, PreviewSVG};