| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file Copyright.txt or https://cmake.org/licensing for details. */ | |
| #include "cmStringReplaceHelper.h" | |
| #include <sstream> | |
| #include <utility> | |
| #include "cmMakefile.h" | |
| cmStringReplaceHelper::cmStringReplaceHelper(const std::string& regex, | |
| std::string replace_expr, | |
| cmMakefile* makefile) | |
| : RegExString(regex) | |
| , RegularExpression(regex) | |
| , ReplaceExpression(std::move(replace_expr)) | |
| , Makefile(makefile) | |
| { | |
| this->ParseReplaceExpression(); | |
| } | |
| bool cmStringReplaceHelper::Replace(const std::string& input, | |
| std::string& output) | |
| { | |
| output.clear(); | |
| // Scan through the input for all matches. | |
| std::string::size_type base = 0; | |
| while (this->RegularExpression.find(input.c_str() + base)) { | |
| if (this->Makefile != nullptr) { | |
| this->Makefile->ClearMatches(); | |
| this->Makefile->StoreMatches(this->RegularExpression); | |
| } | |
| auto l2 = this->RegularExpression.start(); | |
| auto r = this->RegularExpression.end(); | |
| // Concatenate the part of the input that was not matched. | |
| output += input.substr(base, l2); | |
| // Make sure the match had some text. | |
| if (r - l2 == 0) { | |
| std::ostringstream error; | |
| error << "regex \"" << this->RegExString << "\" matched an empty string"; | |
| this->ErrorString = error.str(); | |
| return false; | |
| } | |
| // Concatenate the replacement for the match. | |
| for (const auto& replacement : this->Replacements) { | |
| if (replacement.Number < 0) { | |
| // This is just a plain-text part of the replacement. | |
| output += replacement.Value; | |
| } else { | |
| // Replace with part of the match. | |
| auto n = replacement.Number; | |
| auto start = this->RegularExpression.start(n); | |
| auto end = this->RegularExpression.end(n); | |
| auto len = input.length() - base; | |
| if ((start != std::string::npos) && (end != std::string::npos) && | |
| (start <= len) && (end <= len)) { | |
| output += input.substr(base + start, end - start); | |
| } else { | |
| std::ostringstream error; | |
| error << "replace expression \"" << this->ReplaceExpression | |
| << "\" contains an out-of-range escape for regex \"" | |
| << this->RegExString << "\""; | |
| this->ErrorString = error.str(); | |
| return false; | |
| } | |
| } | |
| } | |
| // Move past the match. | |
| base += r; | |
| } | |
| // Concatenate the text after the last match. | |
| output += input.substr(base, input.length() - base); | |
| return true; | |
| } | |
| void cmStringReplaceHelper::ParseReplaceExpression() | |
| { | |
| std::string::size_type l = 0; | |
| while (l < this->ReplaceExpression.length()) { | |
| auto r = this->ReplaceExpression.find('\\', l); | |
| if (r == std::string::npos) { | |
| r = this->ReplaceExpression.length(); | |
| this->Replacements.emplace_back( | |
| this->ReplaceExpression.substr(l, r - l)); | |
| } else { | |
| if (r - l > 0) { | |
| this->Replacements.emplace_back( | |
| this->ReplaceExpression.substr(l, r - l)); | |
| } | |
| if (r == (this->ReplaceExpression.length() - 1)) { | |
| this->ValidReplaceExpression = false; | |
| this->ErrorString = "replace-expression ends in a backslash"; | |
| return; | |
| } | |
| if ((this->ReplaceExpression[r + 1] >= '0') && | |
| (this->ReplaceExpression[r + 1] <= '9')) { | |
| this->Replacements.emplace_back(this->ReplaceExpression[r + 1] - '0'); | |
| } else if (this->ReplaceExpression[r + 1] == 'n') { | |
| this->Replacements.emplace_back("\n"); | |
| } else if (this->ReplaceExpression[r + 1] == '\\') { | |
| this->Replacements.emplace_back("\\"); | |
| } else { | |
| this->ValidReplaceExpression = false; | |
| std::ostringstream error; | |
| error << "Unknown escape \"" << this->ReplaceExpression.substr(r, 2) | |
| << "\" in replace-expression"; | |
| this->ErrorString = error.str(); | |
| return; | |
| } | |
| r += 2; | |
| } | |
| l = r; | |
| } | |
| } |