LINUX.ORG.RU

Метод QTextEdit::insertHtml() съедает ведущие пробелы. Как обойти?

 ,


2

3

Решаю сейчас в своем WYSIWYG-редакторе следующую задачу:


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

Задача:
Написать функцию (метод), очищающий форматирование выделенного текста в виджете-наследнике QTextEdit.

https://github.com/xintrea/mytetra_dev/issues/5

То есть, нужно изменить только начертание символов. Ссылки должны остаться ссылками. Картинки должны остаться картинками. Таблицы должны остаться таблицами. Изменяется только начертание.

При кажущейся простоте, подводных камней у этой задачи много. Сейчас я почти достиг цели - написал жуткий magick-код, который берет HTML-код выделенного фрагмета текста, обрабатывает его (убирает лишнее, подменяет теги, компенсирует многочисленные закидоны Qt-овского HTML-движка), и вставляет его обратно.

Такая методика наконец-таки заработала чисто, за одним маленьким исключением: при вставке HTML-кода через метод insertHtml(), съедаются ведущие пробелы в тексте. То есть, если в тексте встретится кусок исходного кода, то indentation у него слетит.

Вот пример HTML-кода до вставки и что реально вставилось:

Вставляли:

<p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;"><span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">    QMessageBox msgBox;</span></p>
<p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;"><span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">    msgBox.setText(welcomeText);</span></p>
<p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;"><span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">    msgBox.setInformativeText(infoText);</span></p>


Получили:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><!--StartFragment-->QMessageBox msgBox;</p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">msgBox.setText(welcomeText);</p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">msgBox.setInformativeText(infoText);<!--EndFragment--></p></body></html>


Видно, что ведущие четыре пробела в каждой строчке исчезли.

Код, который это делает:

...
  qDebug() << htmlCode;

  // Вставка очищенного фрагмента
  textArea->textCursor().insertHtml(htmlCode);

  // Выделение только что вставленного фрагмента (это тоже магия)
  cursor.movePosition(QTextCursor::End);
  int afterClearLen=cursor.position();
  int calculateEndCursorPos=startCursorPos + (afterClearLen - afterRemoveSelectionLen);
  cursor.setPosition(startCursorPos, QTextCursor::MoveAnchor);
  cursor.setPosition(calculateEndCursorPos, QTextCursor::KeepAnchor);
  textArea->setTextCursor(cursor);

  qDebug() << textArea->textCursor().selection().toHtml();
...


Вопрос: как можно обойти эту особенность Qt, чтобы в данном случае все пробелы сохранялись?

Я уже готов выбрать редко используемый UTF-8 символ (какой-нибудь иероглиф), заменить им все пробелы во вставляемом HTML, а потом заменить в документе эти символы на пробелы. Или не иероглиф, а нигде-никогда-не-встречаемую-строку. Но это же все неправильно (вдруг иероглиф кто-то использует, или так получится, что уникальная строка таки встретится). А хотелось бы правильно.

А как правильно?


Последний актуальный коммит: https://github.com/xintrea/mytetra_dev/commit/11bde04320971b28420f1fba217ad0e...

★★★★★

Ответ на: комментарий от anonymous

Вот сам подумай. Ты предлагаешь заменить пробелы на неразрывные пробелы. А потом их нужно поменять на обычные пробелы.

Вопрос. Как ты отличишь неразрывные пробелы, которые внесены были искусственно от неразрывных пробелов, которые изначально были в тексте?

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

действительно. на на самом деле тебе нужно только первый заменить. А потом нужно ли их обратно потом менять?

anonymous ()
Ответ на: комментарий от anonymous

Чтобы это проверить, нужно решить другую задачу: как в HTML поменять пробел " " на на неразрывный пробел «амперсандnbsp;» так, чтобы захватились только пробелы в тексте. А пробелы в тегах и в атрибутах не затронулись.

Что-то я не могу составить такую заковыристую регулярку. В Qt в QRegEx еще есть проблема обработки многострочных строк - если регулярка попадет на перенос строки, она не отработает.

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

эм. я думал оно там разбирает html в дерево и поэтому, собссно, пробелы пропадают. Кстати в эту сторону не смотрел?

Регуляркой тогда хз. только если любой пробел после «>» считать текстом.

anonymous ()

Распарсить HTML полноценным парсером и вставить полученный текст без использования insertHtml

