Line data Source code
1 : /* Copyright (C) 2025 Robert Schiwon 2 : * 3 : * This file is part of libjdx. 4 : * 5 : * libjdx is free software: you can redistribute it and/or modify it under the 6 : * terms of the GNU Lesser General Public License as published by the Free 7 : * Software Foundation, either version 3 of the License, or (at your option) any 8 : * later version. 9 : * 10 : * libjdx is distributed in the hope that it will be useful, but WITHOUT ANY 11 : * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 : * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 : * details. 14 : * 15 : * You should have received a copy of the GNU Lesser General Public License 16 : * along with libjdx. If not, see <https://www.gnu.org/licenses/>. 17 : */ 18 : #include "util/LdrUtils.hpp" 19 : #include "jdx/ParseException.hpp" 20 : #include "util/StringUtils.hpp" 21 : 22 : #include <algorithm> 23 : #include <regex> 24 : #include <string> 25 : #include <utility> 26 : 27 3084 : bool libjdx::jdx::util::isLdrStart(const std::string& line) 28 : { 29 3084 : static const std::regex regex{"\\s*##.*=.*"}; 30 : // match_continuous => match at start only 31 3084 : return std::regex_search( 32 3084 : line, regex, std::regex_constants::match_continuous); 33 : } 34 : 35 2331 : std::string libjdx::jdx::util::normalizeLdrStart(const std::string& ldr) 36 : { 37 2331 : std::string output{}; 38 2331 : auto it = ldr.cbegin(); 39 : // skip leading white spaces 40 2345 : for (; it != ldr.cend(); ++it) 41 : { 42 2345 : if (!isSpace(*it)) 43 : { 44 2331 : break; 45 : } 46 : } 47 : // check and skip "##" marking start of LDR 48 6991 : for (auto i{0}; i < 2; i++) 49 : { 50 4662 : if (it == ldr.cend() || *it != '#') 51 : { 52 : throw ParseException( 53 2 : "Malformed LDR start, missing double hashes: " + ldr); 54 : } 55 4660 : output += *it++; 56 : } 57 : // normalize label 58 4658 : std::string label{}; 59 17611 : while (it != ldr.cend() && *it != '=') 60 : { 61 15282 : label += *it++; 62 : } 63 2329 : output.append(normalizeLdrLabel(label)); 64 : // add remaining string content 65 2329 : if (it == ldr.cend() || *it != '=') 66 : { 67 1 : throw ParseException("Malformed LDR start, missing equals: " + ldr); 68 : } 69 2328 : output.append(it, ldr.end()); 70 4656 : return output; 71 : } 72 : 73 4426 : std::string libjdx::jdx::util::normalizeLdrLabel(const std::string& label) 74 : { 75 4426 : std::string output{}; 76 : // normalize LDR label, i.e. the string between ## and = 77 27607 : auto makeUpperCase = [](const unsigned char c) { return std::toupper(c); }; 78 32846 : for (const char c : label) 79 : { 80 28420 : if (c == ' ' || c == '-' || c == '/' || c == '_') 81 : { 82 : // discard 83 813 : continue; 84 : } 85 : output 86 27607 : += static_cast<char>(makeUpperCase(static_cast<unsigned char>(c))); 87 : } 88 8852 : return output; 89 : } 90 : 91 2326 : std::pair<std::string, std::string> libjdx::jdx::util::parseLdrStart( 92 : const std::string& ldrStart) 93 : { 94 2326 : size_t posEquals = ldrStart.find('='); 95 2326 : if (std::string::npos == posEquals) 96 : { 97 : throw ParseException( 98 1 : "Malformed LDR start, missing equals: " + ldrStart); 99 : } 100 4650 : std::string label = ldrStart.substr(0, posEquals + 1); 101 4649 : std::string normalizedLabel = normalizeLdrStart(label); 102 4648 : if (normalizedLabel.size() < 3 || normalizedLabel.at(0) != '#' 103 2324 : || normalizedLabel.at(1) != '#' 104 4648 : || normalizedLabel.at(normalizedLabel.size() - 1) != '=') 105 : { 106 : throw ParseException( 107 : "Malformed LDR start, normalization yields illegal label: " 108 0 : + normalizedLabel); 109 : } 110 : // strip leading and trailing symbols from label 111 2324 : normalizedLabel.erase(0, 2); 112 2324 : normalizedLabel.erase(normalizedLabel.size() - 1); 113 : 114 4648 : std::string value = ldrStart.substr(posEquals + 1); 115 2324 : if (!value.empty() && value.at(0) == ' ') 116 : { 117 : // strip leading blank from value if present 118 2053 : value.erase(0, 1); 119 : } 120 : 121 4648 : return std::make_pair(normalizedLabel, value); 122 : } 123 : 124 : std::pair<std::string, std::optional<std::string>> 125 3081 : libjdx::jdx::util::stripLineComment( 126 : const std::string& line, bool trimContent, bool trimComment) 127 : { 128 3081 : const auto pos = line.find("$$"); 129 3081 : if (pos == std::string::npos) 130 : { 131 : // no comment 132 2822 : if (!trimContent) 133 : { 134 22 : return make_pair(line, std::nullopt); 135 : } 136 2800 : auto trimmedLine{line}; 137 2800 : util::trim(trimmedLine); 138 2800 : return make_pair(trimmedLine, std::nullopt); 139 : } 140 : 141 : // separate comment 142 518 : auto content = line.substr(0, pos); 143 259 : auto comment = line.substr(pos + 2); 144 259 : if (trimContent) 145 : { 146 195 : util::trim(content); 147 : } 148 259 : if (trimComment) 149 : { 150 61 : util::trim(comment); 151 : } 152 518 : return std::make_pair(content, comment); 153 : } 154 : 155 2097 : std::optional<const libjdx::jdx::StringLdr> libjdx::jdx::util::findLdr( 156 : const std::vector<StringLdr>& ldrs, const std::string& label) 157 : { 158 4194 : std::string normalizedLabel = normalizeLdrLabel(label); 159 : auto it = std::find_if( 160 12286 : ldrs.begin(), ldrs.end(), [&normalizedLabel](const StringLdr& ldr) { 161 12286 : return ldr.getLabel() == normalizedLabel; 162 2097 : }); 163 : 164 2097 : if (it != ldrs.cend()) 165 : { 166 710 : return *it; 167 : } 168 1387 : return std::nullopt; 169 : } 170 : 171 938 : std::optional<std::string> libjdx::jdx::util::findLdrValue( 172 : const std::vector<StringLdr>& ldrs, const std::string& label) 173 : { 174 1876 : auto ldr = util::findLdr(ldrs, label); 175 1454 : return ldr.has_value() ? std::optional<std::string>(ldr.value().getValue()) 176 2392 : : std::optional<std::string>(std::nullopt); 177 : } 178 : 179 211 : void libjdx::jdx::util::skipToNextLdr(io::TextReader& reader, 180 : std::optional<std::string>& nextLine, bool forceSkipFirstLine) 181 : { 182 211 : if (forceSkipFirstLine) 183 : { 184 193 : if (reader.eof()) 185 : { 186 0 : nextLine = std::nullopt; 187 : } 188 : else 189 : { 190 193 : nextLine = reader.readLine(); 191 : } 192 : } 193 828 : while (nextLine.has_value() && !util::isLdrStart(nextLine.value())) 194 : { 195 617 : if (reader.eof()) 196 : { 197 1 : nextLine = std::nullopt; 198 1 : continue; 199 : } 200 616 : nextLine = reader.readLine(); 201 : } 202 211 : } 203 : 204 119 : void libjdx::jdx::util::skipPureComments(io::TextReader& reader, 205 : std::optional<std::string>& nextLine, bool mustPrecedeLdr) 206 : { 207 119 : while (nextLine) 208 : { 209 118 : if (util::isPureComment(nextLine.value())) 210 : { 211 1 : nextLine = reader.eof() 212 3 : ? std::nullopt 213 3 : : std::optional<std::string>{reader.readLine()}; 214 1 : continue; 215 : } 216 117 : if (mustPrecedeLdr && !util::isLdrStart(nextLine.value())) 217 : { 218 : // pure $$ comment lines must be followed by LDR start 219 : // if not this special case, give up 220 : throw ParseException( 221 : "Unexpected content found instead of pure comment ($$): " 222 0 : + nextLine.value()); 223 : } 224 117 : break; 225 : } 226 118 : } 227 : 228 1448 : bool libjdx::jdx::util::isPureComment(const std::string& line) 229 : { 230 : // only $$ comment? 231 2896 : auto preCommentValue = util::stripLineComment(line, true).first; 232 2896 : return preCommentValue.empty(); 233 : } 234 : 235 284 : bool libjdx::jdx::util::isBrukerSpecificSectionStart(const std::string& line) 236 : { 237 284 : return line.rfind("$$ Bruker specific parameters", 0) == 0; 238 : } 239 : 240 116 : bool libjdx::jdx::util::isBrukerSpecificSectionEnd(const std::string& line) 241 : { 242 116 : return line.rfind("$$ End of Bruker specific parameters", 0) == 0; 243 : }