LCOV - code coverage report
Current view: top level - jdx - NTuples.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 152 159 95.6 %
Date: 2025-06-15 16:18:27 Functions: 17 17 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 "jdx/NTuples.hpp"
      19             : #include "jdx/ParseException.hpp"
      20             : #include "util/LdrUtils.hpp"
      21             : #include "util/StringUtils.hpp"
      22             : 
      23             : #include <utility>
      24             : 
      25          46 : libjdx::jdx::NTuples::NTuples(const std::string& label, std::string dataForm,
      26             :     const std::vector<StringLdr>& blockLdrs, io::TextReader& reader,
      27          46 :     std::optional<std::string>& nextLine)
      28          70 :     : m_dataForm{std::move(dataForm)}
      29             : {
      30          46 :     validateInput(label);
      31          46 :     parse(blockLdrs, reader, nextLine);
      32          40 : }
      33             : 
      34          13 : std::string libjdx::jdx::NTuples::getDataForm() const
      35             : {
      36          13 :     return m_dataForm;
      37             : }
      38             : 
      39          18 : const std::vector<libjdx::jdx::StringLdr>& libjdx::jdx::NTuples::getLdrs() const
      40             : {
      41          18 :     return m_ldrs;
      42             : }
      43             : 
      44             : const std::vector<libjdx::jdx::NTuplesAttributes>&
      45          16 : libjdx::jdx::NTuples::getAttributes() const
      46             : {
      47          16 :     return m_attributes;
      48             : }
      49             : 
      50          11 : size_t libjdx::jdx::NTuples::getNumPages() const
      51             : {
      52          11 :     return m_pages.size();
      53             : }
      54             : 
      55          14 : const libjdx::jdx::Page& libjdx::jdx::NTuples::getPage(size_t pageIndex) const
      56             : {
      57          14 :     return m_pages.at(pageIndex);
      58             : }
      59             : 
      60          46 : void libjdx::jdx::NTuples::validateInput(const std::string& label)
      61             : {
      62          46 :     if (label != s_label)
      63             :     {
      64           0 :         throw ParseException("Illegal label at " + std::string{s_label}
      65           0 :                              + " start encountered: " + label);
      66             :     }
      67          46 : }
      68             : 
      69          46 : void libjdx::jdx::NTuples::parse(const std::vector<StringLdr>& blockLdrs,
      70             :     io::TextReader& reader, std::optional<std::string>& nextLine)
      71             : {
      72             :     // skip potential comment lines
      73         138 :     nextLine = reader.eof() ? std::nullopt
      74         138 :                             : std::optional<std::string>{reader.readLine()};
      75          46 :     util::skipPureComments(reader, nextLine, true);
      76             :     // parse PAGE parameters
      77          46 :     m_attributes = parseAttributes(reader, nextLine);
      78             :     // parse pages
      79         112 :     while (nextLine.has_value() && util::isLdrStart(nextLine.value()))
      80             :     {
      81         114 :         auto [label, pageVar] = util::parseLdrStart(nextLine.value());
      82         111 :         pageVar = util::stripLineComment(pageVar, true).first;
      83         111 :         if (label == "ENDNTUPLES")
      84             :         {
      85             :             // ##END NTUPLES is described as optional in JCAMP6_2b Draft
      86             :             // but is required for indicating the NTUPLES end
      87             : 
      88             :             // skip ##END NTUPLES
      89          40 :             nextLine = reader.eof()
      90         120 :                            ? std::nullopt
      91         120 :                            : std::optional<std::string>{reader.readLine()};
      92          40 :             return;
      93             :         }
      94          71 :         if (label != "PAGE")
      95             :         {
      96             :             throw ParseException("Unexpected content found in NTUPLES record: "
      97           0 :                                  + nextLine.value());
      98             :         }
      99         212 :         nextLine = reader.eof() ? std::nullopt
     100         212 :                                 : std::optional<std::string>{reader.readLine()};
     101             :         auto page
     102         142 :             = Page(label, pageVar, m_attributes, blockLdrs, reader, nextLine);
     103          68 :         m_pages.push_back(std::move(page));
     104             :     }
     105           1 :     if (!nextLine.has_value())
     106             :     {
     107           1 :         throw ParseException("Unexpected end of NTUPLES record: " + m_dataForm);
     108             :     }
     109             : }
     110             : 
     111             : std::vector<libjdx::jdx::NTuplesAttributes>
     112          46 : libjdx::jdx::NTuples::parseAttributes(
     113             :     io::TextReader& reader, std::optional<std::string>& nextLine)
     114             : {
     115          46 :     m_ldrs = readLdrs(nextLine, reader);
     116          91 :     auto attrMap = splitValues(m_ldrs);
     117          90 :     auto standardAttrMap = extractStandardAttributes(attrMap);
     118             : 
     119         135 :     auto attrNames = findValue("VARNAME", standardAttrMap);
     120          45 :     if (!attrNames)
     121             :     {
     122             :         // VARNAMEs are required by the spec
     123             :         throw ParseException(
     124           1 :             "No \"VAR_NAME\" LDR found in NTUPLES: " + m_dataForm);
     125             :     }
     126          44 :     if (!attrNames.value().empty())
     127             :     {
     128             :         // check if last VAR_NAME is blank, i.e., there is a trailing comma
     129             :         // if so, remove, thus ignore column in subsequent processing
     130             :         // required to sucessfully process test data set
     131          88 :         auto lastVarName = attrNames.value().back();
     132          44 :         util::trim(lastVarName);
     133          44 :         if (lastVarName.empty())
     134             :         {
     135          12 :             attrNames.value().pop_back();
     136             :         }
     137             :     }
     138          44 :     auto numAttrs = attrNames.value().size();
     139             : 
     140          44 :     std::vector<NTuplesAttributes> output;
     141         198 :     for (size_t i = 0; i < numAttrs; ++i)
     142             :     {
     143         308 :         auto ntv = map(standardAttrMap, attrMap, i);
     144         154 :         output.push_back(ntv);
     145             :     }
     146          88 :     return output;
     147             : }
     148             : 
     149          46 : std::vector<libjdx::jdx::StringLdr> libjdx::jdx::NTuples::readLdrs(
     150             :     std::optional<std::string>& firstLdrStart, io::TextReader& reader)
     151             : {
     152          46 :     std::optional<std::string>& nextLine = firstLdrStart;
     153          46 :     std::vector<StringLdr> output;
     154         489 :     while (nextLine)
     155             :     {
     156         488 :         auto [title, value] = util::parseLdrStart(nextLine.value());
     157         488 :         if (title == "PAGE" || title == "ENDNTUPLES" || title == "END")
     158             :         {
     159             :             // all NTUPLES LDRs read
     160          45 :             break;
     161             :         }
     162         443 :         nextLine = parseStringValue(value, reader);
     163         443 :         output.emplace_back(title, value);
     164             :     }
     165          46 :     return output;
     166             : }
     167             : 
     168             : std::map<std::string, std::vector<std::string>>
     169          46 : libjdx::jdx::NTuples::splitValues(const std::vector<StringLdr>& ldrs)
     170             : {
     171          46 :     std::map<std::string, std::vector<std::string>> output;
     172         484 :     for (const auto& ldr : ldrs)
     173             :     {
     174         878 :         auto valueString = util::stripLineComment(ldr.getValue(), true).first;
     175        1317 :         auto values = util::split(valueString, ",", true);
     176         439 :         auto inserted = output.emplace(ldr.getLabel(), values).second;
     177         439 :         if (!inserted)
     178             :         {
     179             :             throw ParseException(
     180           1 :                 "Duplicate LDR found in NTUPLE: " + ldr.getLabel());
     181             :         }
     182             :     }
     183          45 :     return output;
     184             : }
     185             : 
     186             : std::map<std::string, std::vector<std::string>>
     187          45 : libjdx::jdx::NTuples::extractStandardAttributes(
     188             :     std::map<std::string, std::vector<std::string>>& attributes)
     189             : {
     190             :     // see:
     191             :     // https://stackoverflow.com/questions/180516/how-to-filter-items-from-a-stdmap
     192         436 :     auto matches = [](const std::string& attr) {
     193         436 :         return std::find(std::begin(s_standardAttrNames),
     194             :                    std::end(s_standardAttrNames), attr)
     195         436 :                != std::end(s_standardAttrNames);
     196             :     };
     197          45 :     std::map<std::string, std::vector<std::string>> standardAttrs;
     198             :     // remove standard attributes
     199         481 :     for (auto it = attributes.begin(); it != attributes.end();)
     200             :     {
     201         436 :         if (matches((*it).first))
     202             :         {
     203         394 :             standardAttrs.insert(*it);
     204         394 :             attributes.erase(it++);
     205             :         }
     206             :         else
     207             :         {
     208          42 :             ++it;
     209             :         }
     210             :     }
     211          90 :     return standardAttrs;
     212             : }
     213             : 
     214         154 : libjdx::jdx::NTuplesAttributes libjdx::jdx::NTuples::map(
     215             :     const std::map<std::string, std::vector<std::string>>& standardAttributes,
     216             :     const std::map<std::string, std::vector<std::string>>& additionalAttributes,
     217             :     size_t valueColumnIndex)
     218             : {
     219             :     auto pickColumnValue
     220        1573 :         = [valueColumnIndex](
     221        4701 :               const std::optional<std::vector<std::string>>& values) {
     222        1573 :               if (values.value().size() <= valueColumnIndex)
     223             :               {
     224           9 :                   return std::optional<std::string>{std::nullopt};
     225             :               }
     226        3128 :               auto value = values.value().at(valueColumnIndex);
     227        1564 :               util::trim(value);
     228             :               return std::optional<std::string>{
     229        1564 :                   values.value().at(valueColumnIndex)};
     230         154 :           };
     231             : 
     232             :     auto findColumnValue
     233        1694 :         = [&pickColumnValue, &standardAttributes = std::as_const(
     234        3255 :                                  standardAttributes)](const std::string& key) {
     235        3388 :               auto values = findValue(key, standardAttributes);
     236        1694 :               if (!values)
     237             :               {
     238         287 :                   return std::optional<std::string>{std::nullopt};
     239             :               }
     240        1407 :               return pickColumnValue(values);
     241         154 :           };
     242             : 
     243         462 :     auto varNameString = findColumnValue("VARNAME");
     244         154 :     if (!varNameString)
     245             :     {
     246             :         // VARNAMEs are required by the spec
     247             :         throw ParseException(
     248           0 :             R"(No "VAR_NAME" LDR found in NTUPLES ")" + m_dataForm
     249           0 :             + "\" column: " + std::to_string(valueColumnIndex));
     250             :     }
     251         462 :     auto symbolString = findColumnValue("SYMBOL");
     252         462 :     auto varTypeString = findColumnValue("VARTYPE");
     253         462 :     auto varFormString = findColumnValue("VARFORM");
     254         462 :     auto varDimString = findColumnValue("VARDIM");
     255         462 :     auto unitsString = findColumnValue("UNITS");
     256         462 :     auto firstString = findColumnValue("FIRST");
     257         462 :     auto lastString = findColumnValue("LAST");
     258         462 :     auto minString = findColumnValue("MIN");
     259         462 :     auto maxString = findColumnValue("MAX");
     260         462 :     auto factorString = findColumnValue("FACTOR");
     261             : 
     262             :     // TODO: make stol/stod more reliable (new util function) and use across
     263             :     // project
     264         154 :     if (!varNameString)
     265             :     {
     266             :         throw ParseException("VAR_NAME missing in NTUPLES column: "
     267           0 :                              + std::to_string(valueColumnIndex));
     268             :     }
     269         154 :     if (!symbolString)
     270             :     {
     271             :         throw ParseException("SYMBOL missing in NTUPLES column: "
     272           0 :                              + std::to_string(valueColumnIndex));
     273             :     }
     274         308 :     auto varName = varNameString.value();
     275         308 :     auto symbol = symbolString.value();
     276         154 :     auto& varType = varTypeString;
     277         154 :     auto& varForm = varFormString;
     278             : 
     279         296 :     auto varDim = varDimString && !varDimString.value().empty()
     280         270 :                       ? std::optional<uint64_t>(std::stol(varDimString.value()))
     281         154 :                       : std::nullopt;
     282         154 :     auto& units = unitsString;
     283         283 :     auto first = firstString && !firstString.value().empty()
     284         255 :                      ? std::optional<double>(std::stod(firstString.value()))
     285         154 :                      : std::nullopt;
     286         283 :     auto last = lastString && !lastString.value().empty()
     287         255 :                     ? std::optional<double>(std::stod(lastString.value()))
     288         154 :                     : std::nullopt;
     289         238 :     auto min = minString && !minString.value().empty()
     290         238 :                    ? std::optional<double>(std::stod(minString.value()))
     291         154 :                    : std::nullopt;
     292         238 :     auto max = maxString && !maxString.value().empty()
     293         238 :                    ? std::optional<double>(std::stod(maxString.value()))
     294         154 :                    : std::nullopt;
     295         245 :     auto factor = factorString && !factorString.value().empty()
     296         245 :                       ? std::optional<double>(std::stod(factorString.value()))
     297         154 :                       : std::nullopt;
     298             : 
     299         308 :     std::vector<StringLdr> additionalAttrsInColum;
     300         320 :     for (const auto& var : additionalAttributes)
     301             :     {
     302         332 :         auto value = pickColumnValue(var.second);
     303         166 :         if (value)
     304             :         {
     305         161 :             additionalAttrsInColum.emplace_back(var.first, value.value());
     306             :         }
     307             :     }
     308             : 
     309             :     NTuplesAttributes nTupleAttrs{
     310             :         varName,
     311             :         symbol,
     312             :         varType,
     313             :         varForm,
     314             :         varDim,
     315             :         units,
     316             :         first,
     317             :         last,
     318             :         min,
     319             :         max,
     320             :         factor,
     321             :         additionalAttrsInColum,
     322         154 :     };
     323             : 
     324         308 :     return nTupleAttrs;
     325             : }
     326             : 
     327        1739 : std::optional<std::vector<std::string>> libjdx::jdx::NTuples::findValue(
     328             :     const std::string& key,
     329             :     const std::map<std::string, std::vector<std::string>>& map)
     330             : {
     331        1739 :     const auto it = map.find(key);
     332        1739 :     if (it != map.end())
     333             :     {
     334        1451 :         return std::optional<std::vector<std::string>>{(*it).second};
     335             :     }
     336         288 :     return std::optional<std::vector<std::string>>{std::nullopt};
     337             : }

Generated by: LCOV version 1.14