annulen ★★★★★ ()
Ответ на: комментарий от annulen

У меня кроссплатформенная программа по методике Qt-only. Есть какой-то полноценный HTML-парсер, чтобы вставить его жестко в код?

Xintrea ★★★★★ ()
Ответ на: комментарий от anonymous

Есть, но стандарт HTML не является подмножеством XML, хотя нам везде трут обратное.

В HTML есть четыре тега, которые не должны закрываться. И хрен его знает как они реализованы в Qt.

Xintrea ★★★★★ ()
Ответ на: комментарий от annulen

Насколько эта хрень стабильна, чтобы включать ее в свой код, который будет парсить пользовательские данные?

Xintrea ★★★★★ ()
Ответ на: комментарий от annulen

В общем, решил для пробы распарсить через DOM XML.

Загружаю HTML код в QDomDocument. Прочесываю его рекурсивно такой функцией:

void TypefaceFormatter::recurseReplaceSpaces(const QDomNode &node)
{
  QDomNode domNode = node.firstChild();
  QDomText domText;

  // Если текущий элемент существует
  while(!(domNode.isNull()))
  {
    // Если узел - это текст
    if(domNode.isText())
    {
      QDomText domText = domNode.toText();
      if(!domText.isNull())
      {
        QString text=domText.data();
        text.replace(" ", "&nbsp;");
        qDebug() << "Replace spaces: " << text;

        // В узле устанавливается новая строка
        domNode.setNodeValue(text);
      }
    }

    recurseReplaceSpaces(domNode);
    domNode = domNode.nextSibling();
  }
}


В ходе ее работы вижу:

00:10:33 [DBG] Replace spaces:  "&nbsp;&nbsp;&nbsp;QMessageBox&nbsp;msgBox;"
00:10:33 [DBG] Replace spaces:  "&nbsp;&nbsp;&nbsp;msgBox.setText(welcomeText);"
00:10:33 [DBG] Replace spaces:  "&nbsp;&nbsp;&nbsp;msgBox.setInformativeText(infoText);"


Казалось бы, вопрос решен? Нет!

Когда генерируем HTML из этого DOM-дерева через метод doc.toString(), символ «амперсанд» заменяется на «амперсандamp;», и мы получаем каку:

 <p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">
  <span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">&amp;nbsp;&amp;nbsp;&amp;nbsp;QMessageBox&amp;nbsp;msgBox;</span>
 </p>
 <p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">
  <span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">&amp;nbsp;&amp;nbsp;&amp;nbsp;msgBox.setText(welcomeText);</span>
 </p>
 <p style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">
  <span style="margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;">&amp;nbsp;&amp;nbsp;&amp;nbsp;msgBox.setInformativeText(infoText);</span>
 </p>


В какой момент происходит эта подмена, понять не могу. То ли сразу при установке значения текстового узла, то ли при генерации doc.toString().

Факт в том, что из DOM не могу получить HTML с «aперсандnbsp;». А это ключевое ключевое действие, которое надо сделать.

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

Это решает xml schema в которой написаны правила кто, где и как должен закрываться а кто не должен.

deep-purple ★★★★★ ()
Ответ на: комментарий от Xintrea

Загружаю HTML код в QDomDocument

fail. QDomDocument может парсить только XHTML, а речь шла о HTML

annulen ★★★★★ ()
Ответ на: комментарий от Xintrea

А вот это мне знакомо, правда не из культей, да, есть такой касяк, ищи, где-то должен быть опшн которым можно указать что энтитисы не надо еще раз конвертить.

deep-purple ★★★★★ ()
<span style="white-space: pre;">&#32;&#32;&#32;&#32;</span>

Либо pre-wrap, в зависимости от того, что тебе нужно.

anonymous ()

...съедаются ведущие пробелы в тексте. То есть, если в тексте встретится кусок исходного кода, то indentation у него слетит.

ЕМНИП, в HTML количество пробелов, а равно наличие ведущих пробелов может считаться ценной информацией только внутри тега <pre>. Во всех остальных местах пробелы - это исключительно инструмент для удобства чтения HTML-кода, и любой форматёр имеет законное право их «съесть» или, наоборот, добавить.

...Ну или то, что анон комментарием выше посоветовал, как я понимаю, это то же самое, только через CSS.

hobbit ★★★★★ ()
Последнее исправление: hobbit (всего исправлений: 2 )

