Evgenii Legotckoi
13 января 2017 г. 20:15

Qt/C++ - Урок 058. Подсветка синтаксиса HTML кода в QTextEdit

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

Подсветка синтаксиса HTML в QTextEdit будет выглядеть следующим образом:


Структура проекта

Проект состоит из следующих файлов:

  • HTMLExample.pro - профайл проекта;
  • main.cpp - стартовый файл проекта;
  • mainwindow.h - заголовочный файл окна приложения;
  • mainwindow.cpp - файл исходных кодов окна приложения;
  • mainwindow.ui - файл интерфейса;
  • HTMLHighLighter.h - заголовочный файл класса для подсветки HTML кода;
  • HTMLHighLighter.cpp - файл исходных кодов класса для подсветка HTML кода;

main.cpp, HTMLExample.pro - создаются по умолчанию, в mainwindow.ui добавляем только QTextEdit .

mainwindow.h

Здесь подключаем заголовочный файл HTMLHighLighter и объявляем его объект.

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include "HTMLHighLighter.h"
  6.  
  7. namespace Ui {
  8. class MainWindow;
  9. }
  10.  
  11. class MainWindow : public QMainWindow
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. explicit MainWindow(QWidget *parent = 0);
  17. ~MainWindow();
  18.  
  19. private:
  20. Ui::MainWindow *ui;
  21. HtmlHighLighter *m_htmlHightLighter;
  22. };
  23.  
  24. #endif // MAINWINDOW_H

mainwindow.cpp

А в данном файле просто устанавливаем объект HTMLHighLighter в документ объекта QTextEdit .

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. MainWindow::MainWindow(QWidget *parent) :
  5. QMainWindow(parent),
  6. ui(new Ui::MainWindow)
  7. {
  8. ui->setupUi(this);
  9. m_htmlHightLighter = new HtmlHighLighter(ui->textEdit->document());
  10. }
  11.  
  12. MainWindow::~MainWindow()
  13. {
  14. delete ui;
  15. }

HTMLHighLighter.h

Особенность подсветки синтаксиса кода или просто текста в QTextEdit заключается в том, что класс QSyntaxtHighLighter перебирает все текстовые блоки, на которые разделён текст (разбит с помощью символа перевода строки) и определяет, как подсвечивать текущий блок с самого начала в зависимости от состояния подсветки предыдущего текстового блока.

Естественно это необходимо реализовать. В конструкторе класса будут инициализированы правила подсветки различных частей кода и шаблоны, по которым будут определяться эти части кода. А в методе highlightBlock(const QString &text), будет реализована логика обработки текста.

  1. #ifndef HTMLHIGHLIGHTER_H
  2. #define HTMLHIGHLIGHTER_H
  3.  
  4. #include <QSyntaxHighlighter>
  5.  
  6. QT_BEGIN_NAMESPACE
  7. class QTextDocument;
  8. class QTextCharFormat;
  9. QT_END_NAMESPACE
  10.  
  11. class HtmlHighLighter : public QSyntaxHighlighter
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. explicit HtmlHighLighter(QTextDocument *parent = 0);
  17.  
  18. protected:
  19. void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
  20.  
  21. private:
  22. // Состояние подсветки, в которой находится текстовый блок на момент его закрытия
  23. enum States {
  24. None, // Без подсветки
  25. Tag, // Подсветка внутри тега
  26. Comment, // Внутри комментария
  27. Quote // Внутри кавычек, которые внутри тега
  28. };
  29.  
  30. struct HighlightingRule
  31. {
  32. QRegExp pattern;
  33. QTextCharFormat format;
  34. };
  35. QVector<HighlightingRule> startTagRules; // Правила форматирования для открывающих тегов
  36. QVector<HighlightingRule> endTagRules; // Правила форматирования для закрывающих тегов
  37.  
  38. QRegExp openTag; // Символ открытия тега - "<"
  39. QRegExp closeTag; // Символ закрытия тег - ">"
  40. QTextCharFormat edgeTagFormat; // Форматирование символов openTag и closeTag
  41. QTextCharFormat insideTagFormat; // Форматирование текста внутри тега
  42.  
  43. QRegExp commentStartExpression; // Регулярка начала комментария
  44. QRegExp commentEndExpression; // Регулярка закрытия комментария
  45. QTextCharFormat multiLineCommentFormat; // Форматирование текста внутри комментария
  46.  
  47. QRegExp quotes; // Регулярное выражение для текста в кавычках внутри тега
  48. QTextCharFormat quotationFormat; // Форматирование текста в кавычках внутри тега
  49. QTextCharFormat tagsFormat; // Форматирование самих тегов
  50. };
  51.  
  52. #endif // HTMLHIGHLIGHTER_H

