/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2012-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file CSVFormatter.h14/// @author Michael Behrisch15/// @date 2025-06-1216///17// Output formatter for CSV output18/****************************************************************************/19#pragma once20#include <config.h>2122#include <memory>23#ifdef HAVE_FMT24#include <fmt/ostream.h>25#endif2627#include "OutputFormatter.h"282930// ===========================================================================31// class definitions32// ===========================================================================33/**34* @class CSVFormatter35* @brief Output formatter for CSV output36*/37class CSVFormatter : public OutputFormatter {38public:39/// @brief Constructor40CSVFormatter(const std::string& columnNames, const char separator = ';');4142/// @brief Destructor43virtual ~CSVFormatter() { }4445/** @brief Keeps track of an open XML tag by adding a new element to the stack46*47* @param[in] into The output stream to use (unused)48* @param[in] xmlElement Name of element to open (unused)49* @return The OutputDevice for further processing50*/51void openTag(std::ostream& into, const std::string& xmlElement);5253/** @brief Keeps track of an open XML tag by adding a new element to the stack54*55* @param[in] into The output stream to use (unused)56* @param[in] xmlElement Name of element to open (unused)57*/58void openTag(std::ostream& into, const SumoXMLTag& xmlElement);5960/** @brief Closes the most recently opened tag61*62* @param[in] into The output stream to use63* @return Whether a further element existed in the stack and could be closed64* @todo it is not verified that the topmost element was closed65*/66bool closeTag(std::ostream& into, const std::string& comment = "");6768/** @brief writes a named attribute69*70* @param[in] into The output stream to use71* @param[in] attr The attribute (name)72* @param[in] val The attribute value73*/74template <class T>75void writeAttr(std::ostream& into, const SumoXMLAttr attr, const T& val) {76checkAttr(attr);77*myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;78}7980template <class T>81void writeAttr(std::ostream& into, const std::string& attr, const T& val) {82assert(!myCheckColumns);83if (!myWroteHeader) {84if (std::find(myHeader.begin(), myHeader.end(), attr) != myHeader.end()) {85myHeader.push_back(myCurrentTag + "_" + attr);86} else {87myHeader.push_back(attr);88}89}90*myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;91}9293void writeNull(std::ostream& /* into */, const SumoXMLAttr attr) {94checkAttr(attr);95*myXMLStack[myCurrentDepth - 1] << mySeparator;96}9798void writeTime(std::ostream& /* into */, const SumoXMLAttr attr, const SUMOTime val) {99checkAttr(attr);100*myXMLStack[myCurrentDepth - 1] << time2string(val) << mySeparator;101}102103bool wroteHeader() const {104return myWroteHeader;105}106107void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {108myExpectedAttrs = expected;109myMaxDepth = depth;110myCheckColumns = expected.any();111}112113private:114/** @brief Helper function to keep track of the written attributes and accumulate the header.115* It checks whether the written attribute is expected in the column based format.116* 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.117*118* @param[in] attr The attribute (name)119*/120inline void checkAttr(const SumoXMLAttr attr) {121if (myCheckColumns && myMaxDepth == myCurrentDepth) {122mySeenAttrs.set(attr);123if (!myExpectedAttrs.test(attr)) {124throw ProcessError(TLF("Unexpected attribute '%', this file format does not support CSV output yet.", toString(attr)));125}126}127if (!myWroteHeader) {128const std::string attrString = toString(attr);129if (myHeaderFormat == "plain" || (myHeaderFormat == "auto" && std::find(myHeader.begin(), myHeader.end(), attrString) == myHeader.end())) {130myHeader.push_back(attrString);131} else {132myHeader.push_back(myCurrentTag + "_" + attrString);133}134}135}136137/// @brief the format to use for the column names138const std::string myHeaderFormat;139140/// @brief The value separator141const char mySeparator;142143/// @brief the CSV header144std::vector<std::string> myHeader;145146/// @brief the currently read tag (only valid when generating the header)147std::string myCurrentTag;148149/// @brief The attributes to write for each begun xml element (excluding the root element)150std::vector<std::unique_ptr<std::ostringstream>> myXMLStack;151152/// @brief the maximum depth of the XML hierarchy (excluding the root element)153int myMaxDepth = 0;154155/// @brief the current depth of the XML hierarchy (excluding the root element)156int myCurrentDepth = 0;157158/// @brief whether the CSV header line has been written159bool myWroteHeader = false;160161/// @brief whether the columns should be checked for completeness162bool myCheckColumns = false;163164/// @brief which CSV columns are expected (just for checking completeness)165SumoXMLAttrMask myExpectedAttrs;166167/// @brief which CSV columns have been set (just for checking completeness)168SumoXMLAttrMask mySeenAttrs;169};170171172// ===========================================================================173// specialized template implementations (for speedup)174// ===========================================================================175template <>176inline void CSVFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const double& val) {177checkAttr(attr);178#ifdef HAVE_FMT179fmt::print(*myXMLStack[myCurrentDepth - 1], "{:.{}f}{}", val, into.precision(), mySeparator);180#else181*myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;182#endif183}184185186template <>187inline void CSVFormatter::writeAttr(std::ostream& /* into */, const SumoXMLAttr attr, const std::string& val) {188checkAttr(attr);189*myXMLStack[myCurrentDepth - 1] << val << mySeparator;190}191192193