# HG changeset patch # User František Kučera # Date 1684023226 -7200 # Node ID e82aaf24b0fe753f409ea36b70cf4aff958a09dd # Parent 71b902e1c5eeeb6e69080c5e819a52361fbee3ba first version, prototype, works sometimes diff -r 71b902e1c5ee -r e82aaf24b0fe CMakeLists.txt --- a/CMakeLists.txt Sun May 14 00:21:14 2023 +0200 +++ b/CMakeLists.txt Sun May 14 02:13:46 2023 +0200 @@ -1,5 +1,5 @@ # Relational pipes -# Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info) +# Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -r 71b902e1c5ee -r e82aaf24b0fe nbproject/configurations.xml --- a/nbproject/configurations.xml Sun May 14 00:21:14 2023 +0200 +++ b/nbproject/configurations.xml Sun May 14 02:13:46 2023 +0200 @@ -42,7 +42,6 @@ - XMLDocumentConstructor.h relpipe-in-ods.cpp @@ -141,8 +140,6 @@ true - - diff -r 71b902e1c5ee -r e82aaf24b0fe src/CLIParser.h --- a/src/CLIParser.h Sun May 14 00:21:14 2023 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/** - * Relational pipes - * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include - -#include -#include -#include - -#include "Configuration.h" - -namespace relpipe { -namespace in { -namespace xmltable { - -class CLIParser { -private: - - string_t readNext(std::vector arguments, int& i) { - if (i < arguments.size()) return arguments[i++]; - else throw relpipe::cli::RelpipeCLIException(L"Missing CLI argument" + (i > 0 ? (L" after " + arguments[i - 1]) : L""), relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); - } - - /** - * TODO: use a common method - */ - bool parseBoolean(const string_t& value) { - if (value == L"true") return true; - else if (value == L"false") return false; - else throw relpipe::cli::RelpipeCLIException(L"Unable to parse boolean value: " + value + L" (expecting true or false)", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); - } - - void addRelation(Configuration& c, RelationConfiguration& currentRelation) { - if (currentRelation.relation.size()) { - c.relationConfigurations.push_back(currentRelation); - currentRelation = RelationConfiguration(); - } - } - - relpipe::writer::TypeId parseTypeId(const string_t& value) { - using t = relpipe::writer::TypeId; - if (value == L"string") return t::STRING; - else if (value == L"integer") return t::INTEGER; - else if (value == L"boolean") return t::BOOLEAN; - else throw relpipe::cli::RelpipeCLIException(L"Unable to parse TypeId: " + value, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); - } - -public: - - static const string_t OPTION_NAMESPACE; - static const string_t OPTION_PARSER_OPTION; - static const string_t OPTION_RELATION; - static const string_t OPTION_NAME_IS_XPATH; - static const string_t OPTION_RECORDS; - static const string_t OPTION_ATTRIBUTE; - static const string_t OPTION_XINCLUDE; - static const string_t OPTION_MODE; - static const string_t OPTION_RAW_XML_NODELIST_WRAPPER; - static const string_t OPTION_RAW_XML_ATTRIBUTE_WRAPPER; - - Configuration parse(const std::vector& arguments) { - Configuration c; - RelationConfiguration currentRelation; - - for (int i = 0; i < arguments.size();) { - string_t option = readNext(arguments, i); - - if (option == OPTION_NAMESPACE) { - c.namespaceMappings.push_back(readNext(arguments, i)); - c.namespaceMappings.push_back(readNext(arguments, i)); - } else if (option == OPTION_PARSER_OPTION) { - c.parserOptions.push_back({readNext(arguments, i), readNext(arguments, i)}); - } else if (option == OPTION_XINCLUDE) { - c.xinclude = parseBoolean(readNext(arguments, i)); - } else if (option == OPTION_RELATION) { - addRelation(c, currentRelation); // previous relation - currentRelation.relation = readNext(arguments, i); - } else if (option == OPTION_NAME_IS_XPATH) { - currentRelation.nameIsXPath = true; - } else if (option == OPTION_RECORDS) { - currentRelation.xpath = readNext(arguments, i); - } else if (option == OPTION_ATTRIBUTE) { - AttributeRecipe attribute; - attribute.mode = currentRelation.mode; - attribute.rawXmlNodeListWrapper = currentRelation.rawXmlNodeListWrapper; - attribute.rawXmlAttributeWrapper = currentRelation.rawXmlAttributeWrapper; - attribute.name = readNext(arguments, i); - attribute.type = parseTypeId(readNext(arguments, i)); - attribute.xpath = readNext(arguments, i); - currentRelation.attributes.push_back(attribute); - } else if (option == OPTION_MODE) { - string_t modeName = readNext(arguments, i); - Mode mode; - if (modeName == L"string") mode = Mode::STRING; - else if (modeName == L"boolean") mode = Mode::BOOLEAN; - else if (modeName == L"raw-xml") mode = Mode::RAW_XML; - else if (modeName == L"line-number") mode = Mode::LINE_NUMBER; - else if (modeName == L"xpath") mode = Mode::XPATH; - else throw relpipe::cli::RelpipeCLIException(L"Unsupported mode: " + modeName, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); - if (currentRelation.attributes.size()) currentRelation.attributes.back().mode = mode; - else currentRelation.mode = mode; - } else if (option == OPTION_RAW_XML_NODELIST_WRAPPER) { - XmlElementSkeleton w = {readNext(arguments, i), readNext(arguments, i), readNext(arguments, i)}; - if (currentRelation.attributes.size()) currentRelation.attributes.back().rawXmlNodeListWrapper = w; - else currentRelation.rawXmlNodeListWrapper = w; - } else if (option == OPTION_RAW_XML_ATTRIBUTE_WRAPPER) { - XmlElementSkeleton w = {readNext(arguments, i), readNext(arguments, i), readNext(arguments, i)}; - if (currentRelation.attributes.size()) currentRelation.attributes.back().rawXmlAttributeWrapper = w; - else currentRelation.rawXmlAttributeWrapper = w; - } else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); - } - addRelation(c, currentRelation); // last relation - - return c; - } - - virtual ~CLIParser() { - } -}; - -const string_t CLIParser::OPTION_NAMESPACE = L"--namespace"; -const string_t CLIParser::OPTION_PARSER_OPTION = L"--parser-option"; -const string_t CLIParser::OPTION_RELATION = L"--relation"; -const string_t CLIParser::OPTION_NAME_IS_XPATH = L"--name-is-xpath"; -const string_t CLIParser::OPTION_RECORDS = L"--records"; -const string_t CLIParser::OPTION_ATTRIBUTE = L"--attribute"; -const string_t CLIParser::OPTION_XINCLUDE = L"--xinclude"; -const string_t CLIParser::OPTION_MODE = L"--mode"; -const string_t CLIParser::OPTION_RAW_XML_NODELIST_WRAPPER = L"--raw-xml-nodelist-wrapper"; -const string_t CLIParser::OPTION_RAW_XML_ATTRIBUTE_WRAPPER = L"--raw-xml-attribute-wrapper"; - -} -} -} diff -r 71b902e1c5ee -r e82aaf24b0fe src/CMakeLists.txt --- a/src/CMakeLists.txt Sun May 14 00:21:14 2023 +0200 +++ b/src/CMakeLists.txt Sun May 14 02:13:46 2023 +0200 @@ -1,5 +1,5 @@ # Relational pipes -# Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info) +# Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -r 71b902e1c5ee -r e82aaf24b0fe src/Configuration.h --- a/src/Configuration.h Sun May 14 00:21:14 2023 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/** - * Relational pipes - * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include - -#include - - -namespace relpipe { -namespace in { -namespace xmltable { - -enum class Mode { - STRING, - BOOLEAN, - // TODO: support also XML number, when we have a rational or decimal numbers in Relational pipes - RAW_XML, - LINE_NUMBER, - XPATH -}; - -class XmlElementSkeleton { -public: - relpipe::writer::string_t name; - relpipe::writer::string_t uri; - relpipe::writer::string_t prefix; - - XmlElementSkeleton() { - } - - XmlElementSkeleton(relpipe::writer::string_t name, relpipe::writer::string_t uri = L"", relpipe::writer::string_t prefix = L"") : name(name), uri(uri), prefix(prefix) { - } - - virtual ~XmlElementSkeleton() { - } -}; - -class AttributeRecipe { -public: - - virtual ~AttributeRecipe() { - } - - relpipe::writer::string_t name; - relpipe::writer::TypeId type; - relpipe::writer::string_t xpath; - Mode mode = Mode::STRING; - XmlElementSkeleton rawXmlNodeListWrapper; - XmlElementSkeleton rawXmlAttributeWrapper; - -}; - -class ParserOptionRecipe { -public: - relpipe::writer::string_t uri; - relpipe::writer::string_t value; - - ParserOptionRecipe(relpipe::writer::string_t uri, relpipe::writer::string_t value) : uri(uri), value(value) { - } -}; - -class RelationConfiguration { -public: - - virtual ~RelationConfiguration() { - } - - relpipe::writer::string_t relation; - relpipe::writer::boolean_t nameIsXPath = false; - relpipe::writer::string_t xpath; - std::vector attributes; - - // Defaults/templates for AttributeRecipe: - Mode mode = Mode::STRING; - XmlElementSkeleton rawXmlNodeListWrapper; - XmlElementSkeleton rawXmlAttributeWrapper = {L"attribute"}; -}; - -class Configuration { -public: - std::vector relationConfigurations; - std::vector namespaceMappings; - std::vector parserOptions; - bool xinclude = false; - - virtual ~Configuration() { - } -}; - -} -} -} \ No newline at end of file diff -r 71b902e1c5ee -r e82aaf24b0fe src/ODSCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ODSCommand.h Sun May 14 02:13:46 2023 +0200 @@ -0,0 +1,128 @@ +/** + * Relational pipes + * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace relpipe { +namespace in { +namespace ods { + +using namespace relpipe::writer; + +class ODSCommand { +private: + std::wstring_convert> convertor; + + xmlpp::Node::PrefixNsMap ns; + std::shared_ptr writer; + + string_t xpath(xmlpp::Node* node, std::string xpath) { + return convertor.from_bytes(node->eval_to_string(xpath, ns)); + } + + void processCell(xmlpp::Node* c, AttributeMetadata& am) { + string_t value = xpath(c, "@o:value"); + if (value.size()) writer->writeAttribute(value); + else writer->writeAttribute(xpath(c, "tx:p")); + // TODO: be aware of the current data types + } + + void processRow(xmlpp::Node* r, std::vector& am) { + for (int i = 0; i < am.size(); i++) { + auto xpe = std::string("t:table-cell[") + + std::to_string(i + 1) + + "]"; + auto cells = r->find(xpe, ns); + if (cells.size() == 1) { + processCell(cells[0], am[i]); + } else { + writer->writeAttribute(L""); + // TODO: support also other data types + } + } + + // FIXME: support sparse data / missing values: + // + // + + } + + void processTable(xmlpp::Node* t) { + auto relation = xpath(t, "@t:name"); + std::vector metadata; + + for (xmlpp::Node* c : t->find("t:table-row[1]/t:table-cell", ns)) { + auto name = xpath(c, "tx:p"); + if (name.size()) { + metadata.push_back({name, TypeId::STRING}); + // TODO: detect and support other data types + } + } + + writer->startRelation(relation, metadata, true); + + int i = 0; + for (xmlpp::Node* r : t->find("t:table-row", ns)) { + i++; + if (i == 1) continue; // skip header row + processRow(r, metadata); + } + } + +public: + + ODSCommand(std::shared_ptr writer) : writer(writer) { + ns["o"] = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; + ns["t"] = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; + ns["tx"] = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; + } + + void process(std::istream & input) { + xmlpp::DomParser parser; + parser.parse_stream(input); + + xmlpp::Element* root = parser.get_document()->get_root_node(); + auto spreadsheets = root->find("/o:document/o:body/o:spreadsheet", ns); + + if (spreadsheets.size() == 1) { + for (xmlpp::Node* table : spreadsheets[0]->find("t:table", ns)) { + processTable(table); + } + } else { + throw RelpipeWriterException(L"Invalid XML structure. " + "Expecting OpenDocument spreadsheet."); + } + + + } +}; + +} +} +} diff -r 71b902e1c5ee -r e82aaf24b0fe src/XMLDocumentConstructor.h --- a/src/XMLDocumentConstructor.h Sun May 14 00:21:14 2023 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/** - * Relational pipes - * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -namespace relpipe { -namespace in { -namespace xmltable { - -#include - -class XMLDocumentConstructor { -private: - std::istream* input = nullptr; - xmlpp::DomParser* parser = nullptr; -public: - - XMLDocumentConstructor(std::istream* input, xmlpp::DomParser* parser) : input(input), parser(parser) { - } - - void setOption(const std::string& uri, const std::string& value) { - } - - void process() { - parser->parse_stream(*input); - } -}; - -} -} -} \ No newline at end of file diff -r 71b902e1c5ee -r e82aaf24b0fe src/XMLTableCommand.h --- a/src/XMLTableCommand.h Sun May 14 00:21:14 2023 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/** - * Relational pipes - * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "Configuration.h" -#include "XMLDocumentConstructor.h" - -namespace relpipe { -namespace in { -namespace xmltable { - -using namespace relpipe::writer; - -class XMLCommand { -private: - std::wstring_convert> convertor; // TODO: support also other encodings. - - string_t formatRawXML(string_t rawXML) { - std::wregex pattern(L"^(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>\\s*)+|\n$"); - // libxml sometimes returns doubled XML declaration (probably a bug), see: - // --relation ini --records '/' --attribute 'xml' string '.' --mode raw-xml # (but not for --records '//*') - // so we remove all of them and also trailing line ends (if any). - return std::regex_replace(rawXML, pattern, L""); - } - - void importNode(xmlpp::Node* parent, xmlpp::Node* child, AttributeRecipe attributeRecipe) { - if (dynamic_cast (child)) parent->add_child_with_new_ns( - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.name), - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.uri), - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.prefix))->import_node(child); - else parent->import_node(child, true); - } - - void importNode(xmlpp::Document* document, xmlpp::Node* child, AttributeRecipe attributeRecipe) { - if (dynamic_cast (child)) document->create_root_node( - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.name), - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.uri), - convertor.to_bytes(attributeRecipe.rawXmlAttributeWrapper.prefix))->import_node(child); - else document->create_root_node_by_import(child, true); - } - - string_t toRawXML(xmlpp::Node* parent, AttributeRecipe attributeRecipe, xmlpp::Node::PrefixNsMap ns) { - xmlpp::Document d; - xmlpp::NodeSet nodes = parent->find(convertor.to_bytes(attributeRecipe.xpath), ns); - - if (attributeRecipe.rawXmlNodeListWrapper.name.size()) { - d.create_root_node( - convertor.to_bytes(attributeRecipe.rawXmlNodeListWrapper.name), - convertor.to_bytes(attributeRecipe.rawXmlNodeListWrapper.uri), - convertor.to_bytes(attributeRecipe.rawXmlNodeListWrapper.prefix)); - for (xmlpp::Node* node : nodes) importNode(d.get_root_node(), node, attributeRecipe); - } else { - if (nodes.size() == 1) importNode(&d, nodes[0], attributeRecipe); - else if (nodes.size() > 1) throw std::invalid_argument("Multiple nodes found where only one was expected. Use nodelist wrapper."); // TODO: better relpipe exception - else return L""; // TODO: null - } - return formatRawXML(convertor.from_bytes(d.write_to_string())); - } - -public: - - void process(std::istream& input, std::ostream& output, Configuration& configuration) { - std::shared_ptr writer(Factory::create(output)); - - xmlpp::DomParser parser; - XMLDocumentConstructor documentConstructor(&input, &parser); - for (ParserOptionRecipe o : configuration.parserOptions) documentConstructor.setOption(convertor.to_bytes(o.uri), convertor.to_bytes(o.value)); - documentConstructor.process(); - if (configuration.xinclude) parser.get_document()->process_xinclude(true); - xmlpp::Element* root = parser.get_document()->get_root_node(); - - xmlpp::Node::PrefixNsMap ns; - for (int i = 0; i < configuration.namespaceMappings.size(); i++) { - std::string prefix = convertor.to_bytes(configuration.namespaceMappings[i]); - std::string uri = convertor.to_bytes(configuration.namespaceMappings[++i]); - ns[prefix] = uri; - } - - for (const RelationConfiguration& r : configuration.relationConfigurations) { - std::vector attributesMetadata; - for (AttributeRecipe a : r.attributes) attributesMetadata.push_back(AttributeMetadata{a.name, a.type}); - relpipe::writer::string_t name = r.nameIsXPath ? convertor.from_bytes(root->eval_to_string(convertor.to_bytes(r.relation), ns)) : r.relation; - writer->startRelation(name, attributesMetadata, true); - for (xmlpp::Node* n : root->find(convertor.to_bytes(r.xpath), ns)) { - for (AttributeRecipe a : r.attributes) { - // TODO: convert to bytes only once - std::string attributeXpath = convertor.to_bytes(a.xpath); - if (a.mode == Mode::STRING) { - writer->writeAttribute(convertor.from_bytes(n->eval_to_string(attributeXpath, ns))); - } else if (a.mode == Mode::BOOLEAN) { - writer->writeAttribute(n->eval_to_boolean(attributeXpath, ns) ? L"true" : L"false"); - } else if (a.mode == Mode::LINE_NUMBER) { - xmlpp::NodeSet attributeNodes = n->find(attributeXpath, ns); - string_t line = attributeNodes.size() ? std::to_wstring(attributeNodes[0]->get_line()) : L""; // TODO: null - writer->writeAttribute(line); - } else if (a.mode == Mode::XPATH) { - xmlpp::NodeSet attributeNodes = n->find(attributeXpath, ns); - string_t line = attributeNodes.size() ? convertor.from_bytes(attributeNodes[0]->get_path()) : L""; // TODO: null - writer->writeAttribute(line); - } else if (a.mode == Mode::RAW_XML) { - writer->writeAttribute(toRawXML(n, a, ns)); - } else { - throw logic_error("Unsupported mode."); // should never happer, TODO: better relpipe exception - } - } - } - } - } -}; - -} -} -} diff -r 71b902e1c5ee -r e82aaf24b0fe src/relpipe-in-ods.cpp --- a/src/relpipe-in-ods.cpp Sun May 14 00:21:14 2023 +0200 +++ b/src/relpipe-in-ods.cpp Sun May 14 02:13:46 2023 +0200 @@ -1,6 +1,6 @@ /** * Relational pipes - * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,6 @@ #include #include -#include - #include #include #include @@ -30,13 +28,11 @@ #include -#include "XMLTableCommand.h" -#include "CLIParser.h" -#include "Configuration.h" +#include "ODSCommand.h" using namespace relpipe::cli; using namespace relpipe::writer; -using namespace relpipe::in::xmltable; +using namespace relpipe::in::ods; int main(int argc, char** argv) { setlocale(LC_ALL, ""); @@ -46,19 +42,14 @@ int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR; try { - CLIParser cliParser; - Configuration configuration = cliParser.parse(cli.arguments()); - XMLCommand command; - command.process(cin, cout, configuration); + std::shared_ptr writer(Factory::create(cout)); + ODSCommand command(writer); + command.process(cin); resultCode = CLI::EXIT_CODE_SUCCESS; } catch (RelpipeWriterException& e) { fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessage().c_str()); fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount()); resultCode = CLI::EXIT_CODE_DATA_ERROR; - } catch (RelpipeCLIException& e) { - fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessage().c_str()); - fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount()); - resultCode = e.getExitCode(); } // TODO: catch xmlpp::exception return resultCode;