/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include "thrift/platform.h" #include "thrift/generate/t_generator.h" using std::map; using std::ofstream; using std::ostream; using std::ostringstream; using std::string; using std::stringstream; using std::vector; using std::stack; using std::set; static const string default_ns_prefix = "http://thrift.apache.org/xml/ns/"; /** * This generator creates an XML model of the parsed IDL tree, and is designed * to make it easy to use this file as the input for other template engines, * such as XSLT. To this end, the generated XML is slightly more verbose than * you might expect... for example, references to "id" types (such as structs, * unions, etc) always specify the name of the IDL document, even if the type * is defined in the same document as the reference. */ class t_xml_generator : public t_generator { public: t_xml_generator( t_program* program, const std::map& parsed_options, const std::string& option_string) : t_generator(program) { (void)option_string; std::map::const_iterator iter; should_merge_includes_ = false; should_use_default_ns_ = true; should_use_namespaces_ = true; for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { if( iter->first.compare("merge") == 0) { should_merge_includes_ = true; } else if( iter->first.compare("no_default_ns") == 0) { should_use_default_ns_ = false; } else if( iter->first.compare("no_namespaces") == 0) { should_use_namespaces_ = false; } else { throw "unknown option xml:" + iter->first; } } out_dir_base_ = "gen-xml"; } ~t_xml_generator() override = default; void init_generator() override; void close_generator() override; std::string display_name() const override; void generate_program() override; void iterate_program(t_program* program); void generate_typedef(t_typedef* ttypedef) override; void generate_enum(t_enum* tenum) override; void generate_function(t_function* tfunc); void generate_field(t_field* field); void generate_service(t_service* tservice) override; void generate_struct(t_struct* tstruct) override; void generate_annotations(std::map> annotations); private: bool should_merge_includes_; bool should_use_default_ns_; bool should_use_namespaces_; ofstream_with_content_based_conditional_update f_xml_; std::set programs_; std::stack elements_; bool top_element_is_empty; bool top_element_is_open; string target_namespace(t_program* program); void write_element_start(const string name); void close_top_element(); void write_element_end(); void write_attribute(string key, string val); void write_int_attribute(string key, int val); string escape_xml_string(const string& input); void write_xml_comment(string msg); void write_type(t_type* ttype); void write_doc(t_doc* tdoc); template string number_to_string(T t) { std::ostringstream out; out.imbue(std::locale::classic()); out.precision(std::numeric_limits::digits10); out << t; return out.str(); } template void write_number(T n) { f_xml_ << number_to_string(n); } template void write_element_number(string name, T n) { write_element_string(name, number_to_string(n)); } string get_type_name(t_type* ttype); void generate_constant(t_const* con); void write_element_string(string name, string value); void write_value(t_type* tvalue); void write_const_value(t_const_value* value); virtual std::string xml_autogen_comment() { return std::string("\n") + " * Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + " *\n" + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"; } }; void t_xml_generator::init_generator() { MKDIR(get_out_dir().c_str()); string f_xml_name = get_out_dir() + program_->get_name() + ".xml"; f_xml_.open(f_xml_name.c_str()); top_element_is_open = false; } string t_xml_generator::target_namespace(t_program* program) { std::map map; std::map::iterator iter; std::map> annotations; std::map>::iterator annotations_iter; annotations = program->get_namespace_annotations("xml"); if ((annotations_iter = annotations.find("targetNamespace")) != annotations.end()) { if (!annotations_iter->second.empty()) { return annotations_iter->second.back(); } } map = program->get_namespaces(); if ((iter = map.find("xml")) != map.end()) { return default_ns_prefix + iter->second; } annotations = program->get_namespace_annotations("*"); if ((annotations_iter = annotations.find("xml.targetNamespace")) != annotations.end()) { if (!annotations_iter->second.empty()) { return annotations_iter->second.back(); } } map = program->get_namespaces(); if ((iter = map.find("*")) != map.end()) { return default_ns_prefix + iter->second; } return default_ns_prefix + program->get_name(); } void t_xml_generator::write_xml_comment(string msg) { close_top_element(); // TODO: indent any EOLs that may occur with msg // TODO: proper msg escaping needed? f_xml_ << indent() << "" << '\n'; top_element_is_empty = false; } void t_xml_generator::close_top_element() { if( top_element_is_open) { top_element_is_open = false; if (elements_.size() > 0 && top_element_is_empty) { f_xml_ << ">" << '\n'; } } } void t_xml_generator::write_element_start(string name) { if (should_use_namespaces_ && !should_use_default_ns_) { name = "idl:" + name; } close_top_element(); f_xml_ << indent() << "<" << name; elements_.push(name); top_element_is_empty = true; top_element_is_open = true; indent_up(); } void t_xml_generator::write_element_end() { indent_down(); if (top_element_is_empty && top_element_is_open) { f_xml_ << " />" << '\n'; } else { f_xml_ << indent() << "" << '\n'; } top_element_is_empty = false; elements_.pop(); } void t_xml_generator::write_attribute(string key, string val) { f_xml_ << " " << key << "=\"" << escape_xml_string(val) << "\""; } void t_xml_generator::write_int_attribute(string key, int val) { write_attribute(key, number_to_string(val)); } void t_xml_generator::write_element_string(string name, string val) { if (should_use_namespaces_ && !should_use_default_ns_) { name = "idl:" + name; } close_top_element(); top_element_is_empty = false; f_xml_ << indent() << "<" << name << ">" << escape_xml_string(val) << "" << '\n'; } string t_xml_generator::escape_xml_string(const string& input) { std::ostringstream ss; for (char iter : input) { switch (iter) { case '&': ss << "&"; break; case '"': ss << """; break; case '\'': ss << "'"; break; case '<': ss << "<"; break; case '>': ss << ">"; break; default: ss << iter; break; } } return ss.str(); } void t_xml_generator::close_generator() { f_xml_.close(); } void t_xml_generator::generate_program() { init_generator(); write_element_start("idl"); if (should_use_namespaces_) { if (should_use_default_ns_) { write_attribute("xmlns", "http://thrift.apache.org/xml/idl"); } write_attribute("xmlns:idl", "http://thrift.apache.org/xml/idl"); } write_xml_comment( xml_autogen_comment()); iterate_program(program_); write_element_end(); close_generator(); } void t_xml_generator::iterate_program(t_program* program) { write_element_start("document"); write_attribute("name", program->get_name()); if (should_use_namespaces_) { const string targetNamespace = target_namespace(program); write_attribute("targetNamespace", targetNamespace); write_attribute("xmlns:" + program->get_name(), targetNamespace); } write_doc(program); const vector includes = program->get_includes(); vector::const_iterator inc_it; for (inc_it = includes.begin(); inc_it != includes.end(); ++inc_it) { write_element_start("include"); write_attribute("name", (*inc_it)->get_name()); write_element_end(); } const map& namespaces = program->get_namespaces(); map::const_iterator ns_it; for (ns_it = namespaces.begin(); ns_it != namespaces.end(); ++ns_it) { write_element_start("namespace"); write_attribute("name", ns_it->first); write_attribute("value", ns_it->second); generate_annotations(program->get_namespace_annotations(ns_it->first)); write_element_end(); } // TODO: can constants have annotations? vector consts = program->get_consts(); vector::iterator c_iter; for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { generate_constant(*c_iter); } vector typedefs = program->get_typedefs(); vector::iterator td_iter; for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { generate_typedef(*td_iter); } vector enums = program->get_enums(); vector::iterator en_iter; for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { generate_enum(*en_iter); } vector objects = program->get_objects(); vector::iterator o_iter; for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { if ((*o_iter)->is_xception()) { generate_xception(*o_iter); } else { generate_struct(*o_iter); } } vector services = program->get_services(); vector::iterator sv_iter; for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { generate_service(*sv_iter); } write_element_end(); if (should_merge_includes_) { programs_.insert(program->get_name()); const vector programs = program->get_includes(); vector::const_iterator prog_it; for (prog_it = programs.begin(); prog_it != programs.end(); ++prog_it) { if (!programs_.count((*prog_it)->get_name())) { iterate_program(*prog_it); } } } } void t_xml_generator::generate_typedef(t_typedef* ttypedef) { write_element_start("typedef"); write_attribute("name", ttypedef->get_name()); write_doc(ttypedef); write_type(ttypedef->get_true_type()); generate_annotations(ttypedef->annotations_); write_element_end(); return; } void t_xml_generator::write_type(t_type* ttype) { const string type = get_type_name(ttype); write_attribute("type", type); if (type == "id") { write_attribute("type-module", ttype->get_program()->get_name()); write_attribute("type-id", ttype->get_name()); } else if (type == "list") { t_type* etype = ((t_list*)ttype)->get_elem_type(); write_element_start("elemType"); write_type(etype); write_element_end(); } else if (type == "set") { t_type* etype = ((t_set*)ttype)->get_elem_type(); write_element_start("elemType"); write_type(etype); write_element_end(); } else if (type == "map") { t_type* ktype = ((t_map*)ttype)->get_key_type(); write_element_start("keyType"); write_type(ktype); write_element_end(); t_type* vtype = ((t_map*)ttype)->get_val_type(); write_element_start("valueType"); write_type(vtype); write_element_end(); } } void t_xml_generator::write_doc(t_doc* tdoc) { if (tdoc->has_doc()) { string doc = tdoc->get_doc(); // for some reason there always seems to be a trailing newline on doc // comments; loop below naively tries to strip off trailing cr/lf int n = 0; for (string::reverse_iterator i = doc.rbegin(); i != doc.rend(); i++,n++) { if (*i != '\n' || *i == '\r') { if (n > 0) { doc.erase(doc.length() - n); } break; } } write_attribute("doc", doc); } } void t_xml_generator::generate_annotations( std::map> annotations) { std::map>::iterator iter; for (iter = annotations.begin(); iter != annotations.end(); ++iter) { for (auto& annotations_value : iter->second) { write_element_start("annotation"); write_attribute("key", iter->first); write_attribute("value", annotations_value); write_element_end(); } } } void t_xml_generator::generate_constant(t_const* con) { write_element_start("const"); write_attribute("name", con->get_name()); write_doc(con); write_type(con->get_type()); write_const_value(con->get_value()); write_element_end(); } void t_xml_generator::write_const_value(t_const_value* value) { switch (value->get_type()) { case t_const_value::CV_IDENTIFIER: case t_const_value::CV_INTEGER: write_element_number("int", value->get_integer()); break; case t_const_value::CV_DOUBLE: write_element_number("double", value->get_double()); break; case t_const_value::CV_STRING: write_element_string("string", value->get_string()); break; case t_const_value::CV_LIST: { write_element_start("list"); std::vector list = value->get_list(); std::vector::iterator lit; for (lit = list.begin(); lit != list.end(); ++lit) { write_element_start("entry"); write_const_value(*lit); write_element_end(); } write_element_end(); break; } case t_const_value::CV_MAP: { write_element_start("map"); std::map map = value->get_map(); std::map::iterator mit; for (mit = map.begin(); mit != map.end(); ++mit) { write_element_start("entry"); write_element_start("key"); write_const_value(mit->first); write_element_end(); write_element_start("value"); write_const_value(mit->second); write_element_end(); write_element_end(); } write_element_end(); break; } default: indent_up(); f_xml_ << indent() << "" << '\n'; indent_down(); break; } } void t_xml_generator::generate_enum(t_enum* tenum) { write_element_start("enum"); write_attribute("name", tenum->get_name()); write_doc(tenum); vector values = tenum->get_constants(); vector::iterator val_iter; for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { t_enum_value* val = (*val_iter); write_element_start("member"); write_attribute("name", val->get_name()); write_int_attribute("value", val->get_value()); write_doc(val); generate_annotations(val->annotations_); write_element_end(); } generate_annotations(tenum->annotations_); write_element_end(); } void t_xml_generator::generate_struct(t_struct* tstruct) { string tagname = "struct"; if (tstruct->is_union()) { tagname = "union"; } else if (tstruct->is_xception()) { tagname = "exception"; } write_element_start(tagname); write_attribute("name", tstruct->get_name()); write_doc(tstruct); vector members = tstruct->get_members(); vector::iterator mem_iter; for (mem_iter = members.begin(); mem_iter != members.end(); mem_iter++) { write_element_start("field"); generate_field(*mem_iter); write_element_end(); } generate_annotations(tstruct->annotations_); write_element_end(); } void t_xml_generator::generate_field(t_field* field) { write_attribute("name", field->get_name()); write_int_attribute("field-id", field->get_key()); write_doc(field); string requiredness; switch (field->get_req()) { case t_field::T_REQUIRED: requiredness = "required"; break; case t_field::T_OPTIONAL: requiredness = "optional"; break; default: requiredness = ""; break; } if (requiredness != "") { write_attribute("required", requiredness); } write_type(field->get_type()); if (field->get_value()) { write_element_start("default"); write_const_value(field->get_value()); write_element_end(); } generate_annotations(field->annotations_); } void t_xml_generator::generate_service(t_service* tservice) { write_element_start("service"); write_attribute("name", tservice->get_name()); if (should_use_namespaces_) { string prog_ns = target_namespace(tservice->get_program()); if (*prog_ns.rbegin() != '/') { prog_ns.push_back('/'); } const string tns = prog_ns + tservice->get_name(); write_attribute("targetNamespace", tns); write_attribute("xmlns:tns", tns); } if (tservice->get_extends()) { const t_service* extends = tservice->get_extends(); write_attribute("parent-module", extends->get_program()->get_name()); write_attribute("parent-id", extends->get_name()); } write_doc(tservice); vector functions = tservice->get_functions(); vector::iterator fn_iter = functions.begin(); for (; fn_iter != functions.end(); fn_iter++) { generate_function(*fn_iter); } generate_annotations(tservice->annotations_); write_element_end(); } void t_xml_generator::generate_function(t_function* tfunc) { write_element_start("method"); write_attribute("name", tfunc->get_name()); if (tfunc->is_oneway()) { write_attribute("oneway", "true"); } write_doc(tfunc); write_element_start("returns"); write_type(tfunc->get_returntype()); write_element_end(); vector members = tfunc->get_arglist()->get_members(); vector::iterator mem_iter = members.begin(); for (; mem_iter != members.end(); mem_iter++) { write_element_start("arg"); generate_field(*mem_iter); write_element_end(); } vector excepts = tfunc->get_xceptions()->get_members(); vector::iterator ex_iter = excepts.begin(); for (; ex_iter != excepts.end(); ex_iter++) { write_element_start("throws"); generate_field(*ex_iter); write_element_end(); } generate_annotations(tfunc->annotations_); write_element_end(); } string t_xml_generator::get_type_name(t_type* ttype) { if (ttype->is_list()) { return "list"; } if (ttype->is_set()) { return "set"; } if (ttype->is_map()) { return "map"; } if ((ttype->is_enum() )|| (ttype->is_struct() )|| (ttype->is_typedef() )|| (ttype->is_xception())){ return "id"; } if (ttype->is_base_type()) { t_base_type* tbasetype = (t_base_type*)ttype; if (tbasetype->is_binary() ) { return "binary"; } return t_base_type::t_base_name(tbasetype->get_base()); } return "(unknown)"; } std::string t_xml_generator::display_name() const { return "XML"; } THRIFT_REGISTER_GENERATOR( xml, "XML", " merge: Generate output with included files merged\n" " no_default_ns: Omit default xmlns and add idl: prefix to all elements\n" " no_namespaces: Do not add namespace definitions to the XML model\n")