Объединяйте тексты и оптимизируйте форматирование

У меня есть текстовые объекты (text1, text2 и т. д.) с информацией о форматировании (например, [bold, italic]). Теперь я хочу объединить эти тексты и отформатировать их в HTML.

В простом случае это преобразует следующее

text1[bold,italic]
text2[]
text3[bold]

в этот HTML:

<b><i>text1</i></b>
text2
<b>text3</b>

Но я хочу по возможности объединять форматирование, а не форматировать каждый текст по отдельности. Например, следующее

text1[bold,italic]
text2[bold]
text3[]

Должно получиться следующее HTML:

<b>
  <i>text1</i>
  text2
</b>
text3

Кроме того, я хочу перенести «самое длинное» форматирование «снаружи» DOM. Например, в следующем случае italics является более длинной цепочкой форматирования и, таким образом, обтекает элемент bold:

text1[bold,italic]
text2[italic]
text3[]

Результат:

<i>
  <b>text1</b>
  text2
</i>
text3

Каким был бы хороший алгоритмический подход для решения этой проблемы? Были бы полезны ответы в псевдокоде или на любом языке программирования.

🤔 А знаете ли вы, что...
HTML поддерживает элементы для создания списков опций и выбора из них, используемых в выпадающих меню.


120
3

Ответы:

Для минимизации общего количества записываемых тегов оптимальным является простой жадный алгоритм. В каждой позиции текста:

  1. Если какое-либо форматирование отключается, запишите конечные теги, соответствующие предыдущим открывающим тегам, пока все необходимое форматирование не будет отключено; затем
  2. Напишите начальные теги для любого форматирования, которое необходимо включить, в порядке убывания длины следующего текста, который он охватывает.

Я собираюсь описать логику и предоставить псевдокод. Если вам нужна реализация PowerShell или C#, дайте мне знать.

Я бы начал с создания пары вспомогательных функций.

  1. parseline(line_num) — функция, которая может извлечь одну строку из входных данных, извлечь текст и модификаторы и вернуть объект с текстом и массивом модификаторов. Вероятно, это регулярное выражение, которое создает объект.
  2. searchline(modifier,start_line) — функция, которая может искать в строках, начинающихся с заданной строки, те, которые соответствуют заданному модификатору. Этот вызывает вышеуказанное в целевой строке и сравнивает полученный объект. Это должно вернуть количество последующих строк, соответствующих этому модификатору.

Затем вам понадобится счетчик для отслеживания максимального номера необработанной строки (от 0 до количества строк - 1).

Создайте две таблицы поиска с тегами HTML для каждого модификатора. HTMLmodifierStart содержит начальные теги, а HTMLmodifierEnd содержит закрывающие HTML-теги для каждого модификатора.

Необязательно: код для предварительной обработки входных данных и преобразования их из текста в массив объектов с использованием первой вспомогательной функции в каждой строке.

Основной цикл

counter=0
while (counter < linecount)
{
    current_modifiers=parseLine(counter).modifiers
    hashtable = {}
    foreach (current_modifier in current_modifiers)
    {
        # build a hash table with each current modifier and the count
        # of subsequent lines matching that modifier
        hashtable[current_modifier]=searchLine(current_modifier,counter)
    }
    # work from highest to lowest modifiers by count
    # and build HTML in the output
    sorted_hashtable=sort hashtable by count descending
    # Add all opening modifier tags
    foreach (modifier in sorted_hashtable)
    {
        output+=HTMLmodifierStart[modifier]
    }
    reverse_sorted_hashtable=sort hashtable by count ascending
    # Add the text from the lines for the shortest modifier block
    # Finish with the closing HTML tag for that modifier
    foreach (modifier in reverse_sorted_hashtable)
    {
        current=counter
        while(current < counter+modifier.count)
        {
            output+=parseLine(current).text
            current++
        }
        # Add closing HTML tag
        output+=HTMLmodifierEnd(modifier)
    }
    # Increment the counter by the largest modifier processed
    counter+=largest number of lines in sorted_hashtable
}

Решено

Вот решение, которое я использовал в конце. Спасибо за все мнения здесь и в других местах. Это вывело меня на правильный путь.

Решение состоит из двух частей.

Выход

Для вывода тегов используется подход на основе стека. Вы перебираете элементы и закрываете все форматирования, которых нет в текущем элементе (удаляя форматирование и всех посредников из стека):

// close all formatting that is not in the current text
$toclose = array_diff($fStack, $formatting);
foreach($toclose as $f) {
    // we need to make sure all formatting is closed, but we close by popping the
    // stack. This ensures we don't create invalid nesting
    while (in_array($f, $fStack)) {
        $result .= $this->closeFormatting(array_pop($fStack));
    }
}

Затем мы открываем все форматирование, которого еще нет в стеке, и добавляем его в стек:

// open formatting that is in the current text but not yet open
$new = array_diff($formatting, $fStack);
foreach ($new as $f) {
    $result .= $this->openFormatting($f);
    $fStack[] = $f;
}

Наконец, вы можете вывести сам текст.

$result .= $text->__toString();
```php

After the loop close all remaining formattings on the stack.

```php
// close remaining formatting
while ($fStack) {
    $result .= $this->closeFormatting(array_pop($fStack));
}

Найдите самые длинные цепочки

Описанный выше подход уже будет работать, но теги могут быть не идеально вложенными. Например. для

text1[bold,italic]
text2[italic]
text3[]

Это может произвести <b><i>text1</i></b><i>text2</i>text3. Это зависит от того, какое форматирование вы нашли первым в $formatting для первого элемента.

Чтобы решить эту проблему, нам нужно отсортировать информацию о форматировании по самой длинной цепочке.

Например. вышеуказанная информация должна быть:

text1[italic, bold]
text2[italic]
text3[]

Чтобы добиться этого, я заранее выполняю простой этап сортировки цепочки. Вы просматриваете тексты назад (начиная с предпоследнего) и предоставляете ему информацию о том, сколько одинаковых форматирований будет после этого. Затем вы сортируете форматирование по длине цепочки (сначала самая высокая).

Вышеупомянутое в конечном итоге будет выглядеть примерно так:

text1[italic:2, bold:1]
text2[italic:1]
text3[]

петля:

function updateFormattingScores()
{
    $len = count($this->texts);
    if ($len < 2) return;
    for($i = $len - 2; $i >= 0; $i--) {
         $this->texts[$i]->updateFormattingScores($this->texts[$i + 1]);
    }
}

обновлять:

public function updateFormattingScores(TextRun $nextRun)
{
    $next = $nextRun->getFormatting();
    foreach ($next as $key => $value) {
        if ($this->formatting[$key] === 0) continue; // we don't have this format
        $this->formatting[$key] += $value; // add follow up chain
    }

    // sort by value, longest chains first
    arsort($this->formatting);
}

При этом запуске перед выводом вывод всегда сначала находит форматирование более длинной цепочки.