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.cpp
15 : /// @author Michael Behrisch
16 : /// @date 2025-06-12
17 : ///
18 : // An output formatter for CSV files
19 : /****************************************************************************/
20 : #include <config.h>
21 :
22 : #ifdef HAVE_FMT
23 : #include <fmt/ostream.h>
24 : #include <fmt/ranges.h>
25 : #endif
26 : #include <utils/common/MsgHandler.h>
27 : #include <utils/common/ToString.h>
28 : #include "CSVFormatter.h"
29 :
30 :
31 : // ===========================================================================
32 : // member method definitions
33 : // ===========================================================================
34 36 : CSVFormatter::CSVFormatter(const std::string& columnNames, const char separator)
35 36 : : OutputFormatter(OutputFormatterType::CSV), myHeaderFormat(columnNames), mySeparator(separator) {
36 36 : if (myHeaderFormat == "none") {
37 0 : myWroteHeader = true;
38 : }
39 36 : }
40 :
41 :
42 : bool
43 80 : CSVFormatter::writeXMLHeader(std::ostream& into, const std::string& rootElement,
44 : const std::map<SumoXMLAttr, std::string>& attrs, bool /* writeMetadata */,
45 : bool /* includeConfig */) {
46 80 : if (attrs.size() > 2) {
47 8 : myHaveRootAttrs = true;
48 8 : openTag(into, rootElement);
49 32 : for (const auto& a : attrs) {
50 24 : if (a.first != SUMO_ATTR_XMLNS && a.first != SUMO_ATTR_SCHEMA_LOCATION) {
51 8 : writeAttr(into, a.first, a.second, false);
52 : }
53 : }
54 : return true;
55 : }
56 : return false;
57 : }
58 :
59 :
60 : void
61 6575 : CSVFormatter::openTag(std::ostream& /* into */, const std::string& xmlElement) {
62 6575 : myXMLStack.push_back((int)myValues.size());
63 6575 : if (!myWroteHeader) {
64 5509 : myCurrentTag = xmlElement;
65 : }
66 6575 : if (myMaxDepth == (int)myXMLStack.size() && myWroteHeader && myCurrentTag != xmlElement) {
67 699 : WRITE_WARNINGF("Encountered mismatch in XML tags (expected % but got %). Column names may be incorrect.", myCurrentTag, xmlElement);
68 : }
69 6575 : }
70 :
71 :
72 : void
73 5814 : CSVFormatter::openTag(std::ostream& /* into */, const SumoXMLTag& xmlElement) {
74 5814 : myXMLStack.push_back((int)myValues.size());
75 5814 : if (!myWroteHeader) {
76 4376 : myCurrentTag = toString(xmlElement);
77 : }
78 9220 : if (myMaxDepth == (int)myXMLStack.size() && myWroteHeader && myCurrentTag != toString(xmlElement)) {
79 33 : WRITE_WARNINGF("Encountered mismatch in XML tags (expected % but got %). Column names may be incorrect.", myCurrentTag, toString(xmlElement));
80 : }
81 5814 : }
82 :
83 :
84 : bool
85 12417 : CSVFormatter::closeTag(std::ostream& into, const std::string& /* comment */) {
86 12417 : if (myMaxDepth == 0) {
87 : // the auto detection case: the first closed tag determines the depth
88 0 : myMaxDepth = (int)myXMLStack.size();
89 : }
90 12417 : const bool eof = myXMLStack.empty() || (myHaveRootAttrs && myXMLStack.size() == 1);
91 12417 : if ((myMaxDepth == (int)myXMLStack.size() || eof) && !myWroteHeader) {
92 : // First complete row or EOF: write the header
93 36 : if (!myCheckColumns) {
94 58 : WRITE_WARNING("Column based formats are still experimental. Autodetection only works for homogeneous output.");
95 : }
96 : #ifdef HAVE_FMT
97 : fmt::print(into, "{}\n", fmt::join(myHeader, std::string_view(&mySeparator, 1)));
98 : #else
99 72 : into << joinToString(myHeader, mySeparator) << "\n";
100 : #endif
101 36 : myWroteHeader = true;
102 : }
103 12417 : if (myNeedsWrite) {
104 11158 : myValues.resize(myHeader.size());
105 : #ifdef HAVE_FMT
106 : const std::string row = fmt::format("{}", fmt::join(myValues, std::string_view(&mySeparator, 1)));
107 : #else
108 11158 : const std::string row = joinToString(myValues, mySeparator);
109 : #endif
110 11158 : myBufferedRows.emplace_back(row);
111 : mySeenAttrs.reset();
112 11158 : myNeedsWrite = false;
113 : }
114 12417 : if (myWroteHeader && !myBufferedRows.empty()) {
115 15155 : for (const std::string& row : myBufferedRows) {
116 11158 : into << row << '\n';
117 : }
118 : myBufferedRows.clear();
119 : }
120 12417 : if (!eof) {
121 12381 : if ((int)myValues.size() > myXMLStack.back()) {
122 12182 : myValues.resize(myXMLStack.back());
123 : }
124 : myXMLStack.pop_back();
125 12381 : return true;
126 : }
127 : return false;
128 : }
129 :
130 :
131 : /****************************************************************************/
|