я бы вставил в текст лидирующие непробельные символы, а потом удалил и не колебал себе мозг

next_time ★★★★★ ()
Ответ на: комментарий от deep-purple

где-то должен быть опшн которым можно указать что энтитисы не надо еще раз конвертить.

Вот я эту опцию нигде не нашел.

В QDomDocument при создании ничего такого указать нельзя. Поиск по подстроке «entity» в доке ничего внятного не показывает. Гоголь по запросу «Qt QDomDocument toString ampersand» дает только одну релевантную ссылку, в которой похожий вопрос ставится, но не решается.

Сцук, я застрял.

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

Может просто regex'ом по нему пройтись? Зачем множить сущности без необходимости...

anonymous ()

Понять где Qt делает такие фокусы можно отладив код insertHtml(). Но это жутко гемморойно и наверняка тебе не под силу. Выходов несколько: 1) Попробовать сильно разные версии Qt. 2) Попросить того кто тебе дал это задание смягчить требования. Это ведь не у тебя проблемы и с большей частью задания ты справился. Даже при коммерческой разработке это нормальная ситуация. 3) Если попытаться понять чего там хотели сделать этим разрабы Qt. Думаю по их мнению форматирование начальными пробелами не имеет смысла. Есть же разнообразные padding и margin для этих целей. А форматирование лидирующими и завершающими пробелами это ошибка. И они такие заботливые эти ошибки исправляют. Возможно даже стандарт html говорит что эти пробелы не должны учитываться движком. Точно не знаю. В общем ты можешь просто посчитать эти самые пробелы в начале каждой строки и обернуть текст в div или span к которому добавишь соответствующий padding или margin слева. Т.е. можешь слегка допилить свой magic код.

Но самый правильный вариант 2.

khenty ()
Ответ на: комментарий от deep-purple

Это какая-то жопа. У Qt реально свой уличный HTML.

Я сделал преобразование выхлопа DOM->XML вот так:

doc.toString().replace("&amp;nbsp;", "&nbsp;");


И вроде все получилось. После вставки в документ строки:

<p>&nbsp;&nbsp;&nbsp;QMessageBox msgBox;</p>


при последующем просмотре получившегося HTML всего QDomDocument мы видим вполне безобидный код:

<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">   QMessageBox msgBox;</p>


Казалось бы, пробелы (перед QMessageBox) сохранены, что еще нужно?

Так вот, оказывается эти пробелы - не просто пробелы. В самом WYSIWYG-редакторе это неразрывные пробелы! И их в HTML-коде никак не отличишь от обычных!!


А если вместо

&nbsp;


попытаться использовать

&#32;


то пробелы в результирующем документе вставляются обычные. Но они при вставке HTML в QDomDocument «смыкаются».



Xintrea ★★★★★ ()
Ответ на: комментарий от khenty

Попросить того кто тебе дал это задание смягчить требования. Это ведь не у тебя проблемы и с большей частью задания ты справился. Даже при коммерческой разработке это нормальная ситуация.

Насколько я понимаю, автор себе сам это задание поставил. В коммерческом проекте действительно возможны разные варианты, а вот в своём хочется совершенства...

hobbit ★★★★★ ()
Последнее исправление: hobbit (всего исправлений: 1 )
Ответ на: комментарий от hobbit

Меня вымораживат то, что элементарная задачка по сбросу форматирования вылилась в непрерывную череду мучений на протяжении всего времени существования программы, а это больше пяти лет. Я уж думал что добрался до решения, но нет.

Xintrea ★★★★★ ()
Ответ на: комментарий от deep-purple

В общем, я надыбал такую штуку в уникоде - OBJECT REPLACEMENT CHARACTER. Кратко говоря, это такой символ, который, в частности, можно использовать для временного обозначения любого места в тексте, на котором расположен некий объект.

http://www.marathon-studios.com/unicode/UFFFC/Object_Replacement_Character

Попробую обозначать пробелы через него, а потом заменять средствами QTextDocument.

Xintrea ★★★★★ ()
Ответ на: комментарий от deep-purple

Ага, в Qt еще и свой, особенный Unicode. Символ UFFFC (Object Replacement Character) бесследно изчезает при вставке в QTextDocument.

Зато нормально сработал символ UFFFD (Replacement Character). Он его не удаляет, не смыкает, нормально принимает из HTML, и его можно заменить средствами курсора QTextCursor.

Xintrea ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.