LCOV - code coverage report
Current view: top level - src/utils/iodevices - CSVFormatter.h (source / functions) Coverage Total Hit
Test: lcov.info Lines: 94.2 % 52 49
Test Date: 2026-07-25 16:16:11 Functions: 29.5 % 88 26

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2012-2026 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    CSVFormatter.h
      15              : /// @author  Michael Behrisch
      16              : /// @date    2025-06-12
      17              : ///
      18              : // Output formatter for CSV output
      19              : /****************************************************************************/
      20              : #pragma once
      21              : #include <config.h>
      22              : 
      23              : #include <memory>
      24              : #include <algorithm>
      25              : #include "OutputFormatter.h"
      26              : #include <utils/common/ToString.h>
      27              : #include <utils/common/Translation.h>
      28              : 
      29              : 
      30              : // ===========================================================================
      31              : // class definitions
      32              : // ===========================================================================
      33              : /**
      34              :  * @class CSVFormatter
      35              :  * @brief Output formatter for CSV output
      36              :  */
      37              : class CSVFormatter : public OutputFormatter {
      38              : public:
      39              :     /// @brief Constructor
      40              :     CSVFormatter(const std::string& columnNames, const char separator = ';');
      41              : 
      42              :     /// @brief Destructor
      43           82 :     virtual ~CSVFormatter() override { }
      44              : 
      45              :     /** @brief Writes an "XML header"
      46              :      *
      47              :      * For CSV output the header is only relevant if it contains additional attributes.
      48              :      *
      49              :      * @param[in] into The output stream to use
      50              :      * @param[in] rootElement The root element to use
      51              :      * @param[in] attrs Additional attributes to save within the rootElement
      52              :      * @return whether something has been written
      53              :      */
      54              :     bool writeXMLHeader(std::ostream& into, const std::string& rootElement,
      55              :                         const std::map<SumoXMLAttr, std::string>& attrs, bool /* writeMetadata */,
      56              :                         bool /* includeConfig */) override;
      57              : 
      58              :     /** @brief Keeps track of an open XML tag by adding a new element to the stack
      59              :      *
      60              :      * @param[in] into The output stream to use (unused)
      61              :      * @param[in] xmlElement Name of the element to open
      62              :      */
      63              :     void openTag(std::ostream& into, const std::string& xmlElement) override;
      64              : 
      65              :     /** @brief Keeps track of an open XML tag by adding a new element to the stack
      66              :      *
      67              :      * @param[in] into The output stream to use (unused)
      68              :      * @param[in] xmlElement Enum identifier of the element to open
      69              :      */
      70              :     void openTag(std::ostream& into, const SumoXMLTag& xmlElement) override;
      71              : 
      72              :     /** @brief Closes the most recently opened tag
      73              :      *
      74              :      * This is where the main action starts. This function determines whether a row is completed and can be written.
      75              :      *
      76              :      * @param[in] into The output stream to use
      77              :      * @param[in] comment A comment to write after the tag (ignored for CSV)
      78              :      * @return Whether a further element existed in the stack and could be closed
      79              :      */
      80              :     bool closeTag(std::ostream& into, const std::string& comment = "") override;
      81              : 
      82              :     /** @brief Writes a named attribute
      83              :      *
      84              :      * This calls checkAttr to check/add this to the known attributes and then adds the string conversion to myValues.
      85              :      *
      86              :      * @param[in] into The output stream to use (used only to determine precision)
      87              :      * @param[in] attr The attribute (name as enum value)
      88              :      * @param[in] val The attribute value
      89              :      * @param[in] isNull whether this is actually a null value (writes the empty string)
      90              :      * @param[in] escape Whether the value should be processed by StringUtils::escapeCSV (only used in the string variant below)
      91              :      */
      92              :     template <class T>
      93       102329 :     void writeAttr(std::ostream& into, const SumoXMLAttr attr, const T& val, const bool isNull, const bool escape) {
      94              :         UNUSED_PARAMETER(escape);
      95       102329 :         checkAttr(attr);
      96       102664 :         myValues.emplace_back(isNull ? "" : toString(val, into.precision()));
      97       102329 :     }
      98              : 
      99              :     /** @brief Writes a named attribute
     100              :      *
     101              :      * This calls checkHeader to check/add this to the known attributes and then adds the string conversion to myValues.
     102              :      *
     103              :      * @param[in] into The output stream to use (used only to determine precision)
     104              :      * @param[in] attr The attribute (name as string)
     105              :      * @param[in] val The attribute value
     106              :      * @param[in] isNull whether this is actually a null value (writes the empty string)
     107              :      * @param[in] escape Whether the value should be processed by StringUtils::escapeCSV (only used in the string variant below)
     108              :      */
     109              :     template <class T>
     110        32196 :     void writeAttr(std::ostream& into, const std::string& attr, const T& val, const bool isNull, const bool escape) {
     111              :         UNUSED_PARAMETER(escape);
     112              :         assert(!myCheckColumns);
     113        32196 :         checkHeader(attr);
     114        32196 :         myValues.emplace_back(isNull ? "" : toString(val, into.precision()));
     115        32196 :     }
     116              : 
     117              :     /// @brief typed overloads (non-template) -- picked by overload resolution over the template
     118        68394 :     void writeAttr(std::ostream& /* into */, const SumoXMLAttr attr, const std::string& val, const bool isNull, const bool escape) {
     119        68394 :         checkAttr(attr);
     120        99019 :         myValues.emplace_back(isNull ? "" : (escape ? StringUtils::escapeCSV(val, mySeparator) : val));
     121        68394 :     }
     122        20980 :     void writeAttr(std::ostream& /* into */, const std::string& attr, const std::string& val, const bool isNull, const bool escape) {
     123              :         assert(!myCheckColumns);
     124        20980 :         checkHeader(attr);
     125        41528 :         myValues.emplace_back(isNull ? "" : (escape ? StringUtils::escapeCSV(val, mySeparator) : val));
     126        20980 :     }
     127              : 
     128              :     /** @brief Writes a time value using time2string
     129              :      *
     130              :      * @param[in] into The output stream to use (unused)
     131              :      * @param[in] attr The attribute (name as enum value)
     132              :      * @param[in] val The attribute value
     133              :      */
     134         1120 :     void writeTime(std::ostream& /* into */, const SumoXMLAttr attr, const SUMOTime val) override {
     135         1120 :         checkAttr(attr);
     136         1120 :         myValues.emplace_back(time2string(val));
     137         1120 :     }
     138              : 
     139              :     /** @brief Whether a complete row has been encountered and triggered header writing
     140              :      *
     141              :      * @return Whether the CSV header has been written
     142              :      */
     143            0 :     bool wroteHeader() const override {
     144            0 :         return myWroteHeader;
     145              :     }
     146              : 
     147              :     /** @brief Which elements are expected and which maximum depth the XML tree has.
     148              :      *
     149              :      * This is not necessary for the functionality but very useful for debugging and tracking whether expected attributes
     150              :      * are still missing (triggers an error in checkAttr). If expected is empty, no tracking takes place.
     151              :      *
     152              :      * The depth parameter is only for performance. If a tag at this depth is closed for the first time,
     153              :      * the header is being written and buffered rows may be flushed. Setting it to a large value (which is also the default)
     154              :      * means you are on the safe side if more attributes or tags show up later but it may result in buffering the complete
     155              :      * output before writing the first line.
     156              :      * Setting it to 0 triggers auto detection which means the first time a tag is closed the maximum depth will be determined.
     157              :      *
     158              :      * @param[in] expected The enum values of the attrs which should be present before a row can be written.
     159              :      * @param[in] depth The maximum expected depth of nested XML elements.
     160              :      */
     161           28 :     void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth) override {
     162           28 :         myExpectedAttrs = expected;
     163           28 :         myMaxDepth = depth;
     164           28 :         myCheckColumns = expected.any();
     165           28 :     }
     166              : 
     167              : private:
     168              :     /** @brief Helper function to keep track of the written attributes and accumulate the header.
     169              :      * It checks whether the written attribute is expected in the column based format.
     170              :      * The check does only apply to the deepest level of the XML hierarchy and not to the order of the columns just to the presence.
     171              :      *
     172              :      * @param[in] attr The attribute (name as enum value)
     173              :      */
     174       171843 :     inline void checkAttr(const SumoXMLAttr attr) {
     175       171843 :         if (myCheckColumns && myMaxDepth == (int)myXMLStack.size()) {
     176         8100 :             mySeenAttrs.set(attr);
     177         8100 :             if (!myExpectedAttrs.test(attr)) {
     178            0 :                 throw ProcessError(TLF("Unexpected attribute '%', this file format does not support CSV output yet.", toString(attr)));
     179              :             }
     180              :         }
     181       171843 :         checkHeader(attr);
     182       171843 :     }
     183              : 
     184              :     template <class ATTR_TYPE>
     185       225019 :     inline void checkHeader(const ATTR_TYPE& attr) {
     186       225019 :         myNeedsWrite = true;
     187       225019 :         if (!myWroteHeader) {
     188       157765 :             std::string headerName = toString(attr);
     189              :             std::string prefix;
     190       607353 :             for (const auto& entry : myXMLStack) {
     191       798860 :                 prefix += entry.first + "_";
     192              :             }
     193              :             const std::string fullHeaderName = prefix + headerName;
     194       207923 :             if (myHeaderFormat != "plain") {
     195       623769 :                 headerName = myXMLStack.back().first + "_" + headerName;
     196              :             }
     197       207923 :             const auto colIt = std::find(myFullHeader.begin(), myFullHeader.end(), fullHeaderName);
     198       207923 :             if (colIt == myFullHeader.end()) {
     199        30515 :                 for (std::string& row : myBufferedRows) {
     200        29822 :                     row += mySeparator;
     201              :                 }
     202          693 :                 myValues.resize(myHeader.size());
     203          693 :                 myHeader.emplace_back(headerName);
     204          693 :                 myFullHeader.emplace_back(fullHeaderName);
     205              :             } else {
     206              :                 // there might be missing attributes inbetween, so make sure header position and value size match
     207       207230 :                 myValues.resize(std::distance(myFullHeader.begin(), colIt));
     208              :             }
     209              :         }
     210       225019 :     }
     211              : 
     212              :     /// @brief the format to use for the column names
     213              :     const std::string myHeaderFormat;
     214              : 
     215              :     /// @brief The value separator
     216              :     const char mySeparator;
     217              : 
     218              :     /// @brief the CSV header
     219              :     std::vector<std::string> myHeader;
     220              : 
     221              :     /// @brief the CSV header if we write the full name
     222              :     std::vector<std::string> myFullHeader;
     223              : 
     224              :     /// @brief The name and number of attributes in the currently open XML elements
     225              :     std::vector<std::pair<const std::string, int> > myXMLStack;
     226              : 
     227              :     /// @brief the current attribute / column values
     228              :     std::vector<std::string> myValues;
     229              : 
     230              :     /// @brief the maximum depth of the XML hierarchy (excluding the root element)
     231              :     int myMaxDepth = 1000;
     232              : 
     233              :     /// @brief whether the CSV header line has been written
     234              :     bool myWroteHeader = false;
     235              : 
     236              :     /// @brief whether any attribute has been written since the last row was emitted
     237              :     bool myNeedsWrite = false;
     238              : 
     239              :     /// @brief whether any root attribute have been encountered
     240              :     bool myHaveRootAttrs = false;
     241              : 
     242              :     /// @brief partial rows buffered before the schema is known (depth < myMaxDepth)
     243              :     std::vector<std::string> myBufferedRows;
     244              : 
     245              :     /// @brief whether the columns should be checked for completeness
     246              :     bool myCheckColumns = false;
     247              : 
     248              :     /// @brief which CSV columns are expected (just for checking completeness)
     249              :     SumoXMLAttrMask myExpectedAttrs;
     250              : 
     251              :     /// @brief which CSV columns have been set (just for checking completeness)
     252              :     SumoXMLAttrMask mySeenAttrs;
     253              : };
        

Generated by: LCOV version 2.0-1