20

Supposing we have an SVG transform string:

transform = "translate(6,5),scale(3,3)";

Is there a slick regex function we could use to parse that into something usable?

Yarin
  • 173,523
  • 149
  • 402
  • 512
  • Could you show if you have tried something? – icedwater Jul 24 '13 at 02:15
  • I can't do regex @icedwater- im looking for some help from someone who can – Yarin Jul 24 '13 at 02:16
  • If you know you need regex, then you know what you expect to have to parse... then it's a matter of finding the correct syntax for a regex. Here, I would google for JavaScript regex, then start with that. – icedwater Jul 24 '13 at 02:22
  • My point is, if you know what you want to filter out, you can try to do it yourself - then run into trouble and ask about that here. Otherwise *slick* and *usable* are up to you to define, which you also don't... – icedwater Jul 24 '13 at 02:32
  • @Yarin Please change the accepted answer to the one from Paul. Using the DOM API is the only reliable implementation for an operation like this. – masterxilo Sep 16 '19 at 09:18
  • I created a properly typed version of a solution here https://stackoverflow.com/questions/57955159/parse-svg-transform-attribute-with-typescript – masterxilo Sep 16 '19 at 10:44

7 Answers7

21

Here's a nifty little snippet of code that might get you what you need, it should cover most scenario in case the string you intend to parse has different number of arguments:

function parse (a)
{
    var b={};
    for (var i in a = a.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?)+\))+/g))
    {
        var c = a[i].match(/[\w\.\-]+/g);
        b[c.shift()] = c;
    }
    return b;
}

Running this

parse('translate(6,5),scale(3,3.5),a(1,1),b(2,23,-34),c(300)');

Will result in this:

{
    translate: [ '6', '5' ],
    scale: [ '3', '3.5' ],
    a: [ '1', '1' ],
    b: [ '2', '23', '-34' ],
    c: [ '300' ]
}
Ian
  • 33,605
  • 26
  • 118
  • 198
chernjie
  • 453
  • 4
  • 6
  • Elegant? Pfft. I looked and the regex and realized pretty instantly that it won't capture scale(.5) – Tatarize Oct 08 '15 at 00:45
  • The way to capture decimals like that is \d*\.?\d+ It's weird looking, but the the one before the radix point can have no digits, and the one after the radix point must have them. But, in the case of 3435 it's the second section that catches the number. In the case of .3535 it's still the second section that catches the number. – Tatarize Oct 08 '15 at 00:48
  • You can check out the fully fleshed out float regex as that's what's going on there: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? -- From this we see you won't catch scale(+2) either. Or scale(1e+2) – Tatarize Oct 08 '15 at 00:53
  • 1
    there can also be multiple translate for example – caub Aug 13 '17 at 13:02
  • I was scraping an XML element using .getAttribute('transform') to get its transform attribute, and Edge browser returned a string without the comma between properties. The regex requires a comma separator. I had to add 'a = a.replace(/ /g,",");' to swap the space out for a comma. Might be nice to have the regex do this instead. Otherwise, maybe this will help someone debug Edge browser. – jswitchback May 30 '19 at 21:09
  • "translate(1 2)" wasn't working - i modified the first regex because to allow comma AND/OR whitespace separated values. /(\w+\((\-?\d+\.?\d*e?\-?\d*,?\s*)+\))+/g – Robby Jul 18 '19 at 19:04
  • 1
    This should not be the accepted answer, this is too brittle. Please use the DOM API, see Paul's answer. – masterxilo Sep 16 '19 at 09:18
20

Assuming you are running in a browser, you could also use the SVG DOM to parse and decode the transform string.

var svgns = "http://www.w3.org/2000/svg";

function getTransformList(transformStr)
{
  // Create a fake <rect> element to set the transform to
  var rect = document.createElementNS(svgns, "rect");
  rect.setAttribute("transform", transformStr);
  // Use the DOM to get the list of decoded transforms
  dumpTransform(rect.transform.baseVal);
}

function dumpTransform(transformList)
{
  for (var i = 0; i < transformList.numberOfItems; i++)
  {
    var transform = transformList.getItem(i);
    var m = transform.matrix;
    switch (transform.type)
    {
      case 2:
        console.log("translate("+m.e+","+m.f+")");
        break;
      case 3:
        console.log("scale("+m.a+","+m.d+")");
        break;
      case 4:
        console.log("rotate("+transform.angle+")");
        // TODO need to also handle rotate(angle, x, y) form
        break;
      case 5:
        // TODO skewX()
        break;
      case 6:
        // TODO skewY(()
        break;
      case 1:
      default:
        console.log("matrix("+m.a+","+m.b+","+m.c+","+m.d+","+m.e+","+m.f+")");
        break;
    }
  }
}

