LCOV - code coverage report
Current view: top level - muse/framework/global - stringutils.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 69.6 % 171 119
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "stringutils.h"
      23              : 
      24              : #include <cctype>
      25              : #include <algorithm>
      26              : 
      27           12 : bool muse::strings::replace(std::string& str, const std::string& from, const std::string& to)
      28              : {
      29           12 :     size_t start_pos = str.find(from);
      30           12 :     if (start_pos == std::string::npos) {
      31           10 :         return false;
      32              :     }
      33            2 :     str.replace(start_pos, from.length(), to);
      34            2 :     return true;
      35              : }
      36              : 
      37              : namespace {
      38        80333 : void split_simple(const std::string& str, std::vector<std::string>& out, const std::string& delim)
      39              : {
      40        80333 :     if (delim.empty()) {
      41            1 :         out.push_back(str);
      42            1 :         return;
      43              :     }
      44              : 
      45        80332 :     std::size_t current, previous = 0;
      46        80332 :     current = str.find(delim);
      47        80332 :     std::size_t delimLen = delim.length();
      48              : 
      49       247100 :     while (current != std::string::npos) {
      50       166768 :         out.push_back(str.substr(previous, current - previous));
      51       166768 :         previous = current + delimLen;
      52       166768 :         current = str.find(delim, previous);
      53              :     }
      54        80332 :     out.push_back(str.substr(previous, current - previous));
      55              : }
      56              : 
      57              : //! Split that handles an escape character, which causes the following escape or separator to be emitted verbatim.
      58            7 : void split_handle_escape(const std::string& str, std::vector<std::string>& out, const std::string& delim, const std::string& esc)
      59              : {
      60            7 :     const size_t delimLen = delim.size();
      61            7 :     const size_t escLen = esc.size();
      62            7 :     std::string current;
      63              : 
      64          138 :     for (size_t i = 0; i < str.size();) {
      65          131 :         if (escLen > 0 && str.compare(i, escLen, esc) == 0) {
      66              :             // Escaped sequence: drop the escape, then emit the following escape
      67              :             // or separator token literally (mirrors what join() produced). Any
      68              :             // other (or dangling) escape consumes a single following character.
      69           16 :             i += escLen;
      70           16 :             if (str.compare(i, escLen, esc) == 0) {
      71            5 :                 current += esc;
      72            5 :                 i += escLen;
      73           11 :             } else if (str.compare(i, delimLen, delim) == 0) {
      74           10 :                 current += delim;
      75           10 :                 i += delimLen;
      76            1 :             } else if (i < str.size()) {
      77            0 :                 current += str[i];
      78            0 :                 ++i;
      79              :             }
      80          115 :         } else if (delimLen > 0 && str.compare(i, delimLen, delim) == 0) {
      81           16 :             out.push_back(current);
      82           16 :             current.clear();
      83           16 :             i += delimLen;
      84              :         } else {
      85           99 :             current += str[i];
      86           99 :             ++i;
      87              :         }
      88              :     }
      89            7 :     out.push_back(current);
      90            7 : }
      91              : }
      92              : 
      93        80340 : void muse::strings::split(const std::string& str, std::vector<std::string>& out, const std::string& delim,
      94              :                           const std::string& esc)
      95              : {
      96        80340 :     if (esc.empty()) {
      97        80333 :         split_simple(str, out, delim);
      98              :     } else {
      99            7 :         split_handle_escape(str, out, delim, esc);
     100              :     }
     101        80340 : }
     102              : 
     103              : namespace {
     104           30 : std::string join_simple(const std::vector<std::string>& strs, const std::string& sep)
     105              : {
     106           30 :     std::string str;
     107           30 :     bool first = true;
     108           73 :     for (const std::string& s : strs) {
     109           43 :         if (!first) {
     110           15 :             str += sep;
     111              :         }
     112           43 :         first = false;
     113           43 :         str += s;
     114              :     }
     115           30 :     return str;
     116            0 : }
     117              : 
     118              : //! Join that prefixes any separator or escape characters in the input with the escape string.
     119            8 : std::string join_handle_escape(const std::vector<std::string>& strs, const std::string& sep, const std::string& esc)
     120              : {
     121            8 :     std::string str;
     122            8 :     bool first = true;
     123           34 :     for (const std::string& s : strs) {
     124           26 :         if (!first) {
     125           18 :             str += sep;
     126              :         }
     127           26 :         first = false;
     128          145 :         for (size_t i = 0; i < s.size(); ++i) {
     129          119 :             if (esc.size() > 0 && s.compare(i, esc.size(), esc) == 0) {
     130            6 :                 str += esc;
     131            6 :                 str += esc;
     132            6 :                 i += esc.size() - 1;
     133          113 :             } else if (!sep.empty() && s.compare(i, sep.size(), sep) == 0) {
     134           11 :                 str += esc;
     135           11 :                 str += sep;
     136           11 :                 i += sep.size() - 1;
     137              :             } else {
     138          102 :                 str += s[i];
     139              :             }
     140              :         }
     141              :     }
     142            8 :     return str;
     143            0 : }
     144              : }
     145              : 
     146           38 : std::string muse::strings::join(const std::vector<std::string>& strs, const std::string& sep, const std::string& esc)
     147              : {
     148           38 :     if (esc.empty()) {
     149           30 :         return join_simple(strs, sep);
     150              :     } else {
     151            8 :         return join_handle_escape(strs, sep, esc);
     152              :     }
     153              : }
     154              : 
     155           94 : void muse::strings::ltrim(std::string& s)
     156              : {
     157           94 :     s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
     158           94 :         return !std::isspace(ch);
     159              :     }));
     160           94 : }
     161              : 
     162           94 : void muse::strings::rtrim(std::string& s)
     163              : {
     164          188 :     s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
     165           94 :         return !std::isspace(ch);
     166          188 :     }).base(), s.end());
     167           94 : }
     168              : 
     169           94 : void muse::strings::trim(std::string& s)
     170              : {
     171           94 :     ltrim(s);
     172           94 :     rtrim(s);
     173           94 : }
     174              : 
     175        34664 : std::string muse::strings::toLower(const std::string& source)
     176              : {
     177        34664 :     std::string str = source;
     178        34664 :     std::for_each(str.begin(), str.end(), [](char& c) {
     179       234782 :         c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
     180       234782 :     });
     181        34664 :     return str;
     182              : }
     183              : 
     184            0 : std::string muse::strings::toUpper(const std::string& source)
     185              : {
     186            0 :     std::string str = source;
     187            0 :     std::for_each(str.begin(), str.end(), [](char& c) {
     188            0 :         c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
     189            0 :     });
     190            0 :     return str;
     191              : }
     192              : 
     193            0 : bool muse::strings::startsWith(const std::string& str, const std::string& start)
     194              : {
     195            0 :     if (str.size() < start.size()) {
     196            0 :         return false;
     197              :     }
     198              : 
     199            0 :     for (size_t i = 0; i < start.size(); ++i) {
     200            0 :         if (str.at(i) != start.at(i)) {
     201            0 :             return false;
     202              :         }
     203              :     }
     204              : 
     205            0 :     return true;
     206              : }
     207              : 
     208           46 : bool muse::strings::startsWith(const std::string& str, const std::string_view& start)
     209              : {
     210           46 :     if (str.size() < start.size()) {
     211            0 :         return false;
     212              :     }
     213              : 
     214           46 :     for (size_t i = 0; i < start.size(); ++i) {
     215           46 :         if (str.at(i) != start.at(i)) {
     216           46 :             return false;
     217              :         }
     218              :     }
     219              : 
     220            0 :     return true;
     221              : }
     222              : 
     223           46 : bool muse::strings::startsWith(const std::string& str, const char* start)
     224              : {
     225           46 :     return startsWith(str, std::string_view(start));
     226              : }
     227              : 
     228            0 : bool muse::strings::endsWith(const std::string& str, const std::string& ending)
     229              : {
     230            0 :     if (ending.size() > str.size()) {
     231            0 :         return false;
     232              :     }
     233            0 :     std::string ss = str.substr(str.size() - ending.size());
     234            0 :     return ss == ending;
     235            0 : }
     236              : 
     237            0 : bool muse::strings::endsWith(const std::string& str, const std::string_view& ending)
     238              : {
     239            0 :     if (ending.size() > str.size()) {
     240            0 :         return false;
     241              :     }
     242            0 :     std::string ss = str.substr(str.size() - ending.size());
     243            0 :     return ss == ending;
     244            0 : }
     245              : 
     246            0 : bool muse::strings::endsWith(const std::string& str, const char* end)
     247              : {
     248            0 :     return endsWith(str, std::string_view(end));
     249              : }
     250              : 
     251            0 : std::string muse::strings::leftJustified(const std::string& val, size_t width)
     252              : {
     253            0 :     std::string str;
     254            0 :     str.resize(width, ' ');
     255            0 :     size_t length = width < val.size() ? width : val.size();
     256            0 :     for (size_t i = 0; i < length; ++i) {
     257            0 :         str[i] = val[i];
     258              :     }
     259            0 :     return str;
     260            0 : }
     261              : 
     262            0 : bool muse::strings::lessThanCaseInsensitive(const std::string& lhs, const std::string& rhs)
     263              : {
     264            0 :     int cmp = toLower(lhs).compare(toLower(rhs));
     265            0 :     if (cmp == 0) {
     266            0 :         return lhs < rhs;
     267              :     }
     268              : 
     269            0 :     return cmp < 0;
     270              : }
     271              : 
     272            0 : bool muse::strings::lessThanCaseInsensitive(const String& lhs, const String& rhs)
     273              : {
     274            0 :     String lhsLower = lhs.toLower(), rhsLower = rhs.toLower();
     275            0 :     if (lhsLower == rhsLower) {
     276            0 :         return lhs < rhs;
     277              :     }
     278              : 
     279            0 :     return lhsLower < rhsLower;
     280            0 : }
     281              : 
     282         4337 : size_t muse::strings::levenshteinDistance(const std::string& s1, const std::string& s2)
     283              : {
     284         4337 :     size_t N1 = s1.length();
     285         4337 :     size_t N2 = s2.length();
     286              :     size_t i, j;
     287         4337 :     std::vector<size_t> V(N2 + 1);
     288              : 
     289        49046 :     for (i = 0; i <= N2; i++) {
     290        44709 :         V[i] = i;
     291              :     }
     292              : 
     293        33603 :     for (i = 0; i < N1; i++) {
     294        29266 :         V[0] = i + 1;
     295        29266 :         size_t corner = i;
     296       321845 :         for (j = 0; j < N2; j++) {
     297       292579 :             size_t upper = V[j + 1];
     298       292579 :             if (s1[i] == s2[j]) {
     299        22773 :                 V[j + 1] = corner;
     300              :             } else {
     301       269806 :                 V[j + 1] = std::min(V[j], std::min(upper, corner)) + 1;
     302              :             }
     303       292579 :             corner = upper;
     304              :         }
     305              :     }
     306         8674 :     return V[N2];
     307         4337 : }
        

Generated by: LCOV version 2.0-1