Skip to content

Commit dce8673

Browse files
committed
Move guts of wordwrap expression function to QgsStringUtils::wordWrap
To allow use outside of expressions
1 parent 706b13a commit dce8673

File tree

5 files changed

+131
-66
lines changed

5 files changed

+131
-66
lines changed

python/core/auto_generated/qgsstringutils.sip.in

+14
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ links.
260260
:return: string with inserted links
261261

262262
.. versionadded:: 3.0
263+
%End
264+
265+
static QString wordWrap( const QString &string, int length, bool useMaxLineLength = true, const QString &customDelimiter = QString() );
266+
%Docstring
267+
Automatically wraps a \string by inserting new line characters at appropriate locations in the string.
268+
269+
The ``length`` argument specifies either the minimum or maximum length of lines desired, depending
270+
on whether ``useMaxLineLength`` is true. If ``useMaxLineLength`` is true, then the string will be wrapped
271+
so that each line ideally will not exceed ``length`` of characters. If ``useMaxLineLength`` is false, then
272+
the string will be wrapped so that each line will ideally exceed ``length`` of characters.
273+
274+
A custom delimiter can be specified to use instead of space characters.
275+
276+
.. versionadded:: 3.4
263277
%End
264278
};
265279

src/core/expression/qgsexpressionfunction.cpp

+2-66
Original file line numberDiff line numberDiff line change
@@ -1014,73 +1014,9 @@ static QVariant fcnWordwrap( const QVariantList &values, const QgsExpressionCont
10141014
QString str = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
10151015
qlonglong wrap = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
10161016

1017-
if ( !str.isEmpty() && wrap != 0 )
1018-
{
1019-
QString newstr;
1020-
QRegExp rx;
1021-
QString customdelimiter = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
1022-
int delimiterlength;
1023-
1024-
if ( customdelimiter.length() > 0 )
1025-
{
1026-
rx.setPatternSyntax( QRegExp::FixedString );
1027-
rx.setPattern( customdelimiter );
1028-
delimiterlength = customdelimiter.length();
1029-
}
1030-
else
1031-
{
1032-
// \x200B is a ZERO-WIDTH SPACE, needed for worwrap to support a number of complex scripts (Indic, Arabic, etc.)
1033-
rx.setPattern( QStringLiteral( "[\\s\\x200B]" ) );
1034-
delimiterlength = 1;
1035-
}
1036-
1017+
QString customdelimiter = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
10371018

1038-
QStringList lines = str.split( '\n' );
1039-
int strlength, strcurrent, strhit, lasthit;
1040-
1041-
for ( int i = 0; i < lines.size(); i++ )
1042-
{
1043-
strlength = lines[i].length();
1044-
strcurrent = 0;
1045-
strhit = 0;
1046-
lasthit = 0;
1047-
1048-
while ( strcurrent < strlength )
1049-
{
1050-
// positive wrap value = desired maximum line width to wrap
1051-
// negative wrap value = desired minimum line width before wrap
1052-
if ( wrap > 0 )
1053-
{
1054-
//first try to locate delimiter backwards
1055-
strhit = lines[i].lastIndexOf( rx, strcurrent + wrap );
1056-
if ( strhit == lasthit || strhit == -1 )
1057-
{
1058-
//if no new backward delimiter found, try to locate forward
1059-
strhit = lines[i].indexOf( rx, strcurrent + std::labs( wrap ) );
1060-
}
1061-
lasthit = strhit;
1062-
}
1063-
else
1064-
{
1065-
strhit = lines[i].indexOf( rx, strcurrent + std::labs( wrap ) );
1066-
}
1067-
if ( strhit > -1 )
1068-
{
1069-
newstr.append( lines[i].midRef( strcurrent, strhit - strcurrent ) );
1070-
newstr.append( '\n' );
1071-
strcurrent = strhit + delimiterlength;
1072-
}
1073-
else
1074-
{
1075-
newstr.append( lines[i].midRef( strcurrent ) );
1076-
strcurrent = strlength;
1077-
}
1078-
}
1079-
if ( i < lines.size() - 1 ) newstr.append( '\n' );
1080-
}
1081-
1082-
return QVariant( newstr );
1083-
}
1019+
return QgsStringUtils::wordWrap( str, static_cast< int >( wrap ), wrap > 0, customdelimiter );
10841020
}
10851021

10861022
return QVariant();

src/core/qgsstringutils.cpp

+70
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,76 @@ QString QgsStringUtils::insertLinks( const QString &string, bool *foundLinks )
442442
return converted;
443443
}
444444