HTMLHighLighter.cpp

  1. #include "HTMLHighLighter.h"
  2. #include <QTextCharFormat>
  3. #include <QBrush>
  4. #include <QColor>
  5.  
  6.  
  7. HtmlHighLighter::HtmlHighLighter(QTextDocument *parent)
  8. : QSyntaxHighlighter(parent)
  9. {
  10. HighlightingRule rule;
  11.  
  12. edgeTagFormat.setForeground(QBrush(QColor("#32a9dd")));
  13. insideTagFormat.setForeground(Qt::blue);
  14. insideTagFormat.setFontWeight(QFont::Bold);
  15. openTag = QRegExp("<");
  16. closeTag = QRegExp(">");
  17.  
  18. tagsFormat.setForeground(Qt::darkBlue);
  19. tagsFormat.setFontWeight(QFont::Bold);
  20.  
  21. QStringList keywordPatterns;
  22. keywordPatterns << "<\\ba\\b" << "<\\babbr\\b" << "<\\bacronym\\b" << "<\\baddress\\b" << "<\\bapplet\\b"
  23. << "<\\barea\\b" << "<\\barticle\\b" << "<\\baside\\b" << "<\\baudio\\b" << "<\\bb\\b"
  24. << "<\\bbase\\b" << "<\\bbasefont\\b" << "<\\bbdi\\b" << "<\\bbdo\\b" << "<\\bbgsound\\b"
  25. << "<\\bblockquote\\b" << "<\\bbig\\b" << "<\\bbody\\b" << "<\\bblink\\b" << "<\\bbr\\b"
  26. << "<\\bbutton\\b" << "<\\bcanvas\\b" << "<\\bcaption\\b" << "<\\bcenter\\b" << "<\\bcite\\b"
  27. << "<\\bcode\\b" << "<\\bcol\\b" << "<\\bcolgroup\\b" << "<\\bcommand\\b" << "<\\bcomment\\b"
  28. << "<\\bdata\\b" << "<\\bdatalist\\b" << "<\\bdd\\b" << "<\\bdel\\b" << "<\\bdetails\\b"
  29. << "<\\bdfn\\b" << "<\\bdialog\\b" << "<\\bdir\\b" << "<\\bdiv\\b" << "<\\bdl\\b"
  30. << "<\\bdt\\b" << "<\\bem\\b" << "<\\bembed\\b" << "<\\bfieldset\\b" << "<\\bfigcaption\\b"
  31. << "<\\bfigure\\b" << "<\\bfont\\b" << "<\\bfooter\\b" << "<\\bform\\b" << "<\\bframe\\b"
  32. << "<\\bframeset\\b" << "<\\bh1\\b" << "<\\bh2\\b" << "<\\bh3\\b" << "<\\bh4\\b"
  33. << "<\\bh5\\b" << "<\\bh6\\b" << "<\\bhead\\b" << "<\\bheader\\b" << "<\\bhgroup\\b"
  34. << "<\\bhr\\b" << "<\\bhtml\\b" << "<\\bi\\b" << "<\\biframe\\b" << "<\\bimg\\b"
  35. << "<\\binput\\b" << "<\\bins\\b" << "<\\bisindex\\b" << "<\\bkbd\\b" << "<\\bkeygen\\b"
  36. << "<\\blabel\\b" << "<\\blegend\\b" << "<\\bli\\b" << "<\\blink\\b" << "<\\blisting\\b"
  37. << "<\\bmain\\b" << "<\\bmap\\b" << "<\\bmarquee\\b" << "<\\bmark\\b" << "<\\bmenu\\b"
  38. << "<\\bamenuitem\\b" << "<\\bmeta\\b" << "<\\bmeter\\b" << "<\\bmulticol\\b" << "<\\bnav\\b"
  39. << "<\\bnobr\\b" << "<\\bnoembed\\b" << "<\\bnoindex\\b" << "<\\bnoframes\\b" << "<\\bnoscript\\b"
  40. << "<\\bobject\\b" << "<\\bol\\b" << "<\\boptgroup\\b" << "<\\boption\\b" << "<\\boutput\\b"
  41. << "<\\bp\\b" << "<\\bparam\\b" << "<\\bpicture\\b" << "<\\bplaintext\\b" << "<\\bpre\\b"
  42. << "<\\bprogress\\b" << "<\\bq\\b" << "<\\brp\\b" << "<\\brt\\b" << "<\\brtc\\b" << "<\\bruby\\b"
  43. << "<\\bs\\b" << "<\\bsamp\\b" << "<\\bscript\\b" << "<\\bsection\\b" << "<\\bselect\\b"
  44. << "<\\bsmall\\b" << "<\\bsource\\b" << "<\\bspacer\\b" << "<\\bspan\\b" << "<\\bstrike\\b"
  45. << "<\\bstrong\\b" << "<\\bstyle\\b" << "<\\bsub\\b" << "<\\bsummary\\b" << "<\\bsup\\b"
  46. << "<\\btable\\b" << "<\\btbody\\b" << "<\\btd\\b" << "<\\btemplate\\b" << "<\\btextarea\\b"
  47. << "<\\btfoot\\b" << "<\\bth\\b" << "<\\bthead\\b" << "<\\btime\\b" << "<\\btitle\\b"
  48. << "<\\btr\\b" << "<\\btrack\\b" << "<\\btt\\b" << "<\\bu\\b" << "<\\bul\\b" << "<\\bvar\\b"
  49. << "<\\bvideo\\b" << "<\\bwbr\\b" << "<\\bxmp\\b";
  50.  
  51. for (const QString &pattern : keywordPatterns)
  52. {
  53. rule.pattern = QRegExp(pattern);
  54. rule.format = tagsFormat;
  55. startTagRules.append(rule);
  56. }
  57.  
  58. QStringList keywordPatterns_end;
  59. keywordPatterns_end << "<!\\bDOCTYPE\\b" << "</\\ba\\b" << "</\\babbr\\b" << "</\\bacronym\\b" << "</\\baddress\\b" << "</\\bapplet\\b"
  60. << "</\\barea\\b" << "</\\barticle\\b" << "</\\baside\\b" << "</\\baudio\\b" << "</\\bb\\b"
  61. << "</\\bbase\\b" << "</\\bbasefont\\b" << "</\\bbdi\\b" << "</\\bbdo\\b" << "</\\bbgsound\\b"
  62. << "</\\bblockquote\\b" << "</\\bbig\\b" << "</\\bbody\\b" << "</\\bblink\\b" << "</\\bbr\\b"
  63. << "</\\bbutton\\b" << "</\\bcanvas\\b" << "</\\bcaption\\b" << "</\\bcenter\\b" << "</\\bcite\\b"
  64. << "</\\bcode\\b" << "</\\bcol\\b" << "</\\bcolgroup\\b" << "</\\bcommand\\b" << "</\\bcomment\\b"
  65. << "</\\bdata\\b" << "</\\bdatalist\\b" << "</\\bdd\\b" << "</\\bdel\\b" << "</\\bdetails\\b"
  66. << "</\\bdfn\\b" << "</\\bdialog\\b" << "</\\bdir\\b" << "</\\bdiv\\b" << "</\\bdl\\b"
  67. << "</\\bdt\\b" << "</\\bem\\b" << "</\\bembed\\b" << "</\\bfieldset\\b" << "</\\bfigcaption\\b"
  68. << "</\\bfigure\\b" << "</\\bfont\\b" << "</\\bfooter\\b" << "</\\bform\\b" << "</\\bframe\\b"
  69. << "</\\bframeset\\b" << "</\\bh1\\b" << "</\\bh2\\b" << "</\\bh3\\b" << "</\\bh4\\b"
  70. << "</\\bh5\\b" << "</\\bh6\\b" << "</\\bhead\\b" << "</\\bheader\\b" << "</\\bhgroup\\b"
  71. << "</\\bhr\\b" << "</\\bhtml\\b" << "</\\bi\\b" << "</\\biframe\\b" << "</\\bimg\\b"
  72. << "</\\binput\\b" << "</\\bins\\b" << "</\\bisindex\\b" << "</\\bkbd\\b" << "</\\bkeygen\\b"
  73. << "</\\blabel\\b" << "</\\blegend\\b" << "</\\bli\\b" << "</\\blink\\b" << "</\\blisting\\b"
  74. << "</\\bmain\\b" << "</\\bmap\\b" << "</\\bmarquee\\b" << "</\\bmark\\b" << "</\\bmenu\\b"
  75. << "</\\bamenuitem\\b" << "</\\bmeta\\b" << "</\\bmeter\\b" << "</\\bmulticol\\b" << "</\\bnav\\b"
  76. << "</\\bnobr\\b" << "</\\bnoembed\\b" << "</\\bnoindex\\b" << "</\\bnoframes\\b" << "</\\bnoscript\\b"
  77. << "</\\bobject\\b" << "</\\bol\\b" << "</\\boptgroup\\b" << "</\\boption\\b" << "</\\boutput\\b"
  78. << "</\\bp\\b" << "</\\bparam\\b" << "</\\bpicture\\b" << "</\\bplaintext\\b" << "</\\bpre\\b"
  79. << "</\\bprogress\\b" << "</\\bq\\b" << "</\\brp\\b" << "</\\brt\\b" << "</\\brtc\\b" << "</\\bruby\\b"
  80. << "</\\bs\\b" << "</\\bsamp\\b" << "</\\bscript\\b" << "</\\bsection\\b" << "</\\bselect\\b"
  81. << "</\\bsmall\\b" << "</\\bsource\\b" << "</\\bspacer\\b" << "</\\bspan\\b" << "</\\bstrike\\b"
  82. << "</\\bstrong\\b" << "</\\bstyle\\b" << "</\\bsub\\b" << "</\\bsummary\\b" << "</\\bsup\\b"
  83. << "</\\btable\\b" << "</\\btbody\\b" << "</\\btd\\b" << "</\\btemplate\\b" << "</\\btextarea\\b"
  84. << "</\\btfoot\\b" << "</\\bth\\b" << "</\\bthead\\b" << "</\\btime\\b" << "</\\btitle\\b"
  85. << "</\\btr\\b" << "</\\btrack\\b" << "</\\btt\\b" << "</\\bu\\b" << "</\\bul\\b" << "</\\bvar\\b"
  86. << "</\\bvideo\\b" << "</\\bwbr\\b" << "</\\bxmp\\b";
  87.  
  88. for (const QString &pattern : keywordPatterns_end)
  89. {
  90. rule.pattern = QRegExp(pattern);
  91. rule.format = tagsFormat;
  92. endTagRules.append(rule);
  93. }
  94.  
  95. multiLineCommentFormat.setForeground(Qt::darkGray);
  96. commentStartExpression = QRegExp("<!--");
  97. commentEndExpression = QRegExp("-->");
  98.  
  99. quotationFormat.setForeground(Qt::darkGreen);
  100. quotes = QRegExp("\"");
  101. }
  102.  
  103. void HtmlHighLighter::highlightBlock(const QString &text)
  104. {
  105. setCurrentBlockState(HtmlHighLighter::None);
  106.  
  107. // TAG
  108. int startIndex = 0;
  109. // Если не находимся внутри тега,
  110. if (previousBlockState() != HtmlHighLighter::Tag && previousBlockState() != HtmlHighLighter::Quote)
  111. {
  112. // То пытаемся найти начало следующего тега
  113. startIndex = openTag.indexIn(text);
  114. }
  115.  
  116. // Забираем состояние предыдущего текстового блока
  117. int subPreviousTag = previousBlockState();
  118. while (startIndex >= 0)
  119. {
  120. // ищем символ конца тега
  121. int endIndex = closeTag.indexIn(text, startIndex);
  122.  
  123. int tagLength;
  124. // если конец тега не найден, то устанавливаем состояние блока
  125. if (endIndex == -1)
  126. {
  127. setCurrentBlockState(HtmlHighLighter::Tag);
  128. tagLength = text.length() - startIndex;
  129. }
  130. else
  131. {
  132. tagLength = endIndex - startIndex + closeTag.matchedLength();
  133. }
  134.  
  135. // Устанавливаем форматирования для тега
  136. if (subPreviousTag != HtmlHighLighter::Tag)
  137. {
  138. // с начала тега и до конца, если предыдущее состояние не равнялось Tag
  139. setFormat(startIndex, 1, edgeTagFormat);
  140. setFormat(startIndex + 1, tagLength - 1, insideTagFormat);
  141. }
  142. else
  143. {
  144. // Если же находимся уже внутри тега с самого начала блока
  145. // и до конца тега
  146. setFormat(startIndex, tagLength, insideTagFormat);
  147. subPreviousTag = HtmlHighLighter::None;
  148. }
  149. // Форматируем символ конца тега
  150. setFormat(endIndex, 1, edgeTagFormat);
  151.  
  152. /// QUOTES ///////////////////////////////////////
  153. int startQuoteIndex = 0;
  154. // Если не находимся в кавычках с предыдущего блока
  155. if (previousBlockState() != HtmlHighLighter::Quote)
  156. {
  157. // То пытаемся найти начало кавычек
  158. startQuoteIndex = quotes.indexIn(text, startIndex);
  159. }
  160.  
  161. // Подсвечиваем все кавычки внутри тега
  162. while (startQuoteIndex >= 0 && ((startQuoteIndex < endIndex) || (endIndex == -1)))
  163. {
  164. int endQuoteIndex = quotes.indexIn(text, startQuoteIndex + 1);
  165. int quoteLength;
  166. if (endQuoteIndex == -1)
  167. {
  168. // Если закрывающая кавычка не найдена, то устанавливаем состояние Quote для блока
  169. setCurrentBlockState(HtmlHighLighter::Quote);
  170. quoteLength = text.length() - startQuoteIndex;
  171. }
  172. else
  173. {
  174. quoteLength = endQuoteIndex - startQuoteIndex + quotes.matchedLength();
  175. }
  176.  
  177. if ((endIndex > endQuoteIndex) || endIndex == -1)
  178. {
  179. setFormat(startQuoteIndex, quoteLength, quotationFormat);
  180. startQuoteIndex = quotes.indexIn(text, startQuoteIndex + quoteLength);
  181. }
  182. else
  183. {
  184. break;
  185. }
  186. }
  187. //////////////////////////////////////////////////
  188. // Снова ищем начало тега
  189. startIndex = openTag.indexIn(text, startIndex + tagLength);
  190. }
  191.  
  192. // EDGES OF TAGS
  193. // Обработка цвета саимх тегов, то есть подсветка слов div, p, strong и т.д.
  194. for (const HighlightingRule &rule : startTagRules)
  195. {
  196. QRegExp expression(rule.pattern);
  197. int index = expression.indexIn(text);
  198. while (index >= 0)
  199. {
  200. int length = expression.matchedLength();
  201. setFormat(index + 1, length - 1, rule.format);
  202. index = expression.indexIn(text, index + length);
  203. }
  204. }
  205.  
  206. for (const HighlightingRule &rule : endTagRules)
  207. {
  208. QRegExp expression(rule.pattern);
  209. int index = expression.indexIn(text);
  210. while (index >= 0) {
  211. int length = expression.matchedLength();
  212. setFormat(index + 1, 1, edgeTagFormat);
  213. setFormat(index + 2, length - 2, rule.format);
  214. index = expression.indexIn(text, index + length);
  215. }
  216. }
  217.  
  218. // COMMENT
  219. int startCommentIndex = 0;
  220. // Если предыдущее состояние тега не является комментарием
  221. if (previousBlockState() != HtmlHighLighter::Comment)
  222. {
  223. // то пытаемся найти начало комментария
  224. startCommentIndex = commentStartExpression.indexIn(text);
  225. }
  226.  
  227. // Если комментарий найден
  228. while (startCommentIndex >= 0)
  229. {
  230. // Ищем конец комментария
  231. int endCommentIndex = commentEndExpression.indexIn(text, startCommentIndex);
  232. int commentLength;
  233.  
  234. // Если конец не найден
  235. if (endCommentIndex == -1)
  236. {
  237. // То устанавливаем состояние Comment
  238. // Принцип аналогичен, что и для обычных тегов
  239. setCurrentBlockState(HtmlHighLighter::Comment);
  240. commentLength = text.length() - startCommentIndex;
  241. }
  242. else
  243. {
  244. commentLength = endCommentIndex - startCommentIndex
  245. + commentEndExpression.matchedLength();
  246. }
  247.  
  248. setFormat(startCommentIndex, commentLength, multiLineCommentFormat);
  249. startCommentIndex = commentStartExpression.indexIn(text, startCommentIndex + commentLength);
  250. }
  251. }

Скачать проект

Видео

Вам это нравится? Поделитесь в социальных сетях!

nayk1982
  • 29 августа 2018 г. 12:57

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

nayk1982
  • 29 августа 2018 г. 13:01

Половина слов из предыдущего комментария куда-то исчезло. Суть: вместо QRegExp используется QRegularExport. Как в этом случае будет выглядеть highlightBlock?

Evgenii Legotckoi
  • 1 сентября 2018 г. 13:59

Вы хотели сказать QRegularExpression?

Да вроде также должна выглядеть регулярная.
Там ничего особенного не было в регулярном выражении. Всё должно быть совместимо с регулярными перла, которые являются базой для QRegularExpression.
А по поводу ошибки с паркингом комментария, я постараюсь разобраться, когда вернусь из поездки. Спасибо за информацию.



Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь