Skip to content

Commit 1f6bd97

Browse files
committed
Improve formatSetlist performance
This is currently slower than it needs to be because we're parsing the text twice: once to convert the setlist into an HTML string, and again to convert the HTML string into React elements (via `expand2react`). Instead, we can convert the setlist directly into React elements. Running each version of the function 1000x on the setlist from https://musicbrainz.org/event/b54fb46e-4e92-4c6c-a171-a19cc5fea8b7 shows the original to take 0.3108367890119553 seconds and the new version to take 0.05375143200159073. The output is identical other than a trailing `<br />` from the new one, though that matches the original Perl version.
1 parent 32de157 commit 1f6bd97

File tree

1 file changed

+83
-54
lines changed

1 file changed

+83
-54
lines changed

root/static/scripts/common/utility/formatSetlist.js

+83-54
Original file line numberDiff line numberDiff line change
@@ -7,77 +7,106 @@
77
* later version: http://www.gnu.org/licenses/gpl-2.0.txt
88
*/
99

10-
import expand2react from '../i18n/expand2react';
10+
import * as React from 'react';
1111

1212
function setlistLink(entityType, entityGid, content) {
1313
let formattedContent = content;
1414
if (!nonEmpty(formattedContent)) {
1515
formattedContent = entityType + ':' + entityGid;
1616
}
1717

18-
return `<a href="/${entityType}/${entityGid}">${formattedContent}</a>`;
19-
}
20-
21-
function replaceSetlistMbids(entityType, setlistLine) {
22-
const formattedLine = setlistLine.replace(
23-
/\[([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\|([^\]]+))?\]/ig,
24-
function (match, p1, p2) {
25-
return setlistLink(entityType, p1, p2);
26-
},
18+
return (
19+
<a href={`/${entityType}/${entityGid}`}>
20+
{formattedContent}
21+
</a>
2722
);
28-
29-
return formattedLine;
3023
}
3124

32-
function formatSetlistArtist(setlistLine) {
33-
const artistLineLabel = addColonText(l('Artist'));
34-
const artistLineContent = replaceSetlistMbids('artist', setlistLine);
25+
const linkRegExp =
26+
/^\[([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\|([^\]]+))?\]/i;
3527

36-
const artistLine =
37-
`<strong>${artistLineLabel} ${artistLineContent}</strong>`;
38-
39-
return artistLine;
28+
function formatSetlistArtist(entityGid, content) {
29+
return (
30+
<strong>
31+
{addColonText(l('Artist'))}
32+
{' '}
33+
{setlistLink('artist', entityGid, content)}
34+
</strong>
35+
);
4036
}
4137

42-
function formatSetlistWork(setlistLine) {
43-
return replaceSetlistMbids('work', setlistLine);
38+
function formatSetlistWork(entityGid, content) {
39+
return setlistLink('work', entityGid, content);
4440
}
4541

4642
export default function formatSetlist(
4743
setlistText: string,
4844
): Expand2ReactOutput {
49-
let formattedText = setlistText;
50-
51-
// Encode < and >
52-
formattedText = formattedText.replace(/</g, '&lt;');
53-
formattedText = formattedText.replace(/>/g, '&gt;');
54-
55-
// Lines starting with @ are artists
56-
formattedText = formattedText.replace(
57-
/^@ ([^\r\n]*)/gm,
58-
function (match, p1) {
59-
return formatSetlistArtist(p1);
60-
},
61-
);
62-
63-
// Lines starting with * are works
64-
formattedText = formattedText.replace(
65-
/^\* ([^\r\n]*)/gm,
66-
function (match, p1) {
67-
return formatSetlistWork(p1);
68-
},
69-
);
70-
71-
// Lines starting with # are comments
72-
formattedText = formattedText.replace(
73-
/^# ([^\r\n]*)/gm,
74-
function (match, p1) {
75-
return '<span class=\"comment\">' + p1 + '<\/span>';
76-
},
77-
);
78-
79-
// Fix newlines
80-
formattedText = formattedText.replace(/(\015\012|\012\015|\012|\015)/g, '<br \/>');
45+
const rawLines = setlistText.split(/(?:\r\n|\n\r|\r|\n)/);
46+
const elements = [];
47+
48+
for (const rawLine of rawLines) {
49+
if (!nonEmpty(rawLine)) {
50+
elements.push(<br />);
51+
continue;
52+
}
53+
54+
const symbol = rawLine.substring(0, 2);
55+
const line = rawLine.substring(2);
56+
let entityType;
57+
58+
switch (symbol) {
59+
// Lines starting with @ are artists
60+
case '@ ':
61+
entityType = 'artist';
62+
break;
63+
64+
// Lines starting with * are works
65+
case '* ':
66+
entityType = 'work';
67+
break;
68+
69+
// Lines starting with # are comments
70+
case '# ':
71+
elements.push(<span className="comment">{line}</span>);
72+
break;
73+
74+
// Lines that don't start with a symbol are ignored
75+
}
76+
77+
if (entityType) {
78+
const startingBracketRegExp = /\[/g;
79+
80+
let match;
81+
let lastIndex = 0;
82+
83+
while ((match = startingBracketRegExp.exec(line))) {
84+
const textBeforeMatch = line.substring(lastIndex, match.index);
85+
elements.push(textBeforeMatch);
86+
lastIndex = match.index;
87+
88+
const remainder = line.substring(match.index);
89+
const linkMatch = remainder.match(linkRegExp);
90+
91+
if (linkMatch) {
92+
const [linkMatchText, entityGid, content] = linkMatch;
93+
switch (entityType) {
94+
case 'artist':
95+
elements.push(formatSetlistArtist(entityGid, content));
96+
break;
97+
case 'work':
98+
elements.push(formatSetlistWork(entityGid, content));
99+
break;
100+
}
101+
lastIndex += linkMatchText.length;
102+
}
103+
}
104+
105+
elements.push(line.substring(lastIndex));
106+
}
107+
108+
elements.push(<br />);
109+
}
81110

82-
return expand2react(formattedText);
111+
return React.createElement(React.Fragment, null, ...elements);
83112
}

0 commit comments

Comments
 (0)