getTransformList("translate(6,5),scale(3,3)");
Bharata
  • 13,509
  • 6
  • 36
  • 50
Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • This should be the accepted answer. All the others are extremely brittle. – masterxilo Sep 16 '19 at 09:18
  • I created a properly typed version of a solution here https://stackoverflow.com/questions/57955159/parse-svg-transform-attribute-with-typescript – masterxilo Sep 16 '19 at 10:44
  • 1
    With my edit I have made cross browser compatible code. It works now even in IE10. I hope you have nothing against it. – Bharata Nov 02 '20 at 21:47
  • Thanks everyone - making this accepted answer based on the recommendations – Yarin Oct 22 '21 at 11:07
4

Adapted from @chernjie's solution:

function parse_transform(a) {
    var b = {};
    for (var i in a = a.match(/(\w+)\(([^,)]+),?([^)]+)?\)/gi)) {
        var c = a[i].match(/[\w\.\-]+/g);
        b[c.shift()] = c;
    }
    return b;
}
Ivan Chaer
  • 6,980
  • 1
  • 38
  • 48
2
var transform = "translate(6,5),scale(3,3)";
var translate  = /translate\(\s*([^\s,)]+)[ ,]([^\s,)]+)/.exec(transform);
var translateX = translate[1]
var translateY = translate[2]
var scale  = /scale\(\s*([^\s,)]+)[ ,]([^\s,)]+)/.exec(transform);
var scaleX = translate[1]
var scaleY = translate[2]

transformValues = {translate:{x:translateX,y:translateY},
scale:{x:scaleX,y:scaleY}}

Pretty gross, but @icedwater was making me do all the work myself so...

Yarin
  • 173,523
  • 149
  • 402
  • 512
  • Well, you beat me to it - you could have edited it into the question above. I needed to look up JS regexes as well, so. – icedwater Jul 24 '13 at 02:39
0

I'll take a stab at this and say you want it to be parsed into a JSON object containing each of the properties as an entry in a list. This is the closest I have come so far:

function listIntoJSON(theTransformInQuestion) {

    // let's work with transform = "translate(6,5),scale(3,3)" as specified.
    var result = [];  // empty list to be returned
    var splitList = theTransformInQuestion.split(/\W/);
    // now splitList is ["translate", "6", "5", "", "scale", "3", "3", ""]

    for (var t = 0; t < splitList.length; t += 4) {
        var op  = {};
        op.name = splitList[t];
        op.x    = splitList[t + 1];
        op.y    = splitList[t + 2];
        result.push(op);  // add op to the result list
    }
    return result;
}

which will take

transform = "translate(6,5),scale(3,3)";

and return

[
    {name: "translate", x: "6", y: "5"}, 
    {name: "scale", x: "3", y: "3"}
]

and should work for any number of transforms. Bit of a hack, but it will do for now.

icedwater
  • 4,701
  • 3
  • 35
  • 50
0

even it's an old question, today I had the same need and ended up with this regex:

const regex = /((\w+)\((,?\s*-?\d*\.?\d+(px|in|px|cm|mm|pt|pc|em|ex|ch|rem|deg|rad|grad|turn)?)+\)\s*)/gi;

Quite long, but it takes into account unit of measure too.

You can test it here: https://regex101.com/r/M8rRjo/1/

lbrutti
  • 1,181
  • 13
  • 19
0

It's not regex, but there is a similar question (Replacing d3.transform in D3 v4) with a very nice, comprehensive solution:

function getTransformation(transform) {
  // Create a dummy g for calculation purposes only. This will never
  // be appended to the DOM and will be discarded once this function 
  // returns.
  var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
  
  // Set the transform attribute to the provided string value.
  g.setAttributeNS(null, "transform", transform);
  
  // consolidate the SVGTransformList containing all transformations
  // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
  // its SVGMatrix. 
  var matrix = g.transform.baseVal.consolidate().matrix;
  
  // Below calculations are taken and adapted from the private function
  // transform/decompose.js of D3's module d3-interpolate.
  var {a, b, c, d, e, f} = matrix;   // ES6, if this doesn't work, use below assignment
  // var a=matrix.a, b=matrix.b, c=matrix.c, d=matrix.d, e=matrix.e, f=matrix.f; // ES5
  var scaleX, scaleY, skewX;
  if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX;
  if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
  if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY;
  if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX;
  return {
    translateX: e,
    translateY: f,
    rotate: Math.atan2(b, a) * 180 / Math.PI,
    skewX: Math.atan(skewX) * 180 / Math.PI,
    scaleX: scaleX,
    scaleY: scaleY
  };
}

console.log(getTransformation("translate(20,30)"));  
console.log(getTransformation("rotate(45) skewX(20) translate(20,30) translate(-5,40)"));
tgordon18
  • 1,562
  • 1
  • 19
  • 31