Add QRegularExpression support to QTextDocument

Currently QTextDocuement only provides find using QRegExp. This patch
aims to add support for QRegularExpression.

[ChangeLog][QtGui][QTextDocument] Support for searching with a
QRegularExpression in a document has been added.

Change-Id: I6dba10545b83995d093407038a821fe54db3d261
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
This commit is contained in:
Samuel Gaist 2014-08-27 23:14:59 +02:00
parent 4e8720d413
commit 4066a6a893
3 changed files with 181 additions and 16 deletions

View File

@ -48,6 +48,7 @@
#include "qtextlist.h"
#include <qdebug.h>
#include <qregexp.h>
#include <qregularexpression.h>
#include <qvarlengtharray.h>
#include <qtextcodec.h>
#include <qthread.h>
@ -1432,6 +1433,131 @@ QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, Fi
return find(expr, pos, options);
}
#ifndef QT_NO_REGULAREXPRESSION
static bool findInBlock(const QTextBlock &block, const QRegularExpression &expression, int offset,
QTextDocument::FindFlags options, QTextCursor *cursor)
{
QRegularExpression expr(expression);
if (!(options & QTextDocument::FindCaseSensitively))
expr.setPatternOptions(expr.patternOptions() | QRegularExpression::CaseInsensitiveOption);
else
expr.setPatternOptions(expr.patternOptions() & ~QRegularExpression::CaseInsensitiveOption);
QString text = block.text();
text.replace(QChar::Nbsp, QLatin1Char(' '));
QRegularExpressionMatch match;
int idx = -1;
while (offset >= 0 && offset <= text.length()) {
idx = (options & QTextDocument::FindBackward) ?
text.lastIndexOf(expr, offset, &match) : text.indexOf(expr, offset, &match);
if (idx == -1)
return false;
if (options & QTextDocument::FindWholeWords) {
const int start = idx;
const int end = start + match.capturedLength();
if ((start != 0 && text.at(start - 1).isLetterOrNumber())
|| (end != text.length() && text.at(end).isLetterOrNumber())) {
//if this is not a whole word, continue the search in the string
offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
idx = -1;
continue;
}
}
//we have a hit, return the cursor for that.
break;
}
if (idx == -1)
return false;
*cursor = qMove(QTextCursor(block.docHandle(), block.position() + idx));
cursor->setPosition(cursor->position() + match.capturedLength(), QTextCursor::KeepAnchor);
return true;
}
/*!
\since 5.5
Finds the next occurrence, matching the regular expression, \a expr, in the document.
The search starts at the given \a from position, and proceeds forwards
through the document unless specified otherwise in the search options.
The \a options control the type of search performed.
Returns a cursor with the match selected if a match was found; otherwise
returns a null cursor.
If the \a from position is 0 (the default) the search begins from the beginning
of the document; otherwise it begins at the specified position.
*/
QTextCursor QTextDocument::find(const QRegularExpression &expr, int from, FindFlags options) const
{
Q_D(const QTextDocument);
if (!expr.isValid())
return QTextCursor();
int pos = from;
//the cursor is positioned between characters, so for a backward search
//do not include the character given in the position.
if (options & FindBackward) {
--pos ;
if (pos < 0)
return QTextCursor();
}
QTextCursor cursor;
QTextBlock block = d->blocksFind(pos);
if (!(options & FindBackward)) {
int blockOffset = qMax(0, pos - block.position());
while (block.isValid()) {
if (findInBlock(block, expr, blockOffset, options, &cursor))
return cursor;
blockOffset = 0;
block = block.next();
}
} else {
int blockOffset = pos - block.position();
while (block.isValid()) {
if (findInBlock(block, expr, blockOffset, options, &cursor))
return cursor;
block = block.previous();
blockOffset = block.length() - 1;
}
}
return QTextCursor();
}
/*!
\since 5.5
Finds the next occurrence, matching the regular expression, \a expr, in the document.
The search starts at the position of the given \a from cursor, and proceeds
forwards through the document unless specified otherwise in the search
options. The \a options control the type of search performed.
Returns a cursor with the match selected if a match was found; otherwise
returns a null cursor.
If the given \a from cursor has a selection, the search begins after the
selection; otherwise it begins at the cursor's position.
By default the search is case-sensitive, and can match text anywhere in the
document.
*/
QTextCursor QTextDocument::find(const QRegularExpression &expr, const QTextCursor &from, FindFlags options) const
{
int pos = 0;
if (!from.isNull()) {
if (options & QTextDocument::FindBackward)
pos = from.selectionStart();
else
pos = from.selectionEnd();
}
return find(expr, pos, options);
}
#endif // QT_NO_REGULAREXPRESSION
/*!
\fn QTextObject *QTextDocument::createObject(const QTextFormat &format)

View File

@ -169,10 +169,14 @@ public:
QTextCursor find(const QString &subString, int from = 0, FindFlags options = 0) const;
QTextCursor find(const QString &subString, const QTextCursor &from, FindFlags options = 0) const;
QTextCursor find(const QRegExp &expr, int from = 0, FindFlags options = 0) const;
QTextCursor find(const QRegExp &expr, const QTextCursor &from, FindFlags options = 0) const;
#ifndef QT_NO_REGULAREXPRESSION
QTextCursor find(const QRegularExpression &expr, int from = 0, FindFlags options = 0) const;
QTextCursor find(const QRegularExpression &expr, const QTextCursor &from, FindFlags options = 0) const;
#endif
QTextFrame *frameAt(int pos) const;
QTextFrame *rootFrame() const;

View File

@ -86,6 +86,8 @@ private slots:
void find2();
void findWithRegExp_data();
void findWithRegExp();
void findWithRegularExpression_data();
void findWithRegularExpression();
void findMultiple();
void basicIsModifiedChecks();
void moreIsModified();
@ -196,6 +198,7 @@ private slots:
private:
void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
void buildRegExpData();
QTextDocument *doc;
QTextCursor cursor;
@ -352,21 +355,7 @@ void tst_QTextDocument::find()
void tst_QTextDocument::findWithRegExp_data()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
// match integers 0 to 99
QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2;
// match ampersands but not &amp;
QTest::newRow("2") << "His &amp; hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16;
//backward search
QTest::newRow("3") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9;
buildRegExpData();
}
void tst_QTextDocument::findWithRegExp()
@ -393,6 +382,34 @@ void tst_QTextDocument::findWithRegExp()
}
}
void tst_QTextDocument::findWithRegularExpression_data()
{
buildRegExpData();
}
void tst_QTextDocument::findWithRegularExpression()
{
QFETCH(QString, haystack);
QFETCH(QString, needle);
QFETCH(int, flags);
QFETCH(int, from);
QFETCH(int, anchor);
QFETCH(int, position);
cursor.insertText(haystack);
//search using a regular expression
QRegularExpression expr(needle);
QTextDocument::FindFlags flg(flags);
cursor = doc->find(expr, from, flg);
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
}
void tst_QTextDocument::find2()
{
doc->setPlainText("aaa");
@ -2602,6 +2619,24 @@ void tst_QTextDocument::backgroundImage_checkExpectedHtml(const QTextDocument &d
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::buildRegExpData()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
// match integers 0 to 99
QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2;
// match ampersands but not &amp;
QTest::newRow("2") << "His &amp; hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16;
//backward search
QTest::newRow("3") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9;
}
void tst_QTextDocument::backgroundImage_toHtml()
{
CREATE_DOC_AND_CURSOR();