From 5e527213a2430bb3018e5eebd909aef294edf9b5 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 18 Dec 2020 18:48:46 +0300 Subject: Switch to build2 --- xsd/xsd/xsd.cxx | 1144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1144 insertions(+) create mode 100644 xsd/xsd/xsd.cxx (limited to 'xsd/xsd/xsd.cxx') diff --git a/xsd/xsd/xsd.cxx b/xsd/xsd/xsd.cxx new file mode 100644 index 0000000..90f9a86 --- /dev/null +++ b/xsd/xsd/xsd.cxx @@ -0,0 +1,1144 @@ +// file : xsd/xsd.cxx +// license : GNU GPL v2 + exceptions; see accompanying LICENSE file + +#include +#include +#include // std::unique_ptr +#include // std::size_t +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace SemanticGraph = XSDFrontend::SemanticGraph; +namespace Transformations = XSDFrontend::Transformations; + +using namespace std; + +// +// +struct LocationTranslator: XSDFrontend::LocationTranslator +{ + struct Failed {}; + + LocationTranslator (NarrowStrings const& map, + NarrowStrings const& regex, + bool trace); + + virtual NarrowString + translate (NarrowString const&); + +private: + typedef map Map; + + typedef cutl::re::regexsub Regex; + typedef cutl::re::format RegexFormat; + typedef vector RegexVector; + + typedef map Cache; + + Map map_; + RegexVector regex_; + Cache cache_; + bool trace_; +}; + +// +// +struct AnonymousNameTranslator: Transformations::AnonymousNameTranslator +{ + struct Failed {}; + + AnonymousNameTranslator (NarrowStrings const& regex, bool trace); + + virtual String + translate (String const& file, + String const& ns, + String const& name, + String const& xpath); + +private: + typedef cutl::re::wregexsub Regex; + typedef cutl::re::wformat RegexFormat; + typedef vector RegexVector; + + RegexVector regex_; + bool trace_; + +}; + +// +// +struct SchemaPerTypeTranslator: Transformations::SchemaPerTypeTranslator +{ + struct Failed {}; + + SchemaPerTypeTranslator (NarrowStrings const& type_regex, + bool type_trace, + NarrowStrings const& schema_regex, + bool schema_trace); + + virtual String + translate_type (String const& ns, String const& name); + + virtual NarrowString + translate_schema (NarrowString const& file); + +private: + typedef cutl::re::wregexsub TypeRegex; + typedef cutl::re::wformat TypeRegexFormat; + typedef vector TypeRegexVector; + + TypeRegexVector type_regex_; + bool type_trace_; + + typedef cutl::re::regexsub SchemaRegex; + typedef cutl::re::format SchemaRegexFormat; + typedef vector SchemaRegexVector; + + SchemaRegexVector schema_regex_; + bool schema_trace_; +}; + +// +// +struct XercesInitializer +{ + XercesInitializer () + { + xercesc::XMLPlatformUtils::Initialize (); + } + + ~XercesInitializer () + { + xercesc::XMLPlatformUtils::Terminate (); + } +}; + +// Expand the \n escape sequence. +// +void +expand_nl (NarrowString& s); + +int +main (int argc, char* argv[]) +{ + wostream& e (wcerr); + + try + { + cli::argv_file_scanner args (argc, argv, "--options-file"); + help_options help_ops (args, cli::unknown_mode::stop); + + // Handle --build2-metadata (see also buildfile). + // + if (help_ops.build2_metadata_specified ()) + { + wostream& o (wcout); + + // Note that the export.metadata variable should be the first non- + // blank/comment line. + // + o << "# build2 buildfile xsd" << endl + << "export.metadata = 1 xsd" << endl + << "xsd.name = [string] xsd" << endl + << "xsd.version = [string] '" << XSD_VERSION_FULL << '\'' << endl + << "xsd.checksum = [string] '" << XSD_VERSION_FULL << '\'' << endl; + + return 0; + } + + NarrowString cmd; + if (args.more ()) + cmd = args.next (); + + if (help_ops.version () || cmd == "version") + { + wostream& o (wcout); + + o << "CodeSynthesis XSD XML Schema to C++ compiler " << + XSD_VERSION_STR << endl + << "Copyright (c) " << XSD_COPYRIGHT << "." << endl; + + if (!help_ops.proprietary_license () && cmd == "version") + { + // Parse the options after the command to detect trailing + // --proprietary-license. + // + help_ops = help_options (args, cli::unknown_mode::stop); + } + + if (help_ops.proprietary_license ()) + { + o << "The compiler was invoked in the Proprietary License mode. You " + << "should have\nreceived a proprietary license from Code Synthesis " + << "Tools CC that entitles\nyou to use it in this mode." << endl; + } + else + { + o << "This is free software; see the source for copying conditions. " + << "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS " + << "FOR A PARTICULAR PURPOSE." << endl; + } + + return 0; + } + + if (help_ops.help () || cmd == "help") + { + wostream& o (wcout); + + if (cmd == "help" && args.more ()) + { + NarrowString arg (args.next ()); + + if (arg == "cxx-tree") + { + o << "Usage: " << argv[0] << " cxx-tree [options] file [file ...]" + << endl + << "Options:" << endl; + + CXX::Tree::Generator::usage (); + } + else if (arg == "cxx-parser") + { + o << "Usage: " << argv[0] << " cxx-parser [options] file [file ...]" + << endl + << "Options:" << endl; + + CXX::Parser::Generator::usage (); + } + else + { + o << "error: unknown command '" << arg.c_str () << "'" << endl + << "info: try '" << argv[0] << " help' for the list of commands" + << endl; + + return 1; + } + + // Add frontend options at the end. + // + options::print_usage (o); + } + else + { + o << "Usage: " << argv[0] << " ..." << endl + << "Commands:" << endl; + + o << " help Print usage information and exit. Use\n" + << " 'help ' for command-specific options." + << endl; + + o << " version Print version and exit." + << endl; + + o << " cxx-tree Generate the C++/Tree mapping." + << endl; + + o << " cxx-parser Generate the C++/Parser mapping." + << endl; + } + + return 0; + } + + if (cmd.empty ()) + { + e << "error: no command specified" << endl + << "info: try '" << argv[0] << " help' for usage information" << endl; + + return 1; + } + + if (cmd != "cxx-tree" && cmd != "cxx-parser") + { + e << "error: unknown command '" << cmd.c_str () << "'" << endl + << "info: try '" << argv[0] << " help' for the list of commands" + << endl; + + return 1; + } + + // We need to parse command line options before we can get to + // the arguments. + // + unique_ptr tree_ops ( + cmd == "cxx-tree" ? new CXX::Tree::options (args) : 0); + + unique_ptr parser_ops ( + cmd == "cxx-parser" ? new CXX::Parser::options (args) : 0); + + CXX::options& common_ops ( + cmd == "cxx-tree" + ? static_cast (*tree_ops) + : static_cast (*parser_ops)); + + // Disabled warnings. + // + WarningSet disabled_w; + { + NarrowStrings const& w (common_ops.disable_warning ()); + + for (NarrowStrings::const_iterator i (w.begin ()); i != w.end (); ++i) + disabled_w.insert (*i); + } + + bool disabled_w_all (disabled_w.find ("all") != disabled_w.end ()); + + if (common_ops.morph_anonymous () && + !disabled_w_all && + disabled_w.find ("D001") == disabled_w.end ()) + { + e << "warning D001: the --morph-anonymous option is on by default and " + << "no longer required" + << endl; + } + + // Collect all the files to compile in a vector. + // + NarrowStrings files; + + while (args.more ()) + files.push_back (args.next ()); + + if (files.empty ()) + { + e << "error: no input file specified" << endl; + return 1; + } + + bool fpt (common_ops.file_per_type ()); + + if (cmd == "cxx-tree" || cmd == "cxx-parser") + { + bool gen (common_ops.generate_xml_schema ()); + bool use (common_ops.extern_xml_schema ()); + + // Things get complicated when we are compiling several schemas at + // once (non-file-per-type mode) and use the --generate-xml-schema/ + // --extern-xml-schema options. The only way we can figure out which + // file corresponds to XML Schema is if the --extern-xml-schema option + // is also present. So we are going to require it for this case, + // especially since it generally makes sense. + // + if (!fpt) + { + if (files.size () > 1 && gen && !use) + { + e << "error: --extern-xml-schema is required when compiling more " + << "than one schema and --generate-xml-schema is specified" + << endl; + + return 1; + } + + if (files.size () == 1 && gen && use) + { + e << "error: --generate-xml-schema and --extern-xml-schema are " + << "mutually exclusive when compiling a single schema" << endl; + + return 1; + } + } + else + { + // The --file-per-type and --generate-xml-schema options are + // incompatible. It also makes sense to use --file-per-type + // and --extern-xml-schema. + // + if (gen) + { + e << "error: --file-per-type and --generate-xml-schema are " + << "incompatible" << endl + << "info: use --generate-xml-schema in a separate invocation " + << "of the compiler" << endl; + + return 1; + } + + if (!use && + !disabled_w_all && disabled_w.find ("D002") == disabled_w.end ()) + { + e << "warning D002: --extern-xml-schema is recommended when " + << "--file-per-type is specified to reduce generated code size" + << endl; + } + } + } + + // + // + FileList file_list; + AutoUnlinks unlinks; + size_t sloc (0); + + LocationTranslator loc_translator ( + common_ops.location_map (), + common_ops.location_regex (), + common_ops.location_regex_trace ()); + + AnonymousNameTranslator anon_translator ( + common_ops.anonymous_regex (), + common_ops.anonymous_regex_trace ()); + + // Load custom string literals, if any. + // + CXX::StringLiteralMap string_literal_map; + + if (NarrowString file = common_ops.custom_literals ()) + { + XercesInitializer xerces_init; + + if (!CXX::read_literal_map (file, string_literal_map)) + { + // Diagnostics has already been issued. + // + return 1; + } + } + + if (!fpt) + { + // File-per-schema compilation mode. + // + + for (size_t i (0); i < files.size (); ++i) + { + // Parse schema. + // + SemanticGraph::Path tu; + + try + { + tu = SemanticGraph::Path (files[i]); + } + catch (SemanticGraph::InvalidPath const&) + { + e << "error: '" << files[i].c_str () << "' is not a valid " + << "filesystem path" << endl; + + return 1; + } + + XSDFrontend::Parser parser ( + cmd != "cxx-tree", + !common_ops.disable_multi_import (), + !common_ops.disable_full_check (), + loc_translator, + disabled_w); + + unique_ptr schema; + + if (cmd == "cxx-tree" || cmd == "cxx-parser") + { + // See if we are generating code for the XML Schema namespace. + // We could be compiling several schemas at once in which case + // handling of the --generate-xml-schema option gets tricky: we + // will need to rely on the presence of the --extern-xml-schema + // to tell us which (fake) schema file corresponds to XML Schema. + // + bool gen_xml_schema (common_ops.generate_xml_schema ()); + + if (gen_xml_schema) + { + if (NarrowString name = common_ops.extern_xml_schema ()) + { + if (tu.string () != name) + gen_xml_schema = false; + } + } + + if (gen_xml_schema) + schema = parser.xml_schema (tu); + else + schema = parser.parse (tu); + } + else + schema = parser.parse (tu); + + // Morph anonymous types. + // + if (!common_ops.preserve_anonymous ()) + { + try + { + Transformations::Anonymous trans (anon_translator); + trans.transform (*schema, tu, true); + } + catch (Transformations::Anonymous::Failed const&) + { + return 1; // Diagnostic has already been issued. + } + } + + // Synthesize enumerations from unions. + // + if (cmd == "cxx-tree") + { + Transformations::EnumSynthesis trans; + trans.transform (*schema, tu); + } + + // Simplify the schema graph. + // + if (cmd == "cxx-parser") + { + Transformations::Simplifier trans; + trans.transform (*schema, tu); + } + + // Try to rearrange definitions so that there is no forward + // inheritance. + // + try + { + Processing::Inheritance::Processor proc; + proc.process (*schema, tu); + } + catch (Processing::Inheritance::Processor::Failed const&) + { + return 1; // Diagnostic has already been issued. + } + + // Normalize and annotate complex content restrictions. + // + if (cmd == "cxx-parser") + { + try + { + Transformations::Restriction trans; + trans.transform (*schema, tu); + } + catch (Transformations::Restriction::Failed const&) + { + return 1; // Diagnostic has already been issued. + } + } + + // Calculate cardinality. + // + { + Processing::Cardinality::Processor proc; + proc.process (*schema, tu); + } + + // Generate mapping. + // + if (cmd == "cxx-tree") + { + try + { + sloc += CXX::Tree::Generator::generate ( + *tree_ops, + *schema, + tu, + false, + string_literal_map, + disabled_w, + file_list, + unlinks); + } + catch (CXX::Tree::Generator::Failed const&) + { + // Diagnostic has already been issued. + // + return 1; + } + } + else if (cmd == "cxx-parser") + { + try + { + sloc += CXX::Parser::Generator::generate ( + *parser_ops, + *schema, + tu, + false, + string_literal_map, + true, + disabled_w, + file_list, + unlinks); + } + catch (CXX::Parser::Generator::Failed const&) + { + // Diagnostic has already been issued. + // + return 1; + } + } + } + } + else + { + // File-per-type compilation mode. + // + SemanticGraph::Paths paths; + + for (size_t i (0); i < files.size (); ++i) + { + try + { + paths.push_back (SemanticGraph::Path (files[i])); + } + catch (SemanticGraph::InvalidPath const&) + { + e << "error: '" << files[i].c_str () << "' is not a valid " + << "filesystem path" << endl; + + return 1; + } + } + + if (cmd == "cxx-parser" && + paths.size () > 1 && + parser_ops->generate_test_driver ()) + { + e << "info: generating test driver for the first schema only: '" << + paths[0] << "'" << endl; + } + + XSDFrontend::Parser parser ( + cmd != "cxx-tree", + !common_ops.disable_multi_import (), + !common_ops.disable_full_check (), + loc_translator, + disabled_w); + + unique_ptr schema (parser.parse (paths)); + + // Morph anonymous types. + // + if (!common_ops.preserve_anonymous ()) + { + try + { + Transformations::Anonymous trans (anon_translator); + trans.transform (*schema, SemanticGraph::Path (), false); + } + catch (Transformations::Anonymous::Failed const&) + { + return 1; // Diagnostic has already been issued. + } + } + + // Synthesize enumerations from unions. + // + if (cmd == "cxx-tree") + { + Transformations::EnumSynthesis trans; + trans.transform (*schema, SemanticGraph::Path ()); + } + + // Simplify the schema graph. + // + if (cmd == "cxx-parser") + { + Transformations::Simplifier trans; + trans.transform (*schema, SemanticGraph::Path ()); + } + + // Normalize and annotate complex content restrictions. + // + if (cmd == "cxx-parser") + { + try + { + Transformations::Restriction trans; + trans.transform (*schema, SemanticGraph::Path ()); + } + catch (Transformations::Restriction::Failed const&) + { + return 1; // Diagnostic has already been issued. + } + } + + // Calculate cardinality. + // + { + Processing::Cardinality::Processor proc; + proc.process (*schema, SemanticGraph::Path ()); + } + + // Rearrange the graph so that each type is in a seperate + // schema file. + // + typedef vector Schemas; + + SchemaPerTypeTranslator type_translator ( + common_ops.type_file_regex (), + common_ops.type_file_regex_trace (), + common_ops.schema_file_regex (), + common_ops.schema_file_regex_trace ()); + + Transformations::SchemaPerType trans ( + type_translator, + common_ops.fat_type_file ()); + + Schemas schemas (trans.transform (*schema)); + + // Generate code. + // + for (Schemas::iterator b (schemas.begin ()), i (b), e (schemas.end ()); + i != e; ++i) + { + SemanticGraph::Schema& s (**i); + SemanticGraph::Path path ( + s.context ().count ("renamed") + ? s.context ().get ("renamed") + : s.used_begin ()->path ()); + + if (cmd == "cxx-tree") + { + try + { + sloc += CXX::Tree::Generator::generate ( + *tree_ops, + s, + path, + true, + string_literal_map, + disabled_w, + file_list, + unlinks); + } + catch (CXX::Tree::Generator::Failed const&) + { + // Diagnostic has already been issued. + // + return 1; + } + } + else if (cmd == "cxx-parser") + { + try + { + // Only generate driver for the first schema. + // + sloc += CXX::Parser::Generator::generate ( + *parser_ops, + s, + path, + true, + string_literal_map, + i == b, + disabled_w, + file_list, + unlinks); + } + catch (CXX::Parser::Generator::Failed const&) + { + // Diagnostic has already been issued. + // + return 1; + } + } + } + } + + // See if we need to produce the file list. + // + if (NarrowString fl = common_ops.file_list ()) + { + typedef std::ofstream OutputFileStream; + + try + { + OutputFileStream ofs; + SemanticGraph::Path path (fl); + + ofs.open (path.string ().c_str (), ios_base::out); + + if (!ofs.is_open ()) + { + wcerr << path << ": error: unable to open in write mode" << endl; + return 1; + } + + NarrowString d (common_ops.file_list_delim ()); + expand_nl (d); + + if (NarrowString p = common_ops.file_list_prologue ()) + { + expand_nl (p); + ofs << p; + } + + for (FileList::iterator i (file_list.begin ()), e (file_list.end ()); + i != e;) + { + ofs << *i; + + if (++i != e) + ofs << d; + } + + if (NarrowString e = common_ops.file_list_epilogue ()) + { + expand_nl (e); + ofs << e; + } + } + catch (SemanticGraph::InvalidPath const&) + { + wcerr << "error: '" << fl.c_str () << "' is not a valid " + << "filesystem path" << endl; + return 1; + } + } + + if (common_ops.show_sloc ()) + e << "total: " << sloc << endl; + + if (size_t sloc_limit = common_ops.sloc_limit ()) + { + if (sloc_limit < sloc) + { + e << "error: SLOC limit of " << sloc_limit + << " lines has been exceeded" << endl; + + return 1; + } + } + + unlinks.cancel (); + + return 0; + } + catch (LocationTranslator::Failed const&) + { + // Diagnostic has already been issued. + } + catch (AnonymousNameTranslator::Failed const&) + { + // Diagnostic has already been issued. + } + catch (SchemaPerTypeTranslator::Failed const&) + { + // Diagnostic has already been issued. + } + catch (Transformations::SchemaPerType::Failed const&) + { + // Diagnostic has already been issued. + } + catch (XSDFrontend::InvalidSchema const&) + { + // Diagnostic has already been issued. + } + catch (cli::exception const& ex) + { + wcerr << ex << endl; + wcerr << "try '" << argv[0] << " help' for usage information" << endl; + } + + return 1; +} + +// LocationTranslator +// + +LocationTranslator:: +LocationTranslator (NarrowStrings const& map, + NarrowStrings const& regex, + bool trace) + : trace_ (trace) +{ + // Map. + // + for (NarrowStrings::const_iterator i (map.begin ()); i != map.end (); ++i) + { + // Split the string in two parts at the last '='. + // + size_t pos (i->rfind ('=')); + + if (pos == NarrowString::npos) + { + wcerr << "error: invalid location map: '" << i->c_str () << + "': delimiter ('=') not found" << endl; + + throw Failed (); + } + + map_[NarrowString (*i, 0, pos)] = NarrowString (*i, pos + 1); + } + + // Regex. + // + for (NarrowStrings::const_iterator i (regex.begin ()); i != regex.end (); ++i) + { + try + { + regex_.push_back (Regex (*i)); + } + catch (RegexFormat const& e) + { + wcerr << "error: invalid location regex: '" << + e.regex ().c_str () << "': " << + e.description ().c_str () << endl; + + throw Failed (); + } + } +} + +NarrowString LocationTranslator:: +translate (NarrowString const& l) +{ + // First check the cache. + // + Cache::const_iterator ci (cache_.find (l)); + + if (ci != cache_.end ()) + return ci->second; + + // Then check the direct map. + // + Map::const_iterator mi (map_.find (l)); + + if (mi != map_.end ()) + { + cache_[l] = mi->second; + return mi->second; + } + + // Finally try regex. + // + if (trace_) + wcerr << "location: '" << l.c_str () << "'" << endl; + + for (RegexVector::reverse_iterator i (regex_.rbegin ()); + i != regex_.rend (); ++i) + { + if (trace_) + wcerr << "try: '" << i->regex ().str ().c_str () << "' : "; + + if (i->match (l)) + { + NarrowString r (i->replace (l)); + + if (trace_) + wcerr << "'" << r.c_str () << "' : +" << endl; + + cache_[l] = r; + return r; + } + + if (trace_) + wcerr << '-' << endl; + } + + // No match - return the original location. + // + cache_[l] = l; + return l; +} + +// AnonymousNameTranslator +// + +AnonymousNameTranslator:: +AnonymousNameTranslator (NarrowStrings const& regex, bool trace) + : trace_ (trace) +{ + for (NarrowStrings::const_iterator i (regex.begin ()); i != regex.end (); ++i) + { + try + { + regex_.push_back (Regex (String (*i))); + } + catch (RegexFormat const& e) + { + wcerr << "error: invalid anonymous type regex: '" << + e.regex () << "': " << e.description ().c_str () << endl; + + throw Failed (); + } + } +} + +String AnonymousNameTranslator:: +translate (String const& file, + String const& ns, + String const& name, + String const& xpath) +{ + String s (file + L' ' + ns + L' ' + xpath); + + if (trace_) + wcerr << "anonymous type: '" << s << "'" << endl; + + for (RegexVector::reverse_iterator i (regex_.rbegin ()); + i != regex_.rend (); ++i) + { + if (trace_) + wcerr << "try: '" << i->regex () << "' : "; + + if (i->match (s)) + { + String r (i->replace (s)); + + if (trace_) + wcerr << "'" << r << "' : +" << endl; + + return r; + } + + if (trace_) + wcerr << '-' << endl; + } + + // No match - return the name. + // + return name; +} + +// SchemaPerTypeTranslator +// + +SchemaPerTypeTranslator:: +SchemaPerTypeTranslator (NarrowStrings const& type_regex, + bool type_trace, + NarrowStrings const& schema_regex, + bool schema_trace) + : type_trace_ (type_trace), schema_trace_ (schema_trace) +{ + for (NarrowStrings::const_iterator i (type_regex.begin ()); + i != type_regex.end (); ++i) + { + try + { + type_regex_.push_back (TypeRegex (String (*i))); + } + catch (TypeRegexFormat const& e) + { + wcerr << "error: invalid type file regex: '" << + e.regex () << "': " << e.description ().c_str () << endl; + + throw Failed (); + } + } + + for (NarrowStrings::const_iterator i (schema_regex.begin ()); + i != schema_regex.end (); ++i) + { + try + { + schema_regex_.push_back (SchemaRegex (*i)); + } + catch (SchemaRegexFormat const& e) + { + wcerr << "error: invalid type file regex: '" << + e.regex ().c_str () << "': " << e.description ().c_str () << endl; + + throw Failed (); + } + } +} + +String SchemaPerTypeTranslator:: +translate_type (String const& ns, String const& name) +{ + String s (ns + L' ' + name); + + if (type_trace_) + wcerr << "type: '" << s << "'" << endl; + + for (TypeRegexVector::reverse_iterator i (type_regex_.rbegin ()); + i != type_regex_.rend (); ++i) + { + if (type_trace_) + wcerr << "try: '" << i->regex () << "' : "; + + if (i->match (s)) + { + String r (i->replace (s)); + + if (type_trace_) + wcerr << "'" << r << "' : +" << endl; + + return r; + } + + if (type_trace_) + wcerr << '-' << endl; + } + + // No match - return empty string. + // + return L""; +} + +NarrowString SchemaPerTypeTranslator:: +translate_schema (NarrowString const& file) +{ + if (schema_trace_) + wcerr << "schema: '" << file.c_str () << "'" << endl; + + for (SchemaRegexVector::reverse_iterator i (schema_regex_.rbegin ()); + i != schema_regex_.rend (); ++i) + { + if (schema_trace_) + wcerr << "try: '" << i->regex ().str ().c_str () << "' : "; + + if (i->match (file)) + { + NarrowString r (i->replace (file)); + + if (schema_trace_) + wcerr << "'" << r.c_str () << "' : +" << endl; + + return r; + } + + if (schema_trace_) + wcerr << '-' << endl; + } + + // No match - return empty string. + // + return ""; +} + +// +// +void +expand_nl (NarrowString& s) +{ + for (size_t i (0); i < s.size ();) + { + if (s[i] == '\\' && (i + 1) < s.size () && s[i + 1] == 'n') + { + NarrowString tmp (s, 0, i); + tmp += '\n'; + tmp.append (s.c_str () + i + 2); + s = tmp; + } + else + ++i; + } +} -- cgit v1.1