/*
Ousía
Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "XmlParser.hpp"
namespace ousia {
using namespace ousia::model;
/* Document structure */
static const State STATE_DOCUMENT = 0;
static const State STATE_HEAD = 1;
static const State STATE_BODY = 2;
/* Special commands */
static const State STATE_USE = 100;
static const State STATE_INCLUDE = 101;
static const State STATE_INLINE = 102;
/* Type system definitions */
static const State STATE_TYPESYSTEM = 200;
static const State STATE_TYPES = 201;
static const State STATE_CONSTANTS = 202;
static const State STATE_CONSTANT = 203;
static const State STATE_ENUM = 204;
static const State STATE_STRUCT = 205;
static const State STATE_FIELD = 206;
class TypesystemHandler : public Handler {
public:
using Handler::Handler;
void start(Variant::mapType &args) override
{
scope().push(project()->createTypesystem(args["name"].asString()));
}
void end() override
{
scope().performDeferredResolution(logger());
// TODO: Automatically call validate in "pop"?
scope().getLeaf()->validate(logger());
scope().pop();
}
static Handler *create(const HandlerData &handlerData)
{
return new TypesystemHandler{handlerData};
}
};
class StructHandler : public Handler {
public:
using Handler::Handler;
void start(Variant::mapType &args) override
{
// Fetch the arguments used for creating this type
const std::string &name = args["name"].asString();
const std::string &parent = args["parent"].asString();
// Fetch the current typesystem and create the struct node
Rooted typesystem = scope().getLeaf().cast();
Rooted structType = typesystem->createStructType(name);
// Try to resolve the parent type and set it as parent structure
if (!parent.empty()) {
scope().resolve(parent, logger(),
[structType](Handle parent,
Logger &logger) mutable {
structType->setParentStructure(
parent, logger);
},
location());
}
// Descend into the struct type
scope().push(structType);
}
void end() override
{
// Descend from the struct type
scope().pop();
}
static Handler *create(const HandlerData &handlerData)
{
return new StructHandler{handlerData};
}
};
class StructFieldHandler : public Handler {
public:
using Handler::Handler;
void start(Variant::mapType &args) override
{
// Read the argument values
const std::string &name = args["name"].asString();
const std::string &type = args["type"].asString();
const Variant &defaultValue = args["default"];
const bool optional =
!(defaultValue.isObject() && defaultValue.asObject() == nullptr);
Rooted structType = scope().getLeaf().cast();
Rooted attribute = structType->createAttribute(
name, defaultValue, optional, logger());
// Try to resolve the type
scope().resolve(type, logger(),
[attribute](Handle type, Logger &logger) mutable {
attribute->setType(type, logger);
},
location());
}
void end() override {}
static Handler *create(const HandlerData &handlerData)
{
return new StructFieldHandler{handlerData};
}
};
static const std::multimap XML_HANDLERS{
/* Document tags */
{"document", {{STATE_NONE}, nullptr, STATE_DOCUMENT}},
{"head", {{STATE_DOCUMENT}, nullptr, STATE_HEAD}},
{"body", {{STATE_DOCUMENT}, nullptr, STATE_BODY, true}},
/* Special commands */
{"use", {{STATE_HEAD}, nullptr, STATE_USE}},
{"include", {{STATE_ALL}, nullptr, STATE_INCLUDE}},
{"inline", {{STATE_ALL}, nullptr, STATE_INLINE}},
/* Typesystem */
{"typesystem",
{{STATE_NONE, STATE_HEAD},
TypesystemHandler::create,
STATE_TYPESYSTEM,
false,
{Argument::String("name")}}},
{"types", {{STATE_TYPESYSTEM}, nullptr, STATE_TYPES}},
{"enum", {{STATE_TYPES}, nullptr, STATE_ENUM}},
{"struct",
{{STATE_TYPES},
StructHandler::create,
STATE_STRUCT,
false,
{Argument::String("name"), Argument::String("parent", "")}}},
{"field",
{{{STATE_STRUCT}},
StructFieldHandler::create,
STATE_FIELD,
false,
{Argument::String("name"), Argument::String("type"),
Argument::Any("default", Variant::fromObject(nullptr))}}},
{"constants", {{STATE_TYPESYSTEM}, nullptr, STATE_CONSTANTS}},
{"constant", {{STATE_CONSTANTS}, nullptr, STATE_CONSTANT}}};
/**
* Wrapper class around the XML_Parser pointer which safely frees it whenever
* the scope is left (e.g. because an exception was thrown).
*/
class ScopedExpatXmlParser {
private:
/**
* Internal pointer to the XML_Parser instance.
*/
XML_Parser parser;
public:
/**
* Constructor of the ScopedExpatXmlParser class. Calls XML_ParserCreateNS
* from the expat library. Throws a parser exception if the XML parser
* cannot be initialized.
*
* @param encoding is the protocol-defined encoding passed to expat (or
* nullptr if expat should determine the encoding by itself).
*/
ScopedExpatXmlParser(const XML_Char *encoding) : parser(nullptr)
{
parser = XML_ParserCreate(encoding);
if (!parser) {
throw LoggableException{
"Internal error: Could not create expat XML parser!"};
}
}
/**
* Destuctor of the ScopedExpatXmlParser, frees the XML parser instance.
*/
~ScopedExpatXmlParser()
{
if (parser) {
XML_ParserFree(parser);
parser = nullptr;
}
}
/**
* Returns the XML_Parser pointer.
*/
XML_Parser operator&() { return parser; }
};
/* Adapter Expat -> ParserStack */
static SourceLocation syncLoggerPosition(XML_Parser p)
{
// Fetch the current location in the XML file
int line = XML_GetCurrentLineNumber(p);
int column = XML_GetCurrentColumnNumber(p);
size_t offs = XML_GetCurrentByteIndex(p);
SourceLocation loc{line, column, offs};
// Update the default location of the current logger instance
ParserStack *stack = static_cast(XML_GetUserData(p));
stack->getContext().logger.setDefaultLocation(loc);
return loc;
}
static void xmlStartElementHandler(void *p, const XML_Char *name,
const XML_Char **attrs)
{
XML_Parser parser = static_cast(p);
SourceLocation loc = syncLoggerPosition(parser);
ParserStack *stack = static_cast(XML_GetUserData(parser));
Variant::mapType args;
const XML_Char **attr = attrs;
while (*attr) {
const std::string key{*(attr++)};
std::pair value = VariantReader::parseGenericString(
*(attr++), stack->getContext().logger);
args.emplace(std::make_pair(key, value.second));
}
stack->start(std::string(name), args, loc);
}
static void xmlEndElementHandler(void *p, const XML_Char *name)
{
XML_Parser parser = static_cast(p);
syncLoggerPosition(parser);
ParserStack *stack = static_cast(XML_GetUserData(parser));
stack->end();
}
static void xmlCharacterDataHandler(void *p, const XML_Char *s, int len)
{
XML_Parser parser = static_cast(p);
ParserStack *stack = static_cast(XML_GetUserData(parser));
const std::string data =
Utils::trim(std::string{s, static_cast(len)});
if (!data.empty()) {
stack->data(data);
}
}
/* Class XmlParser */
Rooted XmlParser::doParse(CharReader &reader, ParserContext &ctx)
{
// Create the parser object
ScopedExpatXmlParser p{"UTF-8"};
// Create the parser stack instance and pass the reference to the state
// machine descriptor
ParserStack stack{ctx, XML_HANDLERS};
XML_SetUserData(&p, &stack);
XML_UseParserAsHandlerArg(&p);
// Set the callback functions
XML_SetStartElementHandler(&p, xmlStartElementHandler);
XML_SetEndElementHandler(&p, xmlEndElementHandler);
XML_SetCharacterDataHandler(&p, xmlCharacterDataHandler);
// Feed data into expat while there is data to process
constexpr size_t BUFFER_SIZE = 64 * 1024;
while (true) {
// Fetch a buffer from expat for the input data
char *buf = static_cast(XML_GetBuffer(&p, BUFFER_SIZE));
if (!buf) {
throw LoggableException{
"Internal error: XML parser out of memory!"};
}
// Read into the buffer
size_t bytesRead = reader.readRaw(buf, BUFFER_SIZE);
// Parse the data and handle any XML error
if (!XML_ParseBuffer(&p, bytesRead, bytesRead == 0)) {
// Fetch the current line number and column
int line = XML_GetCurrentLineNumber(&p);
int column = XML_GetCurrentColumnNumber(&p);
size_t offs = XML_GetCurrentByteIndex(&p);
// Throw a corresponding exception
XML_Error code = XML_GetErrorCode(&p);
std::string msg = std::string{XML_ErrorString(code)};
throw LoggableException{"XML: " + msg, line, column, offs};
}
// Abort once there are no more bytes in the stream
if (bytesRead == 0) {
break;
}
}
return nullptr;
}
}