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 56 : virtual ~CSVFormatter() { }
44 :
45 : /** @brief Keeps track of an open XML tag by adding a new element to the stack
46 : *
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 processing
50 : */
51 : void openTag(std::ostream& into, const std::string& xmlElement);
52 :
53 : /** @brief Keeps track of an open XML tag by adding a new element to the stack
54 : *
55 : * @param[in] into The output stream to use (unused)
56 : * @param[in] xmlElement Name of element to open (unused)
57 : */
58 : void openTag(std::ostream& into, const SumoXMLTag& xmlElement);
59 :
60 : /** @brief Closes the most recently opened tag
61 : *
62 : * @param[in] into The output stream to use
63 : * @return Whether a further element existed in the stack and could be closed
64 : * @todo it is not verified that the topmost element was closed
65 : */
66 : bool closeTag(std::ostream& into, const std::string& comment = "");
67 :
68 : /** @brief writes a named attribute
69 : *
70 : * @param[in] into The output stream to use
71 : * @param[in] attr The attribute (name)
72 : * @param[in] val The attribute value
73 : */
74 : template <class T>
75 17606 : void writeAttr(std::ostream& into, const SumoXMLAttr attr, const T& val, const bool isNull) {
76 17606 : checkAttr(attr);
77 24344 : myValues.emplace_back(isNull ? "" : toString(val, into.precision()));
78 17606 : }
79 :
80 : template <class T>
81 53176 : void writeAttr(std::ostream& into, const std::string& attr, const T& val, const bool isNull) {
82 : assert(!myCheckColumns);
83 53176 : checkHeader(attr);
84 73724 : myValues.emplace_back(isNull ? "" : toString(val, into.precision()));
85 53176 : }
86 :
87 1120 : void writeTime(std::ostream& /* into */, const SumoXMLAttr attr, const SUMOTime val) {
88 1120 : checkAttr(attr);
89 1120 : myValues.emplace_back(time2string(val));
90 1120 : }
91 :
92 0 : bool wroteHeader() const {
93 0 : return myWroteHeader;
94 : }
95 :
96 28 : void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {
97 28 : myExpectedAttrs = expected;
98 28 : myMaxDepth = depth;
99 28 : myCheckColumns = expected.any();
100 28 : }
101 :
102 : private:
103 : /** @brief Helper function to keep track of the written attributes and accumulate the header.
104 : * It checks whether the written attribute is expected in the column based format.
105 : * 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.
106 : *
107 : * @param[in] attr The attribute (name)
108 : */
109 18726 : inline void checkAttr(const SumoXMLAttr attr) {
110 18726 : if (myCheckColumns && myMaxDepth == (int)myXMLStack.size()) {
111 8100 : mySeenAttrs.set(attr);
112 8100 : if (!myExpectedAttrs.test(attr)) {
113 0 : throw ProcessError(TLF("Unexpected attribute '%', this file format does not support CSV output yet.", toString(attr)));
114 : }
115 : }
116 18726 : checkHeader(attr);
117 18726 : }
118 :
119 : template <class ATTR_TYPE>
120 71902 : inline void checkHeader(const ATTR_TYPE& attr) {
121 71902 : myNeedsWrite = true;
122 71902 : if (!myWroteHeader) {
123 3933 : std::string headerName = toString(attr);
124 54091 : if (myHeaderFormat != "plain" && !(myHeaderFormat == "auto" && std::find(myHeader.begin(), myHeader.end(), headerName) == myHeader.end())) {
125 162273 : headerName = myCurrentTag + "_" + headerName;
126 : }
127 54091 : if (std::find(myHeader.begin(), myHeader.end(), headerName) == myHeader.end()) {
128 12737 : for (std::string& row : myBufferedRows) {
129 12347 : row += mySeparator;
130 : }
131 478 : while (myValues.size() < myHeader.size()) {
132 88 : myValues.emplace_back("");
133 : }
134 390 : myHeader.emplace_back(headerName);
135 : }
136 : }
137 71902 : }
138 :
139 : /// @brief the format to use for the column names
140 : const std::string myHeaderFormat;
141 :
142 : /// @brief The value separator
143 : const char mySeparator;
144 :
145 : /// @brief the CSV header
146 : std::vector<std::string> myHeader;
147 :
148 : /// @brief the currently read tag (only valid when generating the header)
149 : std::string myCurrentTag;
150 :
151 : /// @brief The number of attributes in the currently open XML elements
152 : std::vector<int> myXMLStack;
153 :
154 : /// @brief the current attribute / column values
155 : std::vector<std::string> myValues;
156 :
157 : /// @brief the maximum depth of the XML hierarchy (excluding the root element)
158 : int myMaxDepth = 2;
159 :
160 : /// @brief whether the CSV header line has been written
161 : bool myWroteHeader = false;
162 :
163 : /// @brief whether any attribute has been written since the last row was emitted
164 : bool myNeedsWrite = false;
165 :
166 : /// @brief partial rows buffered before the schema is known (depth < myMaxDepth)
167 : std::vector<std::string> myBufferedRows;
168 :
169 : /// @brief whether the columns should be checked for completeness
170 : bool myCheckColumns = false;
171 :
172 : /// @brief which CSV columns are expected (just for checking completeness)
173 : SumoXMLAttrMask myExpectedAttrs;
174 :
175 : /// @brief which CSV columns have been set (just for checking completeness)
176 : SumoXMLAttrMask mySeenAttrs;
177 : };
|