У меня есть текстовые объекты (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 поддерживает элементы для создания списков опций и выбора из них, используемых в выпадающих меню.
Для минимизации общего количества записываемых тегов оптимальным является простой жадный алгоритм. В каждой позиции текста:
Я собираюсь описать логику и предоставить псевдокод. Если вам нужна реализация PowerShell или C#, дайте мне знать.
Я бы начал с создания пары вспомогательных функций.
Затем вам понадобится счетчик для отслеживания максимального номера необработанной строки (от 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);
}
При этом запуске перед выводом вывод всегда сначала находит форматирование более длинной цепочки.