1
\$\begingroup\$

Given some raw HTML, sanitize it by formatting the spaces, as defined below.

Output rules:

Like many challenges, this one is inspired by XKCD.

  1. First, "untag" all tags with nothing but spaces in them. (<i>te<b> </b>xt</i> becomes <i>te xt</i>). If a tag has nothing in it (like <b></b>, simply delete it and move on.
  2. Next, "push" the spaces to the edge. This means that to push spaces at the edge of the tag outside (so <b><i> test </i></b> becomes <b><i> test</i> </b> after one step).
  3. Keep repeating steps 1 and 2 until no new changes happen.
  4. Collapse sets of spaces. For example, <b><i>test</i></b> should become <b><i>test</i></b> .
  5. Finally, trim the string (this means remove the spaces at the edges, so " a " becomes "a"). An empty string can be the result.

Input

The input may be taken as an array of bytes or a string. The only tags will be <b>, <strong>, <i>, <u>, and <em>, and they may have additional attributes may be in the starting tags (so one starting tag could be <b data-this-is-an-attribute="true">. The ending tags, in order, are </b>, </strong>, </i>, </u>, and </em> and will not have attributes. Closing tags will be in order (so <b><i></i></b> works, but <b><i></b></i> will never be a test case). There are no newlines in the input.

Edit: attributes may be in single or double quotes.

Be sure not to follow any of the rules for the tags, especially the spaces!

"<b>  <i> </i></b>" -> "<b>   </b>" -> "   " -> "" (the quotes in this test case are to prevent confusion)
<u> x <em onclick="alert('Hi!')" style="font-size: 50px">kcd </em> comics </u> -> <u>x <em onclick="alert('Hi!')" style="font-size: 50px">kcd</em> comics</u>
<strong>Formatting<i> </i>is <u data-info='test     case'>HA     RD</u></strong>    -> <strong>Formatting is <u data-info='test     case'>HA RD</u></strong>
s p a c     e s -> s p a c e s
one<i> </i>space -> one space
&nbsp; &nbsp; -> &nbsp; &nbsp; (no changes, as we don't consider &nbsp;)

As this is , the shortest answer in bytes wins!

Note: you can use any language to do this challenge; it's just inspired by HTML.

\$\endgroup\$
11
  • \$\begingroup\$ I assume <b> in the ending tags list is a mistake? \$\endgroup\$
    – Bbrk24
    May 3, 2023 at 1:01
  • \$\begingroup\$ Thanks @Bbrk24, fixed. \$\endgroup\$
    – Infigon
    May 3, 2023 at 1:02
  • \$\begingroup\$ It still says <b> in the end tags list, but now </b> is a start tag. \$\endgroup\$
    – Bbrk24
    May 3, 2023 at 1:03
  • 1
    \$\begingroup\$ may i assume input wont contain doctype cdata comment, won’t contain script or style tag, and all lt and gt sign are escaped even in quotes? \$\endgroup\$
    – tsh
    May 3, 2023 at 4:32
  • 1
    \$\begingroup\$ May I assume attribute values always warped in double quotes, not single quotes, not w/o quotes? \$\endgroup\$
    – tsh
    May 3, 2023 at 4:36

2 Answers 2

4
\$\begingroup\$

Retina, 106 97 bytes

{`<([a-z]+)[^>]*>( )*</\1>
$2
((^|>)[^<]* ) +([^>]*($|<))
$1$3
^((<[^>]+>)*) | ((<[^>]+>)*)$
$1$3

Can definitely be golfed some more.

Try it online.

Explanation:

Keep executing the replacements until the result no longer changes:

{`

Step 1: untag all tags that only contain spaces and remove empty tags:

<([a-z]+)[^>]*>( )*</\1>
$2

Step 4: collapse sets of spaces to a single space, unless they're inside an attribute:

((^|>)[^<]* ) +([^>]*($|<))
$1$3

Step 5: 'trim' leading/trailing spaces, ignoring the tags:

^((<[^>]+>)*) | ((<[^>]+>)*)$
$1$3
\$\endgroup\$
5
  • 1
    \$\begingroup\$ I don't think this gives the correct result for te<b></b>st. \$\endgroup\$
    – Neil
    May 3, 2023 at 23:47
  • 1
    \$\begingroup\$ @Neil Woops.. That was working in my initial version, but I accidentally broke that case. Should be fixed again. \$\endgroup\$ May 4, 2023 at 6:56
  • \$\begingroup\$ @Neil Btw, two questions: 1) Is there a builtin for [a-z] outside of Transliterates in Retina? 2) Is there a way to use variables for regex-portions (e.g. the ((<[^>]+>)*) in my last replacement)? I know this is possible in Perl and Ruby style regexes, but Retina is based on .NET regexes where this unfortunately isn't possible if I'm not mistaken? \$\endgroup\$ May 4, 2023 at 7:00
  • \$\begingroup\$ Retina only uses .NET regex for matching, so those aren't possible, sorry. \$\endgroup\$
    – Neil
    May 4, 2023 at 7:41
  • \$\begingroup\$ Traceback (most recent call last): File "answer" in line 20: Step 1, step 4 and step 5 StepError: Steps must be an arithmetic sequence starting at 1 and increasing by 1 each time. \$\endgroup\$ May 5, 2023 at 18:53
2
\$\begingroup\$

Retina 0.8.2, 86 bytes

{`(<[^/>]+>) 
 $1
 (</.*?>)
$1
^ |<[^/>]+></.*?>| $|( )+|(?<=<[^>]*)((["']).*?\3)
$1$2

Try it online! Link includes test cases. Explanation:

{`

Repeat the following replacements as many times as necessary.

(<[^/>]+>) 
 $1

Move a space after an open tag to before it.

 (</.*?>)
$1

Move a space before a close tag to after it.

^ |<[^/>]+></.*?>| $|( )+|(?<=<[^>]*)((["']).*?\3)
$1$2

Handle all of the remaining cases:

  • ^ Delete leading spaces
  • <[^/>]+></.*?> Delete empty tags
  • $ Delete trailing spaces
  • ( )+|(?<=<[^>]*)((["']).*?\3) Collapse spaces not inside attributes
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.