20 #include <QStringList>
21 #include <QTextBoundaryFinder>
22 #include <QRegularExpression>
27 if (
string.isEmpty() )
30 switch ( capitalization )
36 return string.toUpper();
39 return string.toLower();
43 QString temp = string;
45 QTextBoundaryFinder wordSplitter( QTextBoundaryFinder::Word,
string.constData(),
string.length(),
nullptr, 0 );
46 QTextBoundaryFinder letterSplitter( QTextBoundaryFinder::Grapheme,
string.constData(),
string.length(),
nullptr, 0 );
48 wordSplitter.setPosition( 0 );
50 while ( ( first && wordSplitter.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
51 || wordSplitter.toNextBoundary() >= 0 )
54 letterSplitter.setPosition( wordSplitter.position() );
55 letterSplitter.toNextBoundary();
56 QString substr =
string.mid( wordSplitter.position(), letterSplitter.position() - wordSplitter.position() );
57 temp.replace( wordSplitter.position(), substr.length(), substr.toUpper() );
66 static QStringList smallWords;
67 static QStringList newPhraseSeparators;
68 static QRegularExpression splitWords;
69 if ( smallWords.empty() )
71 smallWords = QObject::tr(
"a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|s|the|to|vs.|vs|via" ).split(
'|' );
72 newPhraseSeparators = QObject::tr(
".|:" ).split(
'|' );
73 splitWords = QRegularExpression( QStringLiteral(
"\\b" ), QRegularExpression::UseUnicodePropertiesOption );
76 const QStringList parts =
string.split( splitWords, QString::SkipEmptyParts );
78 bool firstWord =
true;
80 int lastWord = parts.count() - 1;
81 for (
const QString &word : qgis::as_const( parts ) )
83 if ( newPhraseSeparators.contains( word.trimmed() ) )
88 else if ( firstWord || ( i == lastWord ) || !smallWords.contains( word ) )
90 result += word.at( 0 ).toUpper() + word.mid( 1 );
104 result.remove(
' ' );
115 for (
int i = 0; i <
string.size(); ++i )
117 QChar ch =
string.at( i );
118 if ( ch.unicode() > 160 )
119 encoded += QStringLiteral(
"&#%1;" ).arg(
static_cast< int >( ch.unicode() ) );
120 else if ( ch.unicode() == 38 )
121 encoded += QStringLiteral(
"&" );
122 else if ( ch.unicode() == 60 )
123 encoded += QStringLiteral(
"<" );
124 else if ( ch.unicode() == 62 )
125 encoded += QStringLiteral(
">" );
134 int length1 = string1.length();
135 int length2 = string2.length();
138 if ( string1.isEmpty() )
142 else if ( string2.isEmpty() )
148 QString s1( caseSensitive ? string1 : string1.toLower() );
149 QString s2( caseSensitive ? string2 : string2.toLower() );
151 const QChar *s1Char = s1.constData();
152 const QChar *s2Char = s2.constData();
155 int commonPrefixLen = 0;
156 while ( length1 > 0 && length2 > 0 && *s1Char == *s2Char )
166 while ( length1 > 0 && length2 > 0 && s1.at( commonPrefixLen + length1 - 1 ) == s2.at( commonPrefixLen + length2 - 1 ) )
177 else if ( length2 == 0 )
183 if ( length1 > length2 )
186 std::swap( length1, length2 );
191 col.fill( 0, length2 + 1 );
192 QVector< int > prevCol;
193 prevCol.reserve( length2 + 1 );
194 for (
int i = 0; i < length2 + 1; ++i )
198 const QChar *s2start = s2Char;
199 for (
int i = 0; i < length1; ++i )
203 for (
int j = 0; j < length2; ++j )
205 col[j + 1] = std::min( std::min( 1 + col[j], 1 + prevCol[1 + j] ), prevCol[j] + ( ( *s1Char == *s2Char ) ? 0 : 1 ) );
211 return prevCol[length2];
216 if ( string1.isEmpty() || string2.isEmpty() )
223 QString s1( caseSensitive ? string1 : string1.toLower() );
224 QString s2( caseSensitive ? string2 : string2.toLower() );
232 int *currentScores =
new int [ s2.length()];
233 int *previousScores =
new int [ s2.length()];
234 int maxCommonLength = 0;
235 int lastMaxBeginIndex = 0;
237 const QChar *s1Char = s1.constData();
238 const QChar *s2Char = s2.constData();
239 const QChar *s2Start = s2Char;
241 for (
int i = 0; i < s1.length(); ++i )
243 for (
int j = 0; j < s2.length(); ++j )
245 if ( *s1Char != *s2Char )
247 currentScores[j] = 0;
251 if ( i == 0 || j == 0 )
253 currentScores[j] = 1;
257 currentScores[j] = 1 + previousScores[j - 1];
260 if ( maxCommonLength < currentScores[j] )
262 maxCommonLength = currentScores[j];
263 lastMaxBeginIndex = i;
268 std::swap( currentScores, previousScores );
272 delete [] currentScores;
273 delete [] previousScores;
274 return string1.mid( lastMaxBeginIndex - maxCommonLength + 1, maxCommonLength );
279 if ( string1.isEmpty() && string2.isEmpty() )
285 if ( string1.length() != string2.length() )
292 QString s1( caseSensitive ? string1 : string1.toLower() );
293 QString s2( caseSensitive ? string2 : string2.toLower() );
302 const QChar *s1Char = s1.constData();
303 const QChar *s2Char = s2.constData();
305 for (
int i = 0; i < string1.length(); ++i )
307 if ( *s1Char != *s2Char )
318 if (
string.isEmpty() )
321 QString tmp =
string.toUpper();
324 QChar *char1 = tmp.data();
325 QChar *char2 = tmp.data();
327 for (
int i = 0; i < tmp.length(); ++i, ++char2 )
329 if ( ( *char2 ).unicode() >= 0x41 && ( *char2 ).unicode() <= 0x5A && ( i == 0 || ( ( *char2 ).unicode() != 0x41 && ( *char2 ).unicode() != 0x45
330 && ( *char2 ).unicode() != 0x48 && ( *char2 ).unicode() != 0x49
331 && ( *char2 ).unicode() != 0x4F && ( *char2 ).unicode() != 0x55
332 && ( *char2 ).unicode() != 0x57 && ( *char2 ).unicode() != 0x59 ) ) )
339 tmp.truncate( outLen );
341 QChar *tmpChar = tmp.data();
343 for (
int i = 1; i < tmp.length(); ++i, ++tmpChar )
345 switch ( ( *tmpChar ).unicode() )
351 tmp.replace( i, 1, QChar( 0x31 ) );
362 tmp.replace( i, 1, QChar( 0x32 ) );
367 tmp.replace( i, 1, QChar( 0x33 ) );
371 tmp.replace( i, 1, QChar( 0x34 ) );
376 tmp.replace( i, 1, QChar( 0x35 ) );
380 tmp.replace( i, 1, QChar( 0x36 ) );
390 for (
int i = 1; i < tmp.length(); ++i, ++char2 )
392 if ( *char2 != *char1 )
401 tmp.truncate( outLen );
402 if ( tmp.length() < 4 )
414 QString candidateNormalized = candidate.simplified().normalized( QString:: NormalizationForm_C ).toLower();
415 QString searchNormalized = search.simplified().normalized( QString:: NormalizationForm_C ).toLower();
417 int candidateLength = candidateNormalized.length();
418 int searchLength = searchNormalized.length();
422 if ( candidateLength == 0 || searchLength == 0 )
425 int candidateIdx = 0;
430 bool isPreviousIndexMatching =
false;
431 bool isWordOpen =
true;
434 while ( candidateIdx < candidateLength )
436 QChar candidateChar = candidateNormalized[ candidateIdx++ ];
437 bool isCandidateCharWordEnd = candidateChar ==
' ' || candidateChar.isPunct();
440 if ( candidateIdx == 1 )
443 else if ( isCandidateCharWordEnd )
450 if ( searchIdx >= searchLength )
453 QChar searchChar = searchNormalized[ searchIdx ];
454 bool isSearchCharWordEnd = searchChar ==
' ' || searchChar.isPunct();
457 if ( candidateChar == searchChar || ( isCandidateCharWordEnd && isSearchCharWordEnd ) )
462 if ( isSearchCharWordEnd )
466 else if ( isPreviousIndexMatching )
474 else if ( isPreviousIndexMatching )
484 isPreviousIndexMatching =
true;
489 isPreviousIndexMatching =
false;
494 if ( searchIdx >= searchLength )
496 bool isEndOfWord = ( candidateIdx >= candidateLength )
498 : candidateNormalized[candidateIdx] ==
' ' || candidateNormalized[candidateIdx].isPunct();
509 if ( searchIdx < searchLength )
512 return static_cast<float>( std::max( score, 0 ) ) / std::max( maxScore, 1 );
518 QString converted = string;
522 static QRegExp urlRegEx(
"(\\b(([\\w-]+://?|www[.])[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~\\s]|/))))" );
523 static QRegExp protoRegEx(
"^(?:f|ht)tps?://|file://" );
524 static QRegExp emailRegEx(
"([\\w._%+-]+@[\\w.-]+\\.[A-Za-z]+)" );
528 while ( urlRegEx.indexIn( converted, offset ) != -1 )
531 QString url = urlRegEx.cap( 1 );
532 QString protoUrl = url;
533 if ( protoRegEx.indexIn( protoUrl ) == -1 )
535 protoUrl.prepend(
"http://" );
537 QString anchor = QStringLiteral(
"<a href=\"%1\">%2</a>" ).arg( protoUrl.toHtmlEscaped(), url.toHtmlEscaped() );
538 converted.replace( urlRegEx.pos( 1 ), url.length(), anchor );
539 offset = urlRegEx.pos( 1 ) + anchor.length();
542 while ( emailRegEx.indexIn( converted, offset ) != -1 )
545 QString email = emailRegEx.cap( 1 );
546 QString anchor = QStringLiteral(
"<a href=\"mailto:%1\">%1</a>" ).arg( email.toHtmlEscaped() );
547 converted.replace( emailRegEx.pos( 1 ), email.length(), anchor );
548 offset = emailRegEx.pos( 1 ) + anchor.length();
560 QString converted = html;
561 converted.replace( QLatin1String(
"<br>" ), QLatin1String(
"\n" ) );
562 converted.replace( QLatin1String(
"<b>" ), QLatin1String(
"**" ) );
563 converted.replace( QLatin1String(
"</b>" ), QLatin1String(
"**" ) );
565 static QRegExp hrefRegEx(
"<a\\s+href\\s*=\\s*([^<>]*)\\s*>([^<>]*)</a>" );
567 while ( hrefRegEx.indexIn( converted, offset ) != -1 )
569 QString url = hrefRegEx.cap( 1 ).replace( QStringLiteral(
"\"" ), QString() );
570 url.replace(
'\'', QString() );
571 QString name = hrefRegEx.cap( 2 );
572 QString anchor = QStringLiteral(
"[%1](%2)" ).arg( name, url );
573 converted.replace( hrefRegEx, anchor );
574 offset = hrefRegEx.pos( 1 ) + anchor.length();
580 QString
QgsStringUtils::wordWrap(
const QString &
string,
const int length,
const bool useMaxLineLength,
const QString &customDelimiter )
582 if (
string.isEmpty() || length == 0 )
587 int delimiterLength = 0;
589 if ( !customDelimiter.isEmpty() )
591 rx.setPatternSyntax( QRegExp::FixedString );
592 rx.setPattern( customDelimiter );
593 delimiterLength = customDelimiter.length();
598 rx.setPattern( QStringLiteral(
"[\\s\\x200B]" ) );
602 const QStringList lines =
string.split(
'\n' );
603 int strLength, strCurrent, strHit, lastHit;
605 for (
int i = 0; i < lines.size(); i++ )
607 strLength = lines.at( i ).length();
612 while ( strCurrent < strLength )
616 if ( useMaxLineLength )
619 strHit = lines.at( i ).lastIndexOf( rx, strCurrent + length );
620 if ( strHit == lastHit || strHit == -1 )
623 strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
629 strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
633 newstr.append( lines.at( i ).midRef( strCurrent, strHit - strCurrent ) );
634 newstr.append(
'\n' );
635 strCurrent = strHit + delimiterLength;
639 newstr.append( lines.at( i ).midRef( strCurrent ) );
640 strCurrent = strLength;
643 if ( i < lines.size() - 1 )
644 newstr.append(
'\n' );
652 string =
string.replace(
',', QChar( 65040 ) ).replace( QChar( 8229 ), QChar( 65072 ) );
653 string =
string.replace( QChar( 12289 ), QChar( 65041 ) ).replace( QChar( 12290 ), QChar( 65042 ) );
654 string =
string.replace(
':', QChar( 65043 ) ).replace(
';', QChar( 65044 ) );
655 string =
string.replace(
'!', QChar( 65045 ) ).replace(
'?', QChar( 65046 ) );
656 string =
string.replace( QChar( 12310 ), QChar( 65047 ) ).replace( QChar( 12311 ), QChar( 65048 ) );
657 string =
string.replace( QChar( 8230 ), QChar( 65049 ) );
658 string =
string.replace( QChar( 8212 ), QChar( 65073 ) ).replace( QChar( 8211 ), QChar( 65074 ) );
659 string =
string.replace(
'_', QChar( 65075 ) ).replace( QChar( 65103 ), QChar( 65076 ) );
660 string =
string.replace(
'(', QChar( 65077 ) ).replace(
')', QChar( 65078 ) );
661 string =
string.replace(
'{', QChar( 65079 ) ).replace(
'}', QChar( 65080 ) );
662 string =
string.replace(
'<', QChar( 65087 ) ).replace(
'>', QChar( 65088 ) );
663 string =
string.replace(
'[', QChar( 65095 ) ).replace(
']', QChar( 65096 ) );
664 string =
string.replace( QChar( 12308 ), QChar( 65081 ) ).replace( QChar( 12309 ), QChar( 65082 ) );
665 string =
string.replace( QChar( 12304 ), QChar( 65083 ) ).replace( QChar( 12305 ), QChar( 65084 ) );
666 string =
string.replace( QChar( 12298 ), QChar( 65085 ) ).replace( QChar( 12299 ), QChar( 65086 ) );
667 string =
string.replace( QChar( 12300 ), QChar( 65089 ) ).replace( QChar( 12301 ), QChar( 65090 ) );
668 string =
string.replace( QChar( 12302 ), QChar( 65091 ) ).replace( QChar( 12303 ), QChar( 65092 ) );
674 , mReplacement( replacement )
675 , mCaseSensitive( caseSensitive )
676 , mWholeWordOnly( wholeWordOnly )
678 if ( mWholeWordOnly )
679 mRx = QRegExp( QString(
"\\b%1\\b" ).arg( mMatch ),
680 mCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
685 QString result = input;
686 if ( !mWholeWordOnly )
688 return result.replace( mMatch, mReplacement, mCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
692 return result.replace( mRx, mReplacement );
699 map.insert( QStringLiteral(
"match" ), mMatch );
700 map.insert( QStringLiteral(
"replace" ), mReplacement );
701 map.insert( QStringLiteral(
"caseSensitive" ), mCaseSensitive ?
"1" :
"0" );
702 map.insert( QStringLiteral(
"wholeWord" ), mWholeWordOnly ?
"1" :
"0" );
709 properties.value( QStringLiteral(
"replace" ) ),
710 properties.value( QStringLiteral(
"caseSensitive" ), QStringLiteral(
"0" ) ) == QLatin1String(
"1" ),
711 properties.value( QStringLiteral(
"wholeWord" ), QStringLiteral(
"0" ) ) == QLatin1String(
"1" ) );
716 QString result = input;
717 const auto constMReplacements = mReplacements;
720 result = r.process( result );
727 const auto constMReplacements = mReplacements;
731 QDomElement propEl = doc.createElement( QStringLiteral(
"replacement" ) );
732 QgsStringMap::const_iterator it = props.constBegin();
733 for ( ; it != props.constEnd(); ++it )
735 propEl.setAttribute( it.key(), it.value() );
737 elem.appendChild( propEl );
743 mReplacements.clear();
744 QDomNodeList nodelist = elem.elementsByTagName( QStringLiteral(
"replacement" ) );
745 for (
int i = 0; i < nodelist.count(); i++ )
747 QDomElement replacementElem = nodelist.at( i ).toElement();
748 QDomNamedNodeMap nodeMap = replacementElem.attributes();
751 for (
int j = 0; j < nodeMap.count(); ++j )
753 props.insert( nodeMap.item( j ).nodeName(), nodeMap.item( j ).nodeValue() );