LCOV - code coverage report
Current view: top level - jdx/util - LdrUtils.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 99 102 97.1 %
Date: 2025-06-15 16:18:27 Functions: 14 14 100.0 %

          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             : }

Generated by: LCOV version 1.14