/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2021 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #ifdef HAVE_LIBXML2 // mapnik #include #include #include #include #include #include // libxml #include #include #include #include // stl #include #define DEFAULT_OPTIONS (XML_PARSE_NOERROR | XML_PARSE_NOENT | XML_PARSE_NOBLANKS | XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA) namespace mapnik { class libxml2_loader : util::noncopyable { public: libxml2_loader(const char *encoding = nullptr, int options = DEFAULT_OPTIONS, const char *url = nullptr) : ctx_(0), encoding_(encoding), options_(options), url_(url) { LIBXML_TEST_VERSION; ctx_ = xmlNewParserCtxt(); if (!ctx_) { throw std::runtime_error("Failed to create parser context."); } } ~libxml2_loader() { if (ctx_) { xmlFreeParserCtxt(ctx_); } } void load(std::string const& filename, xml_node &node) { if (!mapnik::util::exists(filename)) { throw config_error(std::string("Could not load map file: File does not exist"), 0, filename); } xmlDocPtr doc = xmlCtxtReadFile(ctx_, filename.c_str(), encoding_, options_); if (!doc) { xmlError * error = xmlCtxtGetLastError(ctx_); if (error) { std::string msg("XML document not well formed:\n"); msg += error->message; // remove CR msg = msg.substr(0, msg.size() - 1); throw config_error(msg, error->line, error->file); } } load(doc, node); } void load(const int fd, xml_node &node) { xmlDocPtr doc = xmlCtxtReadFd(ctx_, fd, url_, encoding_, options_); load(doc, node); } void load_string(std::string const& buffer, xml_node &node, std::string const & base_path) { if (!base_path.empty()) { if (!mapnik::util::exists(base_path)) { throw config_error(std::string("Could not locate base_path '") + base_path + "': file or directory does not exist"); } } // NOTE: base_path here helps libxml2 resolve entities correctly: https://github.com/mapnik/mapnik/issues/440 xmlDocPtr doc = xmlCtxtReadMemory(ctx_, buffer.data(), buffer.length(), base_path.c_str(), encoding_, options_); load(doc, node); } void load(const xmlDocPtr doc, xml_node &node) { if (!doc) { std::string msg("XML document not well formed"); xmlError * error = xmlCtxtGetLastError( ctx_ ); if (error) { msg += ":\n"; msg += error->message; throw config_error(msg, error->line, error->file); } else { throw config_error(msg); } } int iXIncludeReturn = xmlXIncludeProcessFlags(doc, options_); if (iXIncludeReturn < 0) { xmlFreeDoc(doc); throw config_error("XML XInclude error. One or more files failed to load."); } xmlNode * root = xmlDocGetRootElement(doc); if (!root) { xmlFreeDoc(doc); throw config_error("XML document is empty."); } populate_tree(root, node); xmlFreeDoc(doc); } private: void inline append_attributes(xmlAttr *attributes, xml_node & node) { for (; attributes; attributes = attributes->next ) { node.add_attribute(reinterpret_cast(attributes->name), reinterpret_cast(attributes->children->content)); } } void inline populate_tree(xmlNode *cur_node, xml_node &node) { for (; cur_node; cur_node = cur_node->next ) { switch (cur_node->type) { case XML_ELEMENT_NODE: { xml_node &new_node = node.add_child(reinterpret_cast(cur_node->name), cur_node->line, false); append_attributes(cur_node->properties, new_node); populate_tree(cur_node->children, new_node); } break; case XML_TEXT_NODE: { std::string trimmed(reinterpret_cast(cur_node->content)); mapnik::util::trim(trimmed); if (trimmed.empty()) break; //Don't add empty text nodes node.add_child(trimmed.c_str(), cur_node->line, true); } break; case XML_COMMENT_NODE: break; default: break; } } } xmlParserCtxtPtr ctx_; const char *encoding_; int options_; const char *url_; }; void read_xml(std::string const & filename, xml_node &node) { libxml2_loader loader; loader.load(filename, node); } void read_xml_string(std::string const & str, xml_node &node, std::string const & base_path) { libxml2_loader loader; loader.load_string(str, node, base_path); } } // end of namespace mapnik #endif