19 #include <QStringList> 
   20 #include <QTextBoundaryFinder> 
   21 #include <QRegularExpression> 
   26   if ( 
string.isEmpty() )
 
   29   switch ( capitalization )
 
   36       return string.toUpper();
 
   40       return string.toLower();
 
   44       QString temp = string;
 
   46       QTextBoundaryFinder wordSplitter( QTextBoundaryFinder::Word, 
string.constData(), 
string.length(), 
nullptr, 0 );
 
   47       QTextBoundaryFinder letterSplitter( QTextBoundaryFinder::Grapheme, 
string.constData(), 
string.length(), 
nullptr, 0 );
 
   49       wordSplitter.setPosition( 0 );
 
   51       while ( ( first && wordSplitter.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
 
   52               || wordSplitter.toNextBoundary() >= 0 )
 
   55         letterSplitter.setPosition( wordSplitter.position() );
 
   56         letterSplitter.toNextBoundary();
 
   57         QString substr = 
string.mid( wordSplitter.position(), letterSplitter.position() - wordSplitter.position() );
 
   58         temp.replace( wordSplitter.position(), substr.length(), substr.toUpper() );
 
   67       static QStringList smallWords;
 
   68       static QStringList newPhraseSeparators;
 
   69       static QRegularExpression splitWords;
 
   70       if ( smallWords.empty() )
 
   72         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( 
'|' );
 
   73         newPhraseSeparators = QObject::tr( 
".|:" ).split( 
'|' );
 
   74         splitWords = QRegularExpression( QStringLiteral( 
"\\b" ), QRegularExpression::UseUnicodePropertiesOption );
 
   77       const bool allSameCase = 
string.toLower() == 
string || 
string.toUpper() == string;
 
   78 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 
   79       const QStringList parts = ( allSameCase ? 
string.toLower() : string ).split( splitWords, QString::SkipEmptyParts );
 
   81       const QStringList parts = ( allSameCase ? 
string.toLower() : string ).split( splitWords, Qt::SkipEmptyParts );
 
   84       bool firstWord = 
true;
 
   86       int lastWord = parts.count() - 1;
 
   87       for ( 
const QString &word : std::as_const( parts ) )
 
   89         if ( newPhraseSeparators.contains( word.trimmed() ) )
 
   94         else if ( firstWord || ( i == lastWord ) || !smallWords.contains( word ) )
 
   96           result += word.at( 0 ).toUpper() + word.mid( 1 );
 
  110       result.remove( 
' ' );
 
  121   for ( 
int i = 0; i < 
string.size(); ++i )
 
  123     QChar ch = 
string.at( i );
 
  124     if ( ch.unicode() > 160 )
 
  125       encoded += QStringLiteral( 
"&#%1;" ).arg( 
static_cast< int >( ch.unicode() ) );
 
  126     else if ( ch.unicode() == 38 )
 
  127       encoded += QLatin1String( 
"&" );
 
  128     else if ( ch.unicode() == 60 )
 
  129       encoded += QLatin1String( 
"<" );
 
  130     else if ( ch.unicode() == 62 )
 
  131       encoded += QLatin1String( 
">" );
 
  140   int length1 = string1.length();
 
  141   int length2 = string2.length();
 
  144   if ( string1.isEmpty() )
 
  148   else if ( string2.isEmpty() )
 
  154   QString s1( caseSensitive ? string1 : string1.toLower() );
 
  155   QString s2( caseSensitive ? string2 : string2.toLower() );
 
  157   const QChar *s1Char = s1.constData();
 
  158   const QChar *s2Char = s2.constData();
 
  161   int commonPrefixLen = 0;
 
  162   while ( length1 > 0 && length2 > 0 && *s1Char == *s2Char )
 
  172   while ( length1 > 0 && length2 > 0 && s1.at( commonPrefixLen + length1 - 1 ) == s2.at( commonPrefixLen + length2 - 1 ) )
 
  183   else if ( length2 == 0 )
 
  189   if ( length1 > length2 )
 
  192     std::swap( length1, length2 );
 
  197   col.fill( 0, length2 + 1 );
 
  198   QVector< int > prevCol;
 
  199   prevCol.reserve( length2 + 1 );
 
  200   for ( 
int i = 0; i < length2 + 1; ++i )
 
  204   const QChar *s2start = s2Char;
 
  205   for ( 
int i = 0; i < length1; ++i )
 
  209     for ( 
int j = 0; j < length2; ++j )
 
  211       col[j + 1] = std::min( std::min( 1 + col[j], 1 + prevCol[1 + j] ), prevCol[j] + ( ( *s1Char == *s2Char ) ? 0 : 1 ) );
 
  217   return prevCol[length2];
 
  222   if ( string1.isEmpty() || string2.isEmpty() )
 
  229   QString s1( caseSensitive ? string1 : string1.toLower() );
 
  230   QString s2( caseSensitive ? string2 : string2.toLower() );
 
  238   int *currentScores = 
new int [ s2.length()];
 
  239   int *previousScores = 
new int [ s2.length()];
 
  240   int maxCommonLength = 0;
 
  241   int lastMaxBeginIndex = 0;
 
  243   const QChar *s1Char = s1.constData();
 
  244   const QChar *s2Char = s2.constData();
 
  245   const QChar *s2Start = s2Char;
 
  247   for ( 
int i = 0; i < s1.length(); ++i )
 
  249     for ( 
int j = 0; j < s2.length(); ++j )
 
  251       if ( *s1Char != *s2Char )
 
  253         currentScores[j] = 0;
 
  257         if ( i == 0 || j == 0 )
 
  259           currentScores[j] = 1;
 
  263           currentScores[j] = 1 + previousScores[j - 1];
 
  266         if ( maxCommonLength < currentScores[j] )
 
  268           maxCommonLength = currentScores[j];
 
  269           lastMaxBeginIndex = i;
 
  274     std::swap( currentScores, previousScores );
 
  278   delete [] currentScores;
 
  279   delete [] previousScores;
 
  280   return string1.mid( lastMaxBeginIndex - maxCommonLength + 1, maxCommonLength );
 
  285   if ( string1.isEmpty() && string2.isEmpty() )
 
  291   if ( string1.length() != string2.length() )
 
  298   QString s1( caseSensitive ? string1 : string1.toLower() );
 
  299   QString s2( caseSensitive ? string2 : string2.toLower() );
 
  308   const QChar *s1Char = s1.constData();
 
  309   const QChar *s2Char = s2.constData();
 
  311   for ( 
int i = 0; i < string1.length(); ++i )
 
  313     if ( *s1Char != *s2Char )
 
  324   if ( 
string.isEmpty() )
 
  327   QString tmp = 
string.toUpper();
 
  330   QChar *char1 = tmp.data();
 
  331   QChar *char2 = tmp.data();
 
  333   for ( 
int i = 0; i < tmp.length(); ++i, ++char2 )
 
  335     if ( ( *char2 ).unicode() >= 0x41 && ( *char2 ).unicode() <= 0x5A && ( i == 0 || ( ( *char2 ).unicode() != 0x41 && ( *char2 ).unicode() != 0x45
 
  336          && ( *char2 ).unicode() != 0x48 && ( *char2 ).unicode() != 0x49
 
  337          && ( *char2 ).unicode() != 0x4F && ( *char2 ).unicode() != 0x55
 
  338          && ( *char2 ).unicode() != 0x57 && ( *char2 ).unicode() != 0x59 ) ) )
 
  345   tmp.truncate( outLen );
 
  347   QChar *tmpChar = tmp.data();
 
  349   for ( 
int i = 1; i < tmp.length(); ++i, ++tmpChar )
 
  351     switch ( ( *tmpChar ).unicode() )
 
  357         tmp.replace( i, 1, QChar( 0x31 ) );
 
  368         tmp.replace( i, 1, QChar( 0x32 ) );
 
  373         tmp.replace( i, 1, QChar( 0x33 ) );
 
  377         tmp.replace( i, 1, QChar( 0x34 ) );
 
  382         tmp.replace( i, 1, QChar( 0x35 ) );
 
  386         tmp.replace( i, 1, QChar( 0x36 ) );
 
  396   for ( 
int i = 1; i < tmp.length(); ++i, ++char2 )
 
  398     if ( *char2 != *char1 )
 
  407   tmp.truncate( outLen );
 
  408   if ( tmp.length() < 4 )
 
  420   QString candidateNormalized = candidate.simplified().normalized( QString:: NormalizationForm_C ).toLower();
 
  421   QString searchNormalized = search.simplified().normalized( QString:: NormalizationForm_C ).toLower();
 
  423   int candidateLength = candidateNormalized.length();
 
  424   int searchLength = searchNormalized.length();
 
  428   if ( candidateLength == 0 || searchLength == 0 )
 
  431   int candidateIdx = 0;
 
  436   bool isPreviousIndexMatching = 
false;
 
  437   bool isWordOpen = 
true;
 
  440   while ( candidateIdx < candidateLength )
 
  442     QChar candidateChar = candidateNormalized[ candidateIdx++ ];
 
  443     bool isCandidateCharWordEnd = candidateChar == 
' ' || candidateChar.isPunct();
 
  446     if ( candidateIdx == 1 )
 
  449     else if ( isCandidateCharWordEnd )
 
  456     if ( searchIdx >= searchLength )
 
  459     QChar searchChar = searchNormalized[ searchIdx ];
 
  460     bool isSearchCharWordEnd = searchChar == 
' ' || searchChar.isPunct();
 
  463     if ( candidateChar == searchChar || ( isCandidateCharWordEnd && isSearchCharWordEnd ) )
 
  468       if ( isSearchCharWordEnd )
 
  472         else if ( isPreviousIndexMatching )
 
  480       else if ( isPreviousIndexMatching )
 
  490       isPreviousIndexMatching = 
true;
 
  495       isPreviousIndexMatching = 
false;
 
  500     if ( searchIdx >= searchLength )
 
  502       bool isEndOfWord = ( candidateIdx >= candidateLength )
 
  504                          : candidateNormalized[candidateIdx] == 
' ' || candidateNormalized[candidateIdx].isPunct();
 
  515   if ( searchIdx < searchLength )
 
  518   return static_cast<float>( std::max( score, 0 ) ) / std::max( maxScore, 1 );
 
  524   QString converted = string;
 
  528   static thread_local QRegularExpression urlRegEx( QStringLiteral( 
"(\\b(([\\w-]+://?|www[.])[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~\\s]|/))))" ) );
 
  529   static thread_local QRegularExpression protoRegEx( QStringLiteral( 
"^(?:f|ht)tps?://|file://" ) );
 
  530   static thread_local QRegularExpression emailRegEx( QStringLiteral( 
"([\\w._%+-]+@[\\w.-]+\\.[A-Za-z]+)" ) );
 
  534   QRegularExpressionMatch match = urlRegEx.match( converted );
 
  535   while ( match.hasMatch() )
 
  538     QString url = match.captured( 1 );
 
  539     QString protoUrl = url;
 
  540     if ( !protoRegEx.match( protoUrl ).hasMatch() )
 
  542       protoUrl.prepend( 
"http://" );
 
  544     QString anchor = QStringLiteral( 
"<a href=\"%1\">%2</a>" ).arg( protoUrl.toHtmlEscaped(), url.toHtmlEscaped() );
 
  545     converted.replace( match.capturedStart( 1 ), url.length(), anchor );
 
  546     offset = match.capturedStart( 1 ) + anchor.length();
 
  547     match = urlRegEx.match( converted, offset );
 
  551   match = emailRegEx.match( converted );
 
  552   while ( match.hasMatch() )
 
  555     QString email = match.captured( 1 );
 
  556     QString anchor = QStringLiteral( 
"<a href=\"mailto:%1\">%1</a>" ).arg( email.toHtmlEscaped() );
 
  557     converted.replace( match.capturedStart( 1 ), email.length(), anchor );
 
  558     offset = match.capturedStart( 1 ) + anchor.length();
 
  559     match = emailRegEx.match( converted, offset );
 
  570   const thread_local QRegularExpression rxUrl( QStringLiteral( 
"^(http|https|ftp|file)://\\S+$" ) );
 
  571   return rxUrl.match( 
string ).hasMatch();
 
  577   QString converted = html;
 
  578   converted.replace( QLatin1String( 
"<br>" ), QLatin1String( 
"\n" ) );
 
  579   converted.replace( QLatin1String( 
"<b>" ), QLatin1String( 
"**" ) );
 
  580   converted.replace( QLatin1String( 
"</b>" ), QLatin1String( 
"**" ) );
 
  581   converted.replace( QLatin1String( 
"<pre>" ), QLatin1String( 
"\n```\n" ) );
 
  582   converted.replace( QLatin1String( 
"</pre>" ), QLatin1String( 
"```\n" ) );
 
  584   static thread_local QRegularExpression hrefRegEx( QStringLiteral( 
"<a\\s+href\\s*=\\s*([^<>]*)\\s*>([^<>]*)</a>" ) );
 
  587   QRegularExpressionMatch match = hrefRegEx.match( converted );
 
  588   while ( match.hasMatch() )
 
  590     QString url = match.captured( 1 ).replace( QLatin1String( 
"\"" ), QString() );
 
  591     url.replace( 
'\'', QString() );
 
  592     QString name = match.captured( 2 );
 
  593     QString anchor = QStringLiteral( 
"[%1](%2)" ).arg( name, url );
 
  594     converted.replace( match.capturedStart(), match.capturedLength(), anchor );
 
  595     offset = match.capturedStart() + anchor.length();
 
  596     match = hrefRegEx.match( converted, offset );
 
  602 QString 
QgsStringUtils::wordWrap( 
const QString &
string, 
const int length, 
const bool useMaxLineLength, 
const QString &customDelimiter )
 
  604   if ( 
string.isEmpty() || length == 0 )
 
  608   QRegularExpression rx;
 
  609   int delimiterLength = 0;
 
  611   if ( !customDelimiter.isEmpty() )
 
  613     rx.setPattern( QRegularExpression::escape( customDelimiter ) );
 
  614     delimiterLength = customDelimiter.length();
 
  619     rx.setPattern( QStringLiteral( 
"[\\x{200B}\\s]" ) );
 
  623   const QStringList lines = 
string.split( 
'\n' );
 
  624   int strLength, strCurrent, strHit, lastHit;
 
  626   for ( 
int i = 0; i < lines.size(); i++ )
 
  628     const QString line = lines.at( i );
 
  629     strLength = line.length();
 
  630     if ( strLength <= length )
 
  633       newstr.append( line );
 
  634       if ( i < lines.size() - 1 )
 
  635         newstr.append( 
'\n' );
 
  642     while ( strCurrent < strLength )
 
  646       if ( useMaxLineLength )
 
  649         strHit = ( strCurrent + length >= strLength ) ? -1 : line.lastIndexOf( rx, strCurrent + length );
 
  650         if ( strHit == lastHit || strHit == -1 )
 
  653           strHit = ( strCurrent + std::abs( length ) >= strLength ) ? -1 : line.indexOf( rx, strCurrent + std::abs( length ) );
 
  659         strHit = ( strCurrent + std::abs( length ) >= strLength ) ? -1 : line.indexOf( rx, strCurrent + std::abs( length ) );
 
  663 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2) 
  664         newstr.append( line.midRef( strCurrent, strHit - strCurrent ) );
 
  666         newstr.append( QStringView {line} .mid( strCurrent, strHit - strCurrent ) );
 
  668         newstr.append( 
'\n' );
 
  669         strCurrent = strHit + delimiterLength;
 
  673 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2) 
  674         newstr.append( line.midRef( strCurrent ) );
 
  676         newstr.append( QStringView {line} .mid( strCurrent ) );
 
  678         strCurrent = strLength;
 
  681     if ( i < lines.size() - 1 )
 
  682       newstr.append( 
'\n' );
 
  690   string = 
