Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2012-2025 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 : #ifdef HAVE_FMT
25 : #include <fmt/ostream.h>
26 : #endif
27 :
28 : #include "OutputFormatter.h"
29 :
30 :
31 : // ===========================================================================
32 : // class definitions
33 : // ===========================================================================
34 : /**
35 : * @class CSVFormatter
36 : * @brief Output formatter for CSV output
37 : */
38 : class CSVFormatter : public OutputFormatter {
39 : public:
40 : /// @brief Constructor
41 : CSVFormatter(const std::string& columnNames, const char separator = ';');
42 :
43 : /// @brief Destructor
44 12 : virtual ~CSVFormatter() { }
45 :
46 : /** @brief Keeps track of an open XML tag by adding a new element to the stack
47 : *
48 : * @param[in] into The output stream to use (unused)
49 : * @param[in] xmlElement Name of element to open (unused)
50 : * @return The OutputDevice for further processing
51 : */
52 : void openTag(std::ostream& into, const std::string& xmlElement);
53 :
54 : /** @brief Keeps track of an open XML tag by adding a new element to the stack
55 : *
56 : * @param[in] into The output stream to use (unused)
57 : * @param[in] xmlElement Name of element to open (unused)
58 : */
59 : void openTag(std::ostream& into, const SumoXMLTag& xmlElement);
60 :
61 : /** @brief Closes the most recently opened tag
62 : *
63 : * @param[in] into The output stream to use
64 : * @return Whether a further element existed in the stack and could be closed
65 : * @todo it is not verified that the topmost element was closed
66 : */
67 : bool closeTag(std::ostream& into, const std::string& comment = "");
68 :
69 : /** @brief writes a named attribute
70 : *
71 : * @param[in] into The output stream to use
72 : * @param[in] attr The attribute (name)
73 : * @param[in] val The attribute value
74 : */
75 : template <class T>
76 0 : void writeAttr(std::ostream& into, const SumoXMLAttr attr, const T& val) {
77 0 : checkAttr(attr);
78 0 : *myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;
79 0 : }
80 :
81 : template <class T>
82 0 : void writeAttr(std::ostream& into, const std::string& attr, const T& val) {
83 : assert(!myCheckColumns);
84 0 : if (!myWroteHeader) {
85 0 : if (std::find(myHeader.begin(), myHeader.end(), attr) != myHeader.end()) {
86 0 : myHeader.push_back(myCurrentTag + "_" + attr);
87 : } else {
88 0 : myHeader.push_back(attr);
89 : }
90 : }
91 0 : *myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;
92 0 : }
93 :
94 696 : void writeNull(std::ostream& /* into */, const SumoXMLAttr attr) {
95 696 : checkAttr(attr);
96 696 : *myXMLStack[myCurrentDepth - 1] << mySeparator;
97 696 : }
98 :
99 720 : void writeTime(std::ostream& /* into */, const SumoXMLAttr attr, const SUMOTime val) {
100 720 : checkAttr(attr);
101 1440 : *myXMLStack[myCurrentDepth - 1] << time2string(val) << mySeparator;
102 720 : }
103 :
104 0 : bool wroteHeader() const {
105 0 : return myWroteHeader;
106 : }
107 :
108 6 : void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {
109 6 : myExpectedAttrs = expected;
110 6 : myMaxDepth = depth;
111 6 : myCheckColumns = expected.any();
112 6 : }
113 :
114 : private:
115 : /** @brief Helper function to keep track of the written attributes and accumulate the header.
116 : * It checks whether the written attribute is expected in the column based format.
117 : * 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.
118 : *
119 : * @param[in] attr The attribute (name)
120 : */
121 7680 : inline void checkAttr(const SumoXMLAttr attr) {
122 7680 : if (myCheckColumns && myMaxDepth == myCurrentDepth) {
123 6960 : mySeenAttrs.set(attr);
124 6960 : if (!myExpectedAttrs.test(attr)) {
125 0 : throw ProcessError(TLF("Unexpected attribute '%', this file format does not support CSV output yet.", toString(attr)));
126 : }
127 : }
128 7680 : if (!myWroteHeader) {
129 666 : const std::string attrString = toString(attr);
130 666 : if (myHeaderFormat == "plain" || (myHeaderFormat == "auto" && std::find(myHeader.begin(), myHeader.end(), attrString) == myHeader.end())) {
131 0 : myHeader.push_back(attrString);
132 : } else {
133 1332 : myHeader.push_back(myCurrentTag + "_" + attrString);
134 : }
135 : }
136 7680 : }
137 :
138 : /// @brief the format to use for the column names
139 : const std::string myHeaderFormat;
140 :
141 : /// @brief The value separator
142 : const char mySeparator;
143 :
144 : /// @brief the CSV header
145 : std::vector<std::string> myHeader;
146 :
147 : /// @brief the currently read tag (only valid when generating the header)
148 : std::string myCurrentTag;
149 :
150 : /// @brief The attributes to write for each begun xml element (excluding the root element)
151 : std::vector<std::unique_ptr<std::ostringstream>> myXMLStack;
152 :
153 : /// @brief the maximum depth of the XML hierarchy (excluding the root element)
154 : int myMaxDepth = 0;
155 :
156 : /// @brief the current depth of the XML hierarchy (excluding the root element)
157 : int myCurrentDepth = 0;
158 :
159 : /// @brief whether the CSV header line has been written
160 : bool myWroteHeader = false;
161 :
162 : /// @brief whether the columns should be checked for completeness
163 : bool myCheckColumns = false;
164 :
165 : /// @brief which CSV columns are expected (just for checking completeness)
166 : SumoXMLAttrMask myExpectedAttrs;
167 :
168 : /// @brief which CSV columns have been set (just for checking completeness)
169 : SumoXMLAttrMask mySeenAttrs;
170 : };
171 :
172 :
173 : // ===========================================================================
174 : // specialized template implementations (for speedup)
175 : // ===========================================================================
176 : template <>
177 4176 : inline void CSVFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const double& val) {
178 4176 : checkAttr(attr);
179 : #ifdef HAVE_FMT
180 : fmt::print(*myXMLStack[myCurrentDepth - 1], "{:.{}f}{}", val, into.precision(), mySeparator);
181 : #else
182 8352 : *myXMLStack[myCurrentDepth - 1] << toString(val, into.precision()) << mySeparator;
183 : #endif
184 4176 : }
185 :
186 :
187 : template <>
188 2088 : inline void CSVFormatter::writeAttr(std::ostream& /* into */, const SumoXMLAttr attr, const std::string& val) {
189 2088 : checkAttr(attr);
190 2088 : *myXMLStack[myCurrentDepth - 1] << val << mySeparator;
191 2088 : }
|