// file : xsd/xsd.cxx // copyright : Copyright (c) 2005-2014 Code Synthesis Tools CC // license : GNU GPL v2 + exceptions; see accompanying LICENSE file #include #include #include // std::auto_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 "../libxsd/xsd/cxx/version.hxx" 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); 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_STR_VERSION << endl << "Copyright (c) 2005-2014 Code Synthesis Tools CC" << 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. // auto_ptr tree_ops ( cmd == "cxx-tree" ? new CXX::Tree::options (args) : 0); auto_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); auto_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); auto_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; } }