string.replace( 
',', QChar( 65040 ) ).replace( QChar( 8229 ), QChar( 65072 ) ); 
 
  691   string = 
string.replace( QChar( 12289 ), QChar( 65041 ) ).replace( QChar( 12290 ), QChar( 65042 ) ); 
 
  692   string = 
string.replace( 
':', QChar( 65043 ) ).replace( 
';', QChar( 65044 ) );
 
  693   string = 
string.replace( 
'!', QChar( 65045 ) ).replace( 
'?', QChar( 65046 ) );
 
  694   string = 
string.replace( QChar( 12310 ), QChar( 65047 ) ).replace( QChar( 12311 ), QChar( 65048 ) ); 
 
  695   string = 
string.replace( QChar( 8230 ), QChar( 65049 ) ); 
 
  696   string = 
string.replace( QChar( 8212 ), QChar( 65073 ) ).replace( QChar( 8211 ), QChar( 65074 ) ); 
 
  697   string = 
string.replace( 
'_', QChar( 65075 ) ).replace( QChar( 65103 ), QChar( 65076 ) ); 
 
  698   string = 
string.replace( 
'(', QChar( 65077 ) ).replace( 
')', QChar( 65078 ) );
 
  699   string = 
string.replace( 
'{', QChar( 65079 ) ).replace( 
'}', QChar( 65080 ) );
 
  700   string = 
