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