diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index fa54776b6d..438fa75492 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -48,6 +48,7 @@ #include "qtextlist.h" #include #include +#include #include #include #include @@ -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) diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h index 854cb29ed9..3f4b8b565b 100644 --- a/src/gui/text/qtextdocument.h +++ b/src/gui/text/qtextdocument.h @@ -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; diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp index 59c951332e..1c74eaa19a 100644 --- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp +++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp @@ -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("haystack"); - QTest::addColumn("needle"); - QTest::addColumn("flags"); - QTest::addColumn("from"); - QTest::addColumn("anchor"); - QTest::addColumn("position"); - - // match integers 0 to 99 - QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2; - // match ampersands but not & - QTest::newRow("2") << "His & 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("haystack"); + QTest::addColumn("needle"); + QTest::addColumn("flags"); + QTest::addColumn("from"); + QTest::addColumn("anchor"); + QTest::addColumn("position"); + + // match integers 0 to 99 + QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2; + // match ampersands but not & + QTest::newRow("2") << "His & 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();