/***************************************************************************
* GXml.cpp - XML class *
* ----------------------------------------------------------------------- *
* copyright (C) 2010-2016 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* 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, either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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 . *
* *
***************************************************************************/
/**
* @file GXml.cpp
* @brief XML class implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include "GTools.hpp"
#include "GException.hpp"
#include "GFilename.hpp"
#include "GUrlFile.hpp"
#include "GUrlString.hpp"
#include "GXml.hpp"
#include "GXmlNode.hpp"
#include "GXmlDocument.hpp"
#include "GXmlText.hpp"
#include "GXmlElement.hpp"
#include "GXmlComment.hpp"
#include "GXmlPI.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_LOAD "GXml::load(std::string&)"
#define G_PARSE "GXml::parse(GUrl&)"
#define G_PROCESS "GXml::process(GXmlNode*, const std::string&)"
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/* __ Debug definitions __________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
GXml::GXml(void)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] xml XML object.
***************************************************************************/
GXml::GXml(const GXml& xml)
{
// Initialise members
init_members();
// Copy members
copy_members(xml);
// Return
return;
}
/***********************************************************************//**
* @brief XML document constructor
*
* @param[in] xml XML text string or file name.
*
* Constructs GXml object by either parsing a text string or a file. If the
* @p xml argument starts with @p param[1] > value > struct
*
* The > symbols indicate subsequent hierarchy levels, the square brackets
* provides the index in case that multiple tags with the same name exist
* at a given hierarchy level. Omitting the index means that the first
* tag with the specified name is accessed.
***************************************************************************/
GXmlElement* GXml::element(const std::string& name)
{
// Return pointer
return m_root.element(name);
}
/***********************************************************************//**
* @brief Return pointer to child element by hierarchy (const version)
*
* @param[in] name Child element hierarchy.
* @return Pointer to child element.
*
* Returns a pointer to the child element described by a hierarchy of the
* following syntax
*
* params > param[1] > value > struct
*
* The > symbols indicate subsequent hierarchy levels, the square brackets
* provides the index in case that multiple tags with the same name exist
* at a given hierarchy level. Omitting the index means that the first
* tag with the specified name is accessed.
***************************************************************************/
const GXmlElement* GXml::element(const std::string& name) const
{
// Return pointer
return m_root.element(name);
}
/***********************************************************************//**
* @brief Return pointer to child element of a given name
*
* @param[in] name Name of child element.
* @param[in] index Node index [0,...,elements()-1].
* @return Pointer to child element.
*
* Returns a pointer to the child number @p index with @p name of the XML
* document root. An exception will be thrown if the @p index is not valid.
***************************************************************************/
GXmlElement* GXml::element(const std::string& name, const int& index)
{
// Return pointer
return m_root.element(name, index);
}
/***********************************************************************//**
* @brief Return pointer to child element of a given name (const variant)
*
* @param[in] name Name of child element.
* @param[in] index Node index [0,...,elements()-1].
* @return Pointer to child element.
*
* Returns a pointer to the child number @p index with @p name of the XML
* document root. An exception will be thrown if the @p index is not valid.
***************************************************************************/
const GXmlElement* GXml::element(const std::string& name, const int& index) const
{
// Return pointer
return m_root.element(name, index);
}
/***********************************************************************//**
* @brief Load XML document from file
*
* @param[in] filename File name.
*
* Loads a XML document from a file by reading from the file's Unified
* Resource Locator (URL). The read() method is invoked for this purpose.
*
* The method uses the GUrlFile file opening constructor to open the URL.
* This constructor will automatically expand any environment variables that
* are present in the filename.
*
* @todo Ideally, we would like to extract the URL type from the filename
* so that any kind of URL can be used for loading.
***************************************************************************/
void GXml::load(const GFilename& filename)
{
// Throw an exception if file does not exist
if (!filename.exists()) {
throw GException::file_open_error(G_LOAD, filename);
}
// Open XML URL as file for reading
GUrlFile url(filename.url().c_str(), "r");
// Read XML document from URL
read(url);
// Close URL
url.close();
// Store filename in XML document
m_root.filename(filename);
// Return
return;
}
/***********************************************************************//**
* @brief Save XML document into file
*
* @param[in] filename File name.
*
* Saves the XML document into a file by writing into the file's Unified
* Resource Locator (URL). The write() method is invoked for this purpose.
*
* The method uses the GUrlFile file opening constructor to open the URL.
* This constructor will automatically expand any environment variables that
* are present in the filename.
*
* @todo Ideally, we would like to extract the URL type from the filename
* so that any kind of URL can be used for loading.
***************************************************************************/
void GXml::save(const GFilename& filename) const
{
// Open XML file for writing
GUrlFile url(filename.url().c_str(), "w");
// Store filename in XML document (circumvent const correctness)
const_cast(&m_root)->filename(filename);
// Write XML document
write(url, 0);
// Close file
url.close();
// Return
return;
}
/***********************************************************************//**
* @brief Read XML document from URL
*
* @param[in] url Unified Resource Locator.
*
* Reads in the XML document by parsing a Unified Resource Locator of any
* type.
***************************************************************************/
void GXml::read(const GUrl& url)
{
// Clear object
clear();
// Parse URL
parse(url);
// Return
return;
}
/***********************************************************************//**
* @brief Write XML document into URL
*
* @param[in] url Unified Resource Locator.
* @param[in] indent Indentation (default = 0).
*
* Writes the XML document in a Unified Resource Locator. Formatting of the
* document can be adapted using the @p indent parameter.
***************************************************************************/
void GXml::write(GUrl& url, const int& indent) const
{
// Write XML document
m_root.write(url);
// Return
return;
}
/***********************************************************************//**
* @brief Print XML object
*
* @param[in] chatter Chattiness.
* @param[in] indent Text indentation.
* @return String containing XML object.
***************************************************************************/
std::string GXml::print(const GChatter& chatter, const int& indent) const
{
// Initialise result string
std::string result;
// Continue only if chatter is not silent
if (chatter != SILENT) {
// Append header
result.append("=== GXml ===");
// Append model
result.append("\n"+m_root.print(chatter, 0));
} // endif: chatter was not silent
// Return result
return result;
}
/***********************************************************************//**
* @brief Print XML object
*
* @param[in] chatter Chattiness.
* @return String containing XML object.
***************************************************************************/
std::string GXml::print(const GChatter& chatter) const
{
// Set result string
std::string result = print(chatter, 0);
// Return result
return result;
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void GXml::init_members(void)
{
// Initialise members
m_root.clear();
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] xml Object from which members which should be copied.
***************************************************************************/
void GXml::copy_members(const GXml& xml)
{
// Copy attributes
m_root = xml.m_root;
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
***************************************************************************/
void GXml::free_members(void)
{
// Return
return;
}
/***********************************************************************//**
* @brief Parse XML URL
*
* @param[in] url Unified Resource Locator.
*
* @exception GException::xml_syntax_error
* XML syntax error.
*
* Parses either a XML file or a XML text string and creates all associated
* nodes. The XML file is split into segments, made either of text or of
* tags.
***************************************************************************/
void GXml::parse(const GUrl& url)
{
// Initialise parser
int c;
bool in_markup = false;
bool in_comment = false;
std::string segment;
GXmlNode* current = &m_root;
// Main parsing loop
while ((c = url.get_char()) != EOF) {
// Convert special characters into line feeds
if (c == '\x85' || c == L'\x2028') {
if (in_markup) {
throw GException::xml_syntax_error(G_PARSE, segment,
"invalid character encountered");
}
else {
c = '\x0a';
}
}
// Skip all linefeeds (to avoid extra linefeeds in text segments)
if (c == '\x0a') {
continue;
}
// If we are not within a markup and if a markup is reached then
// add the text segment to the nodes and switch to in_markup mode
if (in_markup == false) {
// Markup start reached?
if (c == '<') {
// Add text segment to nodes (ignores empty segments)
process_text(¤t, segment);
// Prepare new segment and signal that we are within tag
segment.clear();
segment.append(1, (char)c);
in_markup = true;
}
// Markup stop encountered?
else if (c == '>') {
segment.append(1, (char)c);
throw GException::xml_syntax_error(G_PARSE, segment,
"unexpected closing bracket \">\" encountered");
}
// ... otherwise add character to segment
else {
segment.append(1, (char)c);
}
}
// If we are within a markup and if a markup end is reached then
// process the markup and switch to not in_tag mode
else {
// Markup stop reached?
if (c == '>') {
// Append character to segment
segment.append(1, (char)c);
// If we are in comment then check if this is the end of
// the comment
if (in_comment) {
int n = segment.length();
if (n > 2) {
if (segment.compare(n-3,3,"-->") == 0) {
in_comment = false;
}
}
}
// If we are not in the comment, then process markup
if (!in_comment) {
// Process markup
process_markup(¤t, segment);
// Prepare new segment and signal that we are not
// within markup
segment.clear();
in_markup = false;
}
}
// Markup start encountered?
else if (!in_comment && c == '<') {
// Append character to segment
segment.append(1, (char)c);
// If we encounter an opening bracket then throw an exception
throw GException::xml_syntax_error(G_PARSE, segment,
"unexpected opening bracket \"<\" encountered");
}
// ... otherwise add character to segment
else {
segment.append(1, (char)c);
if (!in_comment && segment == "") == 0)) {
type = MT_COMMENT;
}
// Check for declaration
else if (n >= 7 && (segment.compare(0,6,"") == 0)) {
type = MT_DECLARATION;
}
// Check for processing instruction
else if (n >= 4 && (segment.compare(0,2,"") == 0) &&
(segment.compare(n-2,2,"?>") == 0)) {
type = MT_PROCESSING;
}
// Check for empty element tag
else if (n >= 3 && (segment.compare(0,1,"<") == 0) &&
(segment.compare(n-2,2,"/>") == 0)) {
type = MT_ELEMENT_EMPTY;
}
// Check for element end tag
else if (n >= 3 && (segment.compare(0,2,"") == 0) &&
(segment.compare(n-1,1,">") == 0)) {
type = MT_ELEMENT_END;
}
// Check for element start tag
else if (n >= 2 && (segment.compare(0,1,"<") == 0) &&
(segment.compare(n-1,1,">") == 0)) {
type = MT_ELEMENT_START;
}
// Return type
return type;
}