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 41 : CSVFormatter::CSVFormatter(const std::string& columnNames, const char separator)
35 41 : : OutputFormatter(OutputFormatterType::CSV), myHeaderFormat(columnNames), mySeparator(separator) {
36 41 : if (myHeaderFormat == "none") {
37 0 : myWroteHeader = true;
38 : }
39 41 : }
40 :
41 :
42 : bool
43 85 : CSVFormatter::writeXMLHeader(std::ostream& into, const std::string& rootElement,
44 : const std::map<SumoXMLAttr, std::string>& attrs, bool /* writeMetadata */,
45 : bool /* includeConfig */) {
46 85 : if (attrs.size() > 2) {
47 13 : myHaveRootAttrs = true;
48 13 : openTag(into, rootElement);
49 52 : for (const auto& a : attrs) {
50 39 : if (a.first != SUMO_ATTR_XMLNS && a.first != SUMO_ATTR_SCHEMA_LOCATION) {
51 13 : writeAttr(into, a.first, a.second, false, false);
52 : }
53 : }
54 : return true;
55 : }
56 : return false;
57 : }
58 :
59 :
60 : void
61 6580 : CSVFormatter::openTag(std::ostream& /* into */, const std::string& xmlElement) {
62 6580 : myXMLStack.push_back({xmlElement, (int)myValues.size()});
63 6580 : }
64 :
65 :
66 : void
67 13528 : CSVFormatter::openTag(std::ostream& /* into */, const SumoXMLTag& xmlElement) {
68 13528 : myXMLStack.push_back({toString(xmlElement), (int)myValues.size()});
69 13528 : }
70 :
71 :
72 : bool
73 20136 : CSVFormatter::closeTag(std::ostream& into, const std::string& /* comment */) {
74 20136 : if (myMaxDepth == 0) {
75 : // the auto detection case: the first closed tag determines the depth
76 0 : myMaxDepth = (int)myXMLStack.size();
77 : }
78 20136 : const bool eof = myXMLStack.empty() || (myHaveRootAttrs && myXMLStack.size() == 1);
79 20136 : if ((myMaxDepth == (int)myXMLStack.size() || eof) && !myWroteHeader) {
80 : // First complete row or EOF: write the header
81 41 : if (!myCheckColumns) {
82 68 : WRITE_WARNING("Column based formats are still experimental. Autodetection only works for homogeneous output.");
83 : }
84 41 : bool full = myHeaderFormat == "full";
85 41 : if (myHeaderFormat == "auto") {
86 41 : const std::set<std::string> uniq(myHeader.begin(), myHeader.end());
87 41 : full = uniq.size() < myHeader.size();
88 : }
89 : #ifdef HAVE_FMT
90 : fmt::print(into, "{}\n", fmt::join(full ? myFullHeader : myHeader, std::string_view(&mySeparator, 1)));
91 : #else
92 82 : into << joinToString(full ? myFullHeader : myHeader, mySeparator) << "\n";
93 : #endif
94 41 : myWroteHeader = true;
95 : }
96 20136 : if (myNeedsWrite) {
97 16013 : myValues.resize(myHeader.size());
98 : #ifdef HAVE_FMT
99 : const std::string row = fmt::format("{}", fmt::join(myValues, std::string_view(&mySeparator, 1)));
100 : #else
101 16013 : const std::string row = joinToString(myValues, mySeparator);
102 : #endif
103 16013 : myBufferedRows.emplace_back(row);
104 : mySeenAttrs.reset();
105 16013 : myNeedsWrite = false;
106 : }
107 20136 : if (myWroteHeader && !myBufferedRows.empty()) {
108 17691 : for (const std::string& row : myBufferedRows) {
109 16013 : into << row << '\n';
110 : }
111 : myBufferedRows.clear();
112 : }
113 20136 : if (!eof) {
114 20095 : if ((int)myValues.size() > myXMLStack.back().second) {
115 19896 : myValues.resize(myXMLStack.back().second);
116 : }
117 : myXMLStack.pop_back();
118 20095 : return true;
119 : }
120 : return false;
121 : }
122 :
123 :
124 : /****************************************************************************/
|