I try to parse the expression in a <svg><path d="expression" /></svg>
element. I want to do transformations of the points in JavaScript.
My first step is to parse the path expression. I thought doing this with a RegExp (see below). The code I doesn't behave as I expected. It only finds the last occurrence of each group. What is the best way to make this work? Is it possible with Regexp? If this is the only way RegExp can behave in JavaScript, I suppose I have to match a first regex, then do the rest of the parsing with normal JavaScript. But I would like to first know if it's possible with another RegExp based code. I'd like to learn to use Regexp's better.
let path = `M4,24
L4,23
Q12,23,12, 9
L14, 9
C15, 3,25, 3,26,9
L28,9
Q28,23,36,23
L36,24
Z`
// This function is dysfunctional
function parsePath(path) {
// To simplify the match RegExp, I simplify the path string first:
// All comma's and all whitespace characters are simplified to a simple separator space.
let path2 = path.replaceAll(/[,\s]+/g, ' ')
let rgx = /([A-Za-z])(?:(\d+)\s+)*/g
let matches = [...path2.matchAll(rgx)].map(x => {
return [x[1], x.slice(2)]
})
return matches
}
console.log(parsePath(path));
// OUTPUT:
[
[ 'M', [ '24' ] ],
[ 'L', [ '23' ] ],
[ 'Q', [ '9' ] ],
[ 'L', [ '9' ] ],
[ 'C', [ '9' ] ],
[ 'L', [ '9' ] ],
[ 'Q', [ '23' ] ],
[ 'L', [ '24' ] ],
[ 'Z', [ undefined ] ]
]
// EXPECTED AND DESIRED OUTPUT:
[
[ 'M', [ '4', '24' ] ],
[ 'L', [ '4', '23' ] ],
[ 'Q', [ '12', '23', '12', '9' ] ],
// ...
]
You are repeating a capture group, which gives captures the value of the last iteration.
What you also can omit the the replacement first from comma's and spaces to a single space.
Then change the regex rgx
to match a single char A-Za-z and capture following digits with a repeated comma and again digits in group 2.
Then split the group 2 value on a comma followed by optional whitspace chars ,\s*
([A-Za-z])(\d+(?:,\s*\d+)+)
See the matches on regex 101
(if there can also be optional whitespace chars before the comma, then you can change it to \s*,\s*
)
As an example for your input (assuming that the Z
is not matched because are no digits after it)
let path = `M4,24
L4,23
Q12,23,12, 9
L14, 9
C15, 3,25, 3,26,9
L28,9
Q28,23,36,23
L36,24
Z`
function parsePath(path) {
let rgx = /([A-Za-z])(\d+(?:,\s*\d+)+)/g
return [...path.matchAll(rgx)]
.map(x => [x[1], x[2]
.split(/,\s*/)
])
}
console.log(parsePath(path))
Or another way of writing it using Array.from
and having the Z
as undefined if there is no digit following using an optional capture group in the regex:
let path = `M4,24
L4,23
Q12,23,12, 9
L14, 9
C15, 3,25, 3,26,9
L28,9
Q28,23,36,23
L36,24
Z`
const rgx = /([A-Za-z])((?:\d+(?:,\s*\d+)+)?)?/g;
const parsePath = path =>
Array.from(
path.matchAll(rgx),
x => [x[1], x[2] ? x[2].split(/,\s*/) : undefined]
);
console.log(parsePath(path))