File indexing completed on 2024-05-26 04:57:58

0001 /**
0002  * \file formatreplacer.cpp
0003  * Replaces format codes in a string.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 06 Jul 2008
0008  *
0009  * Copyright (C) 2008-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "formatreplacer.h"
0028 #include <QUrl>
0029 #include "saferename.h"
0030 
0031 /**
0032  * Constructor.
0033  *
0034  * @param str string with format codes
0035  */
0036 FormatReplacer::FormatReplacer(const QString& str) : m_str(str) {}
0037 
0038 /**
0039  * Destructor.
0040  */
0041 FormatReplacer::~FormatReplacer()
0042 {
0043 }
0044 
0045 /**
0046  * Replace escaped characters.
0047  * Replaces the escaped characters ("\n", "\t", "\r", "\\", "\a", "\b",
0048  * "\f", "\v") with the corresponding characters.
0049  */
0050 void FormatReplacer::replaceEscapedChars()
0051 {
0052   if (!m_str.isEmpty()) {
0053     constexpr int numEscCodes = 8;
0054     constexpr QChar escCode[numEscCodes] = {
0055       QLatin1Char('n'), QLatin1Char('t'), QLatin1Char('r'), QLatin1Char('\\'),
0056       QLatin1Char('a'), QLatin1Char('b'), QLatin1Char('f'), QLatin1Char('v')};
0057     constexpr char escChar[numEscCodes] = {
0058       '\n', '\t', '\r', '\\', '\a', '\b', '\f', '\v'};
0059 
0060     for (int pos = 0; pos < m_str.length();) {
0061       pos = m_str.indexOf(QLatin1Char('\\'), pos);
0062       if (pos == -1) break;
0063       ++pos;
0064       for (int k = 0;; ++k) {
0065         if (k >= numEscCodes) {
0066           // invalid code at pos
0067           ++pos;
0068           break;
0069         }
0070         if (m_str[pos] == escCode[k]) {
0071           // code found, replace it
0072           m_str.replace(pos - 1, 2, QLatin1Char(escChar[k]));
0073           break;
0074         }
0075       }
0076     }
0077   }
0078 }
0079 
0080 /**
0081  * Replace percent codes.
0082  *
0083  * @param flags flags: FSF_SupportUrlEncode to support modifier u
0084  *              (with code c "%uc") to URL encode,
0085  *              FSF_ReplaceSeparators to replace directory separators
0086  *              ('/', '\\', ':') in tags,
0087  *              FSF_SupportHtmlEscape to support modifier h
0088  *              (with code c "%hc") to replace HTML metacharacters
0089  *              ('<', '>', '&', '"', ''', non-ascii) in tags.
0090  */
0091 void FormatReplacer::replacePercentCodes(unsigned flags)
0092 {
0093   if (!m_str.isEmpty()) {
0094     for (int pos = 0; pos < m_str.length();) {
0095       pos = m_str.indexOf(QLatin1Char('%'), pos);
0096       if (pos == -1) break;
0097 
0098       int codePos = pos + 1;
0099       int codeLen = 0;
0100       QString prefix, postfix;
0101       bool urlEncode = false;
0102       bool htmlEscape = false;
0103       QString repl;
0104       if ((flags & FSF_SupportUrlEncode) && m_str[codePos] == QLatin1Char('u')) {
0105         ++codePos;
0106         urlEncode = true;
0107       }
0108       if ((flags & FSF_SupportHtmlEscape) && m_str[codePos] == QLatin1Char('h')) {
0109         ++codePos;
0110         htmlEscape = true;
0111       }
0112       if (m_str[codePos] == QLatin1Char('{')) {
0113         if (int closingBracePos = m_str.indexOf(QLatin1Char('}'), codePos + 1);
0114             closingBracePos > codePos + 1) {
0115           QString longCode =
0116             m_str.mid(codePos + 1, closingBracePos - codePos - 1).toLower();
0117           if (longCode.startsWith(QLatin1Char('"'))) {
0118             if (int prefixEnd = longCode.indexOf(QLatin1Char('"'), 1);
0119                 prefixEnd != -1 && prefixEnd < longCode.length() - 2) {
0120               prefix = longCode.mid(1, prefixEnd - 1);
0121               longCode.remove(0, prefixEnd + 1);
0122             }
0123           }
0124           if (longCode.endsWith(QLatin1Char('"'))) {
0125             if (int postfixStart = longCode.lastIndexOf(QLatin1Char('"'), -2);
0126                 postfixStart > 1) {
0127               postfix = longCode.mid(postfixStart + 1,
0128                                      longCode.length() - postfixStart - 2);
0129               longCode.truncate(postfixStart);
0130             }
0131           }
0132           repl = getReplacement(longCode);
0133           codeLen = closingBracePos - pos + 1;
0134         }
0135       } else {
0136         repl = getReplacement(QString(m_str[codePos]));
0137         codeLen = codePos - pos + 1;
0138       }
0139 
0140       if (codeLen > 0) {
0141         if (flags & FSF_ReplaceSeparators) {
0142 #ifdef Q_OS_WIN32
0143           static constexpr char illegalChars[] = "<>:\"|?*\\/";
0144 #else
0145           // ':' and '\' are included in the set of illegal characters to
0146           // keep the old behavior when no string replacement is enabled.
0147           static constexpr char illegalChars[] = ":\\/";
0148 #endif
0149           Utils::replaceIllegalFileNameCharacters(repl, QLatin1String("-"),
0150                                                   illegalChars);
0151         }
0152         if (urlEncode) {
0153           repl = QString::fromLatin1(QUrl::toPercentEncoding(repl));
0154         }
0155         if (htmlEscape) {
0156           repl = escapeHtml(repl);
0157         }
0158         if (!repl.isEmpty()) {
0159           if (!prefix.isEmpty()) {
0160             repl = prefix + repl;
0161           }
0162           if (!postfix.isEmpty()) {
0163             repl += postfix;
0164           }
0165         }
0166         if (!repl.isNull() || codeLen > 2) {
0167           m_str.replace(pos, codeLen, repl);
0168           pos += repl.length();
0169         } else {
0170           ++pos;
0171         }
0172       } else {
0173         ++pos;
0174       }
0175     }
0176   }
0177 }
0178 
0179 /**
0180  * Converts the plain text string @a plain to a HTML string with
0181  * HTML metacharacters replaced by HTML entities.
0182  * @param plain plain text
0183  * @return html text with HTML entities.
0184  */
0185 QString FormatReplacer::escapeHtml(const QString& plain)
0186 {
0187   QString rich;
0188   rich.reserve(static_cast<int>(plain.length() * 1.1));
0189   for (int i = 0; i < plain.length(); ++i) {
0190     if (ushort ch = plain.at(i).unicode(); ch == '<')
0191       rich += QLatin1String("&lt;");
0192     else if (ch == '>')
0193       rich += QLatin1String("&gt;");
0194     else if (ch == '&')
0195       rich += QLatin1String("&amp;");
0196     else if (ch == '"')
0197       rich += QLatin1String("&quot;");
0198     else if (ch == '\'')
0199       rich += QLatin1String("&apos;");
0200     else if (ch >= 128)
0201       rich += QString(QLatin1String("&#%1;")).arg(ch);
0202     else
0203       rich += plain.at(i);
0204   }
0205   return rich;
0206 }