445+
QString QgsStringUtils::wordWrap( const QString &string, const int length, const bool useMaxLineLength, const QString &customDelimiter )
446+
{
447+
if ( string.isEmpty() || length == 0 )
448+
return string;
449+
450+
QString newstr;
451+
QRegExp rx;
452+
int delimiterLength = 0;
453+
454+
if ( !customDelimiter.isEmpty() )
455+
{
456+
rx.setPatternSyntax( QRegExp::FixedString );
457+
rx.setPattern( customDelimiter );
458+
delimiterLength = customDelimiter.length();
459+
}
460+
else
461+
{
462+
// \x200B is a ZERO-WIDTH SPACE, needed for worwrap to support a number of complex scripts (Indic, Arabic, etc.)
463+
rx.setPattern( QStringLiteral( "[\\s\\x200B]" ) );
464+
delimiterLength = 1;
465+
}
466+
467+
const QStringList lines = string.split( '\n' );
468+
int strLength, strCurrent, strHit, lastHit;
469+
470+
for ( int i = 0; i < lines.size(); i++ )
471+
{
472+
strLength = lines.at( i ).length();
473+
strCurrent = 0;
474+
strHit = 0;
475+
lastHit = 0;
476+
477+
while ( strCurrent < strLength )
478+
{
479+
// positive wrap value = desired maximum line width to wrap
480+
// negative wrap value = desired minimum line width before wrap
481+
if ( useMaxLineLength )
482+
{
483+
//first try to locate delimiter backwards
484+
strHit = lines.at( i ).lastIndexOf( rx, strCurrent + length );
485+
if ( strHit == lastHit || strHit == -1 )
486+
{
487+
//if no new backward delimiter found, try to locate forward
488+
strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
489+
}
490+
lastHit = strHit;
491+
}
492+
else
493+
{
494+
strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
495+
}
496+
if ( strHit > -1 )
497+
{
498+
newstr.append( lines.at( i ).midRef( strCurrent, strHit - strCurrent ) );
499+
newstr.append( '\n' );
500+
strCurrent = strHit + delimiterLength;
501+
}
502+
else
503+
{
504+
newstr.append( lines.at( i ).midRef( strCurrent ) );
505+
strCurrent = strLength;
506+
}
507+
}
508+
if ( i < lines.size() - 1 )
509+
newstr.append( '\n' );
510+
}
511+
512+
return newstr;
513+
}
514+
445515
QgsStringReplacement::QgsStringReplacement( const QString &match, const QString &replacement, bool caseSensitive, bool wholeWordOnly )
446516
: mMatch( match )
447517
, mReplacement( replacement )

src/core/qgsstringutils.h

+14
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ class CORE_EXPORT QgsStringUtils
260260
* \since QGIS 3.0
261261
*/
262262
static QString insertLinks( const QString &string, bool *foundLinks = nullptr );
263+
264+
/**
265+
* Automatically wraps a \string by inserting new line characters at appropriate locations in the string.
266+
*
267+
* The \a length argument specifies either the minimum or maximum length of lines desired, depending
268+
* on whether \a useMaxLineLength is true. If \a useMaxLineLength is true, then the string will be wrapped
269+
* so that each line ideally will not exceed \a length of characters. If \a useMaxLineLength is false, then
270+
* the string will be wrapped so that each line will ideally exceed \a length of characters.
271+
*
272+
* A custom delimiter can be specified to use instead of space characters.
273+
*
274+
* \since QGIS 3.4
275+
*/
276+
static QString wordWrap( const QString &string, int length, bool useMaxLineLength = true, const QString &customDelimiter = QString() );
263277
};
264278

265279
#endif //QGSSTRINGUTILS_H

tests/src/core/testqgsstringutils.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class TestQgsStringUtils : public QObject
3838
void titleCase();
3939
void ampersandEncode_data();
4040
void ampersandEncode();
41+
void wordWrap_data();
42+
void wordWrap();
4143

4244
};
4345

@@ -206,6 +208,35 @@ void TestQgsStringUtils::ampersandEncode()
206208

207209
}
208210

211+
void TestQgsStringUtils::wordWrap_data()
212+
{
213+
QTest::addColumn<QString>( "input" );
214+
QTest::addColumn<int>( "length" );
215+
QTest::addColumn<bool>( "isMax" );
216+
QTest::addColumn<QString>( "delimiter" );
217+
QTest::addColumn<QString>( "expected" );
218+
219+
QTest::newRow( "wordwrap" ) << "university of qgis" << 13 << true << QString() << "university of\nqgis";
220+
QTest::newRow( "optional parameters unspecified" ) << "test string" << 5 << true << QString() << "test\nstring";
221+
QTest::newRow( "wordwrap with delim" ) << "university of qgis" << 13 << true << QStringLiteral( " " ) << "university of\nqgis";
222+
QTest::newRow( "wordwrap min" ) << "university of qgis" << 3 << false << QStringLiteral( " " ) << "university\nof qgis";
223+
QTest::newRow( "wordwrap min with delim" ) << "university of qgis" << 3 << false << QStringLiteral( " " ) << "university\nof qgis";
224+
QTest::newRow( "wordwrap on multi line" ) << "university of qgis\nsupports many multiline" << 5 << false << QStringLiteral( " " ) << "university\nof qgis\nsupports\nmany multiline";
225+
QTest::newRow( "wordwrap on zero-space width" ) << QStringLiteral( "test%1zero-width space" ).arg( QChar( 8203 ) ) << 4 << false << QString() << "test\nzero-width\nspace";
226+
QTest::newRow( "optional parameters specified" ) << "testxstring" << 5 << true << "x" << "test\nstring";
227+
}
228+
229+
void TestQgsStringUtils::wordWrap()
230+
{
231+
QFETCH( QString, input );
232+
QFETCH( int, length );
233+
QFETCH( bool, isMax );
234+
QFETCH( QString, delimiter );
235+
QFETCH( QString, expected );
236+
237+
QCOMPARE( QgsStringUtils::wordWrap( input, length, isMax, delimiter ), expected );
238+
}
239+
209240

210241
QGSTEST_MAIN( TestQgsStringUtils )
211242
#include "testqgsstringutils.moc"

0 commit comments

Comments
 (0)