string.replace( 
'<', QChar( 65087 ) ).replace( 
'>', QChar( 65088 ) );
 
  701   string = 
string.replace( 
'[', QChar( 65095 ) ).replace( 
']', QChar( 65096 ) );
 
  702   string = 
string.replace( QChar( 12308 ), QChar( 65081 ) ).replace( QChar( 12309 ), QChar( 65082 ) );   
 
  703   string = 
string.replace( QChar( 12304 ), QChar( 65083 ) ).replace( QChar( 12305 ), QChar( 65084 ) );   
 
  704   string = 
string.replace( QChar( 12298 ), QChar( 65085 ) ).replace( QChar( 12299 ), QChar( 65086 ) ); 
 
  705   string = 
string.replace( QChar( 12300 ), QChar( 65089 ) ).replace( QChar( 12301 ), QChar( 65090 ) );   
 
  706   string = 
string.replace( QChar( 12302 ), QChar( 65091 ) ).replace( QChar( 12303 ), QChar( 65092 ) );   
 
  713   const QLatin1Char backslash( 
'\\' );
 
  714   const int count = 
string.count();
 
  717   escaped.reserve( count * 2 );
 
  718   for ( 
int i = 0; i < count; i++ )
 
  720     switch ( 
string.at( i ).toLatin1() )
 
  736         escaped.append( backslash );
 
  738     escaped.append( 
string.at( i ) );
 
  745   const int charactersToTruncate = 
string.length() - maxLength;
 
  746   if ( charactersToTruncate <= 0 )
 
  750   const int truncateFrom = 
string.length() / 2 - ( charactersToTruncate + 1 ) / 2;
 
  752 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 
  753   return string.leftRef( truncateFrom ) + QString( QChar( 0x2026 ) ) + 
string.midRef( truncateFrom + charactersToTruncate + 1 );
 
  755   return QStringView( 
string ).first( truncateFrom ) + QString( QChar( 0x2026 ) ) + QStringView( 
string ).sliced( truncateFrom + charactersToTruncate + 1 );
 
  761   , mReplacement( replacement )
 
  762   , mCaseSensitive( caseSensitive )
 
  763   , mWholeWordOnly( wholeWordOnly )
 
  765   if ( mWholeWordOnly )
 
  767     mRx.setPattern( QStringLiteral( 
"\\b%1\\b" ).arg( mMatch ) );
 
  768     mRx.setPatternOptions( mCaseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption );
 
  774   QString result = input;
 
  775   if ( !mWholeWordOnly )
 
  777     return result.replace( mMatch, mReplacement, mCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
 
  781     return result.replace( mRx, mReplacement );
 
  788   map.insert( QStringLiteral( 
"match" ), mMatch );
 
  789   map.insert( QStringLiteral( 
"replace" ), mReplacement );
 
  790   map.insert( QStringLiteral( 
"caseSensitive" ), mCaseSensitive ? QStringLiteral( 
"1" ) : QStringLiteral( 
"0" ) );
 
  791   map.insert( QStringLiteral( 
"wholeWord" ), mWholeWordOnly ? QStringLiteral( 
"1" ) : QStringLiteral( 
"0" ) );
 
  798                                properties.value( QStringLiteral( 
"replace" ) ),
 
  799                                properties.value( QStringLiteral( 
"caseSensitive" ), QStringLiteral( 
"0" ) ) == QLatin1String( 
"1" ),
 
  800                                properties.value( QStringLiteral( 
"wholeWord" ), QStringLiteral( 
"0" ) ) == QLatin1String( 
"1" ) );
 
  805   QString result = input;
 
  808     result = r.process( result );
 
  818     QDomElement propEl = doc.createElement( QStringLiteral( 
"replacement" ) );
 
  819     QgsStringMap::const_iterator it = props.constBegin();
 
  820     for ( ; it != props.constEnd(); ++it )
 
  822       propEl.setAttribute( it.key(), it.value() );
 
  824     elem.appendChild( propEl );
 
  830   mReplacements.clear();
 
  831   QDomNodeList nodelist = elem.elementsByTagName( QStringLiteral( 
"replacement" ) );
 
  832   for ( 
int i = 0; i < nodelist.count(); i++ )
 
  834     QDomElement replacementElem = nodelist.at( i ).toElement();
 
  835     QDomNamedNodeMap nodeMap = replacementElem.attributes();
 
  838     for ( 
int j = 0; j < nodeMap.count(); ++j )
 
  840       props.insert( nodeMap.item( j ).nodeName(), nodeMap.item( j ).nodeValue() );