Method
We split the input string on pseudo-legal BBCode tags, placing the tags at odd positions and the remaining parts at even positions. For instance:
[b][i]foo[/i][/b] → ["","[b]","","[i]","foo","[/i]","","[/b]",""]
We then iterate over the resulting array, modifying it in-place whenever a valid pair of opening and closing tags is found.
There is one stack per tag type in S, storing the position of each opening tag and its BBCode parameter, if any.
The counter c is used to keep track of the nesting depth of code blocks, allowing us to disable HTML conversion inside them.
We use two lookup tables:
- one with 10 entries to identify the BBCode tags
- one with 20 entries for the corresponding HTML tags, with and without a BBCode parameter
Commented
s =>
// split the input string on pseudo-legal BBCode tags '[…]'
(a = s.split(/(\[.+?\])/))
// for each part s at index i
.map(S = (s, i) =>
i & 1 ?
// if this is a tag
(
// C = 'closing tag' flag, t = tag name, p = optional parameter
[, C, t,, p] = /.(\/)?(\w+)(=(.+))?./.exec(s),
// force t to lower case and get n = internal BBCode tag ID
n =
// 0 1 2 3 4 5 6 7 8 9
`url|color|size|b|i|quote|img|u|s|code`
.split`|`
.indexOf(t = t.toLowerCase()),
C
) ?
// if this is a closing tag
!(
// attempt to retrieve j = position of the opening tag
// and p = parameter in the opening tag
[j, p] = S[t]?.pop() || [],
// T = HTML tag according to n and the presence of a parameter
// (NB: an 'url' tag is always forced to entry #1)
T =
// 1 3 5 6 8
`|a href="0"||21:0"||2font-1:0"|strong||em||` +
// 10 11 12 14 16 18
`block1|block1><cite>0</cite|1 src="0"||u||s||1`
.split`|`
[n * 2 | !!p || 1]
// unpack: 0 ->→ p or a[j + 1], 1 ->→ t, 2 ->→ 'span style="'
?.replace(/\d/g, n => [ p || a[j + 1], t, 'span style="'][n]),
// decrement c if it's greater than 0 and the tag is 'code'
c -= c && n > 8
) * j && T ?
// if c is not 0 and both j and T are defined,
// replace the opening tag with T
a[a[j] = `<${T}>`, i] =
n - 6 ?
// if this is not an 'img' tag, update the closing tag
// using the name extracted from T
`</${/\w+/.exec(T)}>`
:
// otherwise, clear both a[j + 1] and the closing tag
a[j + 1] = ''
:
// invalid tag: do nothing
0
:
// opening tag: increment c if the tag is 'code'
// and push [i, p] into this tag stack
(c += n > 8, S[t] ||= []).push([i, p])
:
// this is not a tag: do nothing
0,
// c = code block counter, initialized to 0
c = 0
)
// end of map(), return a[] joined
&& a.join``