Проверка орфографии в приложениях Qt

В данной статье описано как добавить к вашему приложению на Qt проверку орфографии с подсветкой орфографически некорректных слов и возможность замены на более подходящие варианты. Для этого используется словарь hunspell.

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

Первым делом, поскольку мы работаем с библиотекой hunspell, необходимо её подключить. Для этого в .pro файл добавляем строку: LIBS += -lhunspell.

Далее создадим свои собственные классы унаследованные от QSyntaxHighlighter и QTextEdit.

Заголовочный файл classes.h:

#ifndef CLASSES_H
#define CLASSES_H
#include <QSyntaxHighlighter>
#include <QTextEdit>
 
class SpellingHighlighter : public QSyntaxHighlighter // Класс для подсвечивания текста
{
    Q_OBJECT
    
public:
    SpellingHighlighter(QTextEdit *parent) :QSyntaxHighlighter(parent) {}
    
protected:
    virtual void highlightBlock(const QString &text) override;
};

class BodyTextEdit : public QTextEdit // Класс для ввода и отображения текста
{
    Q_OBJECT
    
public:
    BodyTextEdit(QWidget* parent = 0);
    
    
public slots:
    void openCustomMenu(QPoint);
    void correctWord(QAction * act);
    
protected:
    SpellingHighlighter * m_highLighter;
};
#endif // CLASSES_H

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

// correct - флаг корректно ли слово, getSuggests - получать ли варианты корректных слов
QStringList suggestCorrections(const QString &word, bool &correct, bool getSuggests = false)
{
    static Hunspell * m_hunSpell = new Hunspell("/usr/share/hunspell/ru_RU.aff", "/usr/share/hunspell/ru_RU.dic");//данные пути корректны для debian
    static QString  encoderStr = QString::fromLatin1( m_hunSpell->get_dic_encoding());// Могут быть различные кодеки, например KOI8-R 
    static QTextCodec * m_codec = QTextCodec::codecForName(encoderStr.toLatin1().constData());

    correct = m_hunSpell->spell(m_codec->fromUnicode(word).constData()) != 0; // проверяем есть ли данное слово в словаре
    if (getSuggests == false)
        return QStringList();
    QStringList suggestions;
    char **suggestWordList = NULL;

    try {
        // Encode from Unicode to the encoding used by current dictionary
        int count = m_hunSpell->suggest(&suggestWordList, m_codec->fromUnicode(word).constData());// получаем список возможных вариантов
        QString lowerWord = word.toLower();

        for (int i = 0; i < count; ++i) {
            QString suggestion = m_codec->toUnicode(suggestWordList[i]);
            suggestions << suggestion;
            free(suggestWordList[i]);
        }
    }
    catch(...)
    {
        qDebug() <<"Error keyword";
    }
    return suggestions;
}

Остается только определить классы и грамотно использовать данную функцию:

#include <QSyntaxHighlighter>
#include <QCompleter>
#include <QTextCodec>
#include <QAction>
#include <QTextEdit>
#include <QMenu>
#include "hunspell/hunspell.hxx" // from package libhunspell-dev
#include <QDebug>
#include "classes.h"

//Описанная чуть выше функция 
QStringList suggestCorrections(const QString &word, bool &correct, bool getSuggests = false)
{
    static Hunspell * m_hunSpell = new Hunspell("/usr/share/hunspell/ru_RU.aff", "/usr/share/hunspell/ru_RU.dic");
    static QString  encoderStr = QString::fromLatin1( m_hunSpell->get_dic_encoding());
    static QTextCodec * m_codec = QTextCodec::codecForName(encoderStr.toLatin1().constData());

    correct = m_hunSpell->spell(m_codec->fromUnicode(word).constData()) != 0;
    if (getSuggests == false)
        return QStringList();
    QStringList suggestions;
    char **suggestWordList = NULL;

    try {
        // Encode from Unicode to the encoding used by current dictionary
        int count = m_hunSpell->suggest(&suggestWordList, m_codec->fromUnicode(word).constData());
        QString lowerWord = word.toLower();

        for (int i = 0; i < count; ++i) {
            QString suggestion = m_codec->toUnicode(suggestWordList[i]);
            suggestions << suggestion;
            free(suggestWordList[i]);
        }
    }
    catch(...)
    {
        qDebug() <<"Error keyword";
    }
    return suggestions;
}

// Переопределяем функцию подсветки текста
void SpellingHighlighter::highlightBlock(const QString &text)
{
    QStringList list = text.split(QRegExp("\\s+"), QString::KeepEmptyParts);//Регулярное выражения по поиску слов
    QTextCharFormat spellingFormat;//Определяем как именно мы будем подсвечивать слова
    spellingFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
    spellingFormat.setUnderlineColor(Qt::red);
    int count_word = list.size();
    int pointer = 0;
    for (int i= 0; i< count_word; i++)
    {
        bool correct = false;
        QString sect = text.section(QRegExp("\\s+"), i, i, QString::SectionIncludeLeadingSep);
        sect.remove(QRegExp("[,!?&*|]"));// Удаляем лишние символы
        suggestCorrections(sect, correct);//Проверяем корректно ли слово
        if (!correct)
        {
            setFormat(pointer, sect.length(),spellingFormat);
        }
        pointer += sect.length();
    }
};


BodyTextEdit::BodyTextEdit(QWidget* parent )
:
QTextEdit(parent)
{
    this->setContextMenuPolicy(Qt::CustomContextMenu);
    m_highLighter = new SpellingHighlighter(this);
    connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(openCustomMenu(QPoint)));
}

//Создание контекстного меню с предложенными вариантами замены
void BodyTextEdit::openCustomMenu(QPoint pos)
{
    QMenu* popupmenu = this->createStandardContextMenu();
    QTextCursor cursor = this->textCursor();
    cursor.select(QTextCursor::WordUnderCursor);
    this->setTextCursor(cursor);
    if (this->textCursor().hasSelection())
    {
        QString text = this->textCursor().selectedText();
        bool correct = false;
        QStringList suggest = suggestCorrections(text, correct, true);
        auto firstAction = popupmenu->actions().first();
        if (!correct)
        {
            QList<QAction*> addedActions;
            for (auto word  : suggest)
            {
                QAction * act = new QAction(word, popupmenu);
                act->setData(word);
                addedActions.append(act);
            }
            popupmenu->insertActions(firstAction, addedActions);
            connect(popupmenu, SIGNAL(triggered(QAction*)), this, SLOT(correctWord(QAction*)));
        }
    }
    popupmenu->exec(this->mapToGlobal(pos));
    delete popupmenu;
}

//Замена слова на выбранное в меню
void BodyTextEdit::correctWord(QAction *act)
{
    if (act->data().isNull())
        return;
    QString word = act->data().toString();
    QTextCursor cursor = this->textCursor();
    cursor.beginEditBlock();

    cursor.removeSelectedText();
    cursor.insertText(word);
    cursor.endEditBlock();
}

Заключение:

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

Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 1

    0
    Достаточно забавно что я делал такую-же штуку вот буквально на прошлой неделе, только на python. Т.к. там нет прямого доступа к hunspell, то работает оно через enchant и с самопальным простейшим токенизером, если проверять английский то можно использовать встроенный токенизер enchant. Если вдруг кому надо, делюсь:
    # -*- coding: utf-8 -*-
    
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    
    import enchant
    import re
    
    def findWord(text, pos):
        words = re.split('(\W+)', text)
        idx = 0
        for w in words:
            if pos >= idx and pos < (idx + len(w)):
                return w, idx, len(w)
            idx += len(w)
        return None, -1, 0
    
    class Highlighter(QSyntaxHighlighter):
        def __init__(self, arg):
            QSyntaxHighlighter.__init__(self, arg.document())
            self.textEdit = arg
            arg.customContextMenuRequested.connect(self.contextMenu)
            self.dic = enchant.Dict("ru_RU")
    
        def highlightBlock(self, text):
            if len(text) == 0:
                return
            words = re.split('(\W+)', text)
            idx = 0
            fmt = QTextCharFormat()
            fmt.setUnderlineColor(QColor(255, 0, 0))
            fmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
            for w in words:
                if re.match('\w', w) and not(self.dic.check(w)):
                    self.setFormat(idx, len(w), fmt)
                idx += len(w)
    
        def contextMenu(self, pt):
            cursor = self.textEdit.textCursor()
            word = None
            if not(cursor.hasSelection()):
                text = self.textEdit.toPlainText()
                word, p, i = findWord(text, cursor.position())
            menu = self.textEdit.createStandardContextMenu()
            if word:
                fa = menu.actions()[0]
                sl = self.dic.suggest(word)
                if len(sl) == 0:
                    none = QAction("(none)", self)
                    none.setEnabled(False)
                    menu.insertAction(fa, none)
                for s in sl:
                    a = QAction(s, self)
                    a.triggered.connect(lambda b, r=s: self.replaceWord(r))
                    menu.insertAction(fa, a)
                menu.insertSeparator(fa)
            gp = self.textEdit.mapToGlobal(pt)
            menu.exec_(gp)
    
        def replaceWord(self, rep):
            cursor = self.textEdit.textCursor()
            text = self.textEdit.toPlainText()
            word, p, i = findWord(text, cursor.position())
            cursor.beginEditBlock()
            cursor.setPosition(p)
            cursor.setPosition(p + i, QTextCursor.KeepAnchor)
            cursor.removeSelectedText()
            cursor.insertText(rep)
            cursor.endEditBlock()
    


    Использование очень простое, просто нужно создать и хранить где-то объект параметром передав ему QTextEdit.

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

    Самое читаемое