Formatting simple HTML

kata programming

مساله:

یک منبع را با نحوه HTML ساده فرمت بندی کنید.

منبع شامل تگ ها و متن است.

این لزوماً HTML معتبر یا یک سند کامل نیست (ممکن است یک قطعه باشد).

تگ ها و متن

تگ ها یا به صورت تگ های باز و تگ های بسته (<tag>محتوی</tag>) یا به صورت خود بسته (</tag>) می باشد.

همه تگ ها* باید در خط خود باشند.

هیچ برچسب درون خطی وجود ندارد. محتوای بین برچسب های باز و بسته باید تورفتگی داشته باشد. هیچ فضای خالی (غیر از تورفتگی/خط جدید) نباید قبل یا بعد از برچسب ها باشد.

همه متن (متوالی) باید در خط خود باشد. ممکن است متن دارای فضای خالی نادرست باشد. این فضاها باید به صورت فضای واحد جمع شود. (فضای خالی داخل تگ ها را مجدداً قالب بندی نکنید.)

متن نباید با فضای خالی(غیر از تورفتگی/خط جدید) شروع یا پایان یابد.

استثنا*: تگ </br>، زمانی که بعد از یک تگ نباشد، باید متنی که بعد از آن وجود دارد بدون هیچ مداخله ای به خط بعد برود.

باید توسط یک خط جدید دنبال شود.

جزئیات:

  • خطوط جدید باید n\ داشته باشند
  • تورفتگی ها باید دو فاصله در هر سطح باشد
  • فضای خالی در متن باید دارای فضاهای تکی باشد
  • منبع اصلاح شده باید با خط جدید خاتمه یابد
  • یک بررسی سلامت وجود خواهد داشت: تکرار برنامه نباید خروجی را تغییر دهد
  • همه ورودی ها معتبر است
  • هیچ فضای خالی در تگ ها تا بعد از تگ وجود نخواهد داشت و هیچ فضای خالی قبل از بسته شدن تگ وجود نخواهد داشت.

Task

Format a source in a simple HTML-dialect.

A source consists of tags and text.
It is not necessarily valid HTML, or a complete document ( it may be a snippet ).

Tags and text

Tags are either matching opening and closing tags ( <tag>content</tag> ), or self-closed ( <tag /> ).
All tags* need to be on their own line. There are no inline tags.
Content between opening and closing tags should be indented.
No whitespace ( other than indent/newline ) should be before or after tags.

All ( consecutive ) text needs to be on its own line.
Text may have spurious whitespace. This needs to be collapsed to single spaces.
( Do not reformat whitespace inside tags. )
Text should not begin or end with whitespace ( other than indent/newline ).

* Exception: the <br /> tag, when not after a tag, should be after its text without an intervening newline.
It should be followed by a newline.

Details

Newlines must be \n

Indents must be two spaces per level

Whitespace in text must be single spaces

Reformatted source must end with a newline

There will be a sanity check: repeated application should not change the output

All input is valid

There will be no whitespace in tags until after the tag ( <tag or </tag ), and
there will be no whitespace directly before the closing > ( tag> or /> )

Preloaded

For testing and debugging, Utility class with static methods escHTML and escWS has been predefined;
they escape HTML special characters ( & < > ) and whitespace ( tab, newline and space ) respectively for printing to the console.
When using both, apply escWS after escHTML, or your spaces will come out as &amp;space;.


import java.util.function.*;
import java.util.regex.*;

public class Solution {
    private static final Pattern TOKENS = Pattern.compile("<[^>]*>|[^<\\s]+");
    private static final Predicate<String> CLOSING_TAG = Pattern.compile("^</").asPredicate();
    private static final Predicate<String> OPENING_TAG = Pattern.compile("^<(?!/).*[^/]>$").asPredicate();
    private static final Predicate<String> TAG = Pattern.compile("^<(?!br)").asPredicate();
    
    public static String indent(String source) {
        StringBuilder result = new StringBuilder();
        String[] tokens = TOKENS.matcher(source).results().map(MatchResult::group).toArray(String[]::new);
        for (int i = 0, indent = 0; i < tokens.length; i++) {
            if (CLOSING_TAG.test(tokens[i])) {
                indent--;
            }
            if (result.length() > 0 && result.charAt(result.length() - 1) == '\n') {
                result.append("  ".repeat(indent));
            }
            result.append(tokens[i]);
            if (OPENING_TAG.test(tokens[i])) {
                indent++;
            }
            if (tokens[i].startsWith("<") || TAG.test((i < tokens.length - 1 ? tokens[i + 1] : "<>"))) {
                result.append('\n');
            } else if (!(i < tokens.length - 1 ? tokens[i + 1] : "").startsWith("<br")) {
                result.append(' ');
            }
        }
        return result.toString();
    }
}

دیدگاهتان را بنویسید