/*
Ousía
Copyright (C) 2014 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 "XmlOutput.hpp"
#include
#include
namespace ousia {
namespace xml {
/**
* Wrapper structure for transformation parameters.
*/
struct TransformParams {
Manager &mgr;
Logger &logger;
bool pretty;
bool flat;
SourceId documentId;
// this stores all already serialized dependent typesystems and ontologies.
std::unordered_set serialized;
TransformParams(Manager &mgr, Logger &logger, bool pretty, bool flat,
SourceId documentId)
: mgr(mgr),
logger(logger),
pretty(pretty),
flat(flat),
documentId(documentId)
{
}
};
/*
* These are forward method declarations to allow for cross-references of
* methods.
*/
/*
* Ontology transformation.
*/
static Rooted transformOntology(Handle parent,
Handle o,
TransformParams &P);
/*
* Typesystem transformation.
*/
static std::string getTypeRef(Handle referencing,
Handle referenced);
static Rooted transformStructTypeEntry(Handle parent,
const std::string &tagName,
Handle t,
Handle a,
TransformParams &P);
static Rooted transformStructType(Handle parent,
const std::string &structTagName,
const std::string &fieldTagName,
Handle t,
TransformParams &P);
static Rooted transformTypesystem(Handle parent,
Handle t,
TransformParams &P);
/*
* Attribute transformation.
*/
static std::map transformAttributes(
const std::string &name, DocumentEntity *entity, TransformParams &P);
static void addNameAttribute(Handle n,
std::map &attrs);
/*
* DocumentEntity transformation.
*/
static void transformChildren(DocumentEntity *parentEntity,
Handle parent, TransformParams &P);
static Rooted transformStructuredEntity(Handle parent,
Handle s,
TransformParams &P);
/*
* Annotations.
*/
static Rooted transformAnchor(Handle parent, Handle a,
TransformParams &P);
/*
* DocumentPrimitives.
*/
static std::string toString(Variant v, TransformParams &P);
static Rooted transformPrimitive(Handle parent,
Handle type,
Handle p,
TransformParams &P);
/*
* The actual transformation implementation starts here.
*/
static Rooted createImportElement(Handle parent,
Handle referenced,
ResourceManager &resourceManager,
const std::string &rel,
TransformParams &P)
{
SourceLocation loc = referenced->getLocation();
// check if the source location is the same as for the whole document.
// in that case we do not want to make an import statement.
if (P.documentId == loc.getSourceId()) {
return nullptr;
}
// if that is not the case, we try to find the respective resource.
Resource res = resourceManager.getResource(loc.getSourceId());
if (!res.isValid()) {
return nullptr;
}
// if we found it we create an import element.
Rooted import{new Element{
P.mgr, parent, "import", {{"rel", rel}, {"src", res.getLocation()}}}};
return import;
}
void XmlTransformer::writeXml(Handle doc, std::ostream &out,
Logger &logger, ResourceManager &resourceManager,
bool pretty, bool flat)
{
Manager &mgr = doc->getManager();
// the outermost tag is the document itself.
Rooted document{new Element{mgr, {nullptr}, "document"}};
// create parameter wrapper object
TransformParams P{mgr, logger, pretty, flat,
doc->getLocation().getSourceId()};
// write imports for all referenced ontologies.
for (auto o : doc->getOntologies()) {
if (!flat) {
Rooted import = createImportElement(
document, o, resourceManager, "ontology", P);
if (import != nullptr) {
document->addChild(import);
// add the import as namespace information to the document node
// as well.
document->getAttributes().emplace(
std::string("xmlns:") + o->getName(), o->getName());
continue;
} else {
logger.warning(std::string(
"The location of ontology \"" + o->getName() +
"\" could not be retrieved using the given ResourceManager."
" The ontology is now serialized inline."));
}
}
Rooted ontology = transformOntology(document, o, P);
document->addChild(ontology);
}
// write imports for all referenced typesystems.
for (auto t : doc->getTypesystems()) {
if (!flat) {
Rooted import = createImportElement(
document, t, resourceManager, "typesystem", P);
if (import != nullptr) {
document->addChild(import);
continue;
} else {
logger.warning(
std::string("The location of typesystem \"" + t->getName() +
"\" could not be retrieved using the given "
"ResourceManager. "
" The typesystem is now serialized inline."));
}
}
Rooted typesystem = transformTypesystem(document, t, P);
document->addChild(typesystem);
}
// transform the root element (and, using recursion, everything below it)
Rooted root =
transformStructuredEntity(document, doc->getRoot(), P);
document->addChild(root);
// then serialize.
document->serialize(
out, "",
pretty);
}
/*
* Ontology transformation functions.
*/
static std::string getStringForBool(bool val)
{
if (val) {
return "true";
} else {
return "false";
}
}
static std::string getStructuredClassRef(Handle referencing,
Handle referenced)
{
std::string res;
if (referencing->getParent() == referenced->getParent()) {
res = referenced->getName();
} else {
res = referenced->getParent().cast()->getName() + "." +
referenced->getName();
}
return res;
}
static Rooted transformTokenDescriptor(Handle parent,
const TokenDescriptor &descr,
const std::string &tagName,
TransformParams &P)
{
if (descr.isEmpty()) {
return nullptr;
}
Rooted tag{new Element(P.mgr, parent, tagName)};
std::string str;
if (descr.special) {
// TODO: Handle this case
} else {
str = descr.token;
}
Rooted token{new Text(P.mgr, parent, str)};
tag->addChild(token);
return tag;
}
static Rooted transformFieldDescriptor(Handle parent,
Handle fd,
TransformParams &P)
{
// find the correct tag name.
std::string tagName;
if (fd->isPrimitive()) {
tagName = "primitive";
} else {
tagName = "field";
}
// transform the attributes.
std::map attrs;
addNameAttribute(fd, attrs);
bool isSubtree = fd->getFieldType() == FieldDescriptor::FieldType::SUBTREE;
attrs.emplace("subtree", getStringForBool(isSubtree));
attrs.emplace("optional", getStringForBool(fd->isOptional()));
// TODO: whitespace mode?
// create the XML element itself.
Rooted fieldDescriptor{new Element(P.mgr, parent, tagName, attrs)};
// translate the syntax.
Rooted syntax{new Element(P.mgr, parent, "syntax")};
fieldDescriptor->addChild(syntax);
{
Rooted open =
transformTokenDescriptor(syntax, fd->getOpenToken(), "open", P);
if (open != nullptr) {
syntax->addChild(open);
}
Rooted close =
transformTokenDescriptor(syntax, fd->getCloseToken(), "close", P);
if (close != nullptr) {
syntax->addChild(close);
}
}
if (!fd->isPrimitive()) {
// translate the child references.
for (auto s : fd->getChildren()) {
std::string ref =
getStructuredClassRef(fd->getParent().cast(), s);
Rooted childRef{new Element(P.mgr, fieldDescriptor,
"childRef", {{"ref", ref}})};
fieldDescriptor->addChild(childRef);
}
} else {
// translate the primitive type.
fieldDescriptor->getAttributes().emplace(
"type", getTypeRef(nullptr, fd->getPrimitiveType()));
}
return fieldDescriptor;
}
static void transformDescriptor(Handle elem, Handle syntax,
Handle d, TransformParams &P)
{
// add name.
addNameAttribute(d, elem->getAttributes());
// transform the attributes descriptor.
Rooted attributes = transformStructType(
elem, "attributes", "attribute", d->getAttributesDescriptor(), P);
// remove the parent entry if it is there.
attributes->getAttributes().erase("parent");
elem->addChild(attributes);
// transform the syntactic sugar description.
{
Rooted open =
transformTokenDescriptor(syntax, d->getOpenToken(), "open", P);
if (open != nullptr) {
syntax->addChild(open);
}
Rooted close =
transformTokenDescriptor(syntax, d->getCloseToken(), "close", P);
if (close != nullptr) {
syntax->addChild(close);
}
}
// transform all field descriptors.
for (auto fd : d->getFieldDescriptors()) {
Rooted fieldDescriptor = transformFieldDescriptor(elem, fd, P);
elem->addChild(fieldDescriptor);
}
}
static Rooted transformStructuredClass(Handle parent,
Handle s,
TransformParams &P)
{
Rooted structuredClass{new Element(P.mgr, parent, "struct")};
// transform the specific StructuredClass properties.
structuredClass->getAttributes().emplace(
"cardinality", toString(Variant(s->getCardinality()), P));
if (s->getSuperclass() != nullptr) {
structuredClass->getAttributes().emplace(
"isa", getStructuredClassRef(s, s->getSuperclass()));
}
structuredClass->getAttributes().emplace(
"transparent", getStringForBool(s->isTransparent()));
structuredClass->getAttributes().emplace(
"root", getStringForBool(s->hasRootPermission()));
// transform the syntactic sugar descriptors
Rooted syntax{new Element(P.mgr, structuredClass, "syntax")};
structuredClass->addChild(syntax);
{
Rooted shortForm =
transformTokenDescriptor(syntax, s->getShortToken(), "short", P);
if (shortForm != nullptr) {
syntax->addChild(shortForm);
}
}
// transform the descriptor properties
transformDescriptor(structuredClass, syntax, s, P);
return structuredClass;
}
static Rooted transformAnnotationClass(Handle parent,
Handle a,
TransformParams &P)
{
Rooted annotationClass{new Element(P.mgr, parent, "struct")};
Rooted syntax{new Element(P.mgr, annotationClass, "syntax")};
annotationClass->addChild(syntax);
transformDescriptor(annotationClass, syntax, a, P);
return annotationClass;
}
Rooted transformOntology(Handle parent, Handle o,
TransformParams &P)
{
// only transform this ontology if it was not transformed already.
if (o->getLocation().getSourceId() != P.documentId) {
// also: store that we have serialized this ontology.
if (!P.serialized.insert(o->getLocation().getSourceId()).second) {
return nullptr;
}
}
if (P.flat) {
// transform all referenced ontologies if we want a standalone version.
for (auto o2 : o->getOntologies()) {
Rooted refOnto = transformOntology(parent, o2, P);
if (refOnto != nullptr) {
parent->addChild(refOnto);
}
}
// transform all referenced typesystems if we want a standalone version.
for (auto t : o->getTypesystems()) {
Rooted refTypes = transformTypesystem(parent, t, P);
if (refTypes != nullptr) {
parent->addChild(refTypes);
}
}
}
// transform the ontology itself.
// create an XML element for the ontology.
Rooted ontology{new Element(P.mgr, parent, "ontology")};
addNameAttribute(o, ontology->getAttributes());
// transform all StructuredClasses.
for (auto s : o->getStructureClasses()) {
Rooted structuredClass =
transformStructuredClass(ontology, s, P);
ontology->addChild(structuredClass);
}
// transform all AnnotationClasses.
for (auto a : o->getAnnotationClasses()) {
Rooted annotationClass =
transformAnnotationClass(ontology, a, P);
ontology->addChild(annotationClass);
}
// return the transformed Ontology.
return ontology;
}
/*
* Typesystem transformation functions.
*/
std::string getTypeRef(Handle referencing, Handle referenced)
{
std::string typeRef;
if (referencing != referenced->getTypesystem() &&
!(referenced->getTypesystem()->isa(&RttiTypes::SystemTypesystem))) {
// qualify the name if that's necessary.
typeRef = referenced->getTypesystem()->getName() + "." +
referenced->getName();
} else {
typeRef = referenced->getName();
}
return typeRef;
}
Rooted transformStructTypeEntry(Handle parent,
const std::string &tagName,
Handle t,
Handle a,
TransformParams &P)
{
// create an xml element for the attribute.
Rooted attribute{new Element(P.mgr, parent, tagName)};
addNameAttribute(a, attribute->getAttributes());
// add the type reference
{
std::string typeRef = getTypeRef(t->getTypesystem(), a->getType());
attribute->getAttributes().emplace("type", typeRef);
}
// set the default value.
if (!a->getDefaultValue().isNull() &&
(!a->getDefaultValue().isObject() ||
a->getDefaultValue().asObject() != nullptr)) {
attribute->getAttributes().emplace("default",
toString(a->getDefaultValue(), P));
}
return attribute;
}
Rooted transformStructType(Handle parent,
const std::string &structTagName,
const std::string &fieldTagName,
Handle t, TransformParams &P)
{
// create an xml element for the struct type itself.
Rooted structType{new Element(P.mgr, parent, structTagName)};
addNameAttribute(t, structType->getAttributes());
// transformt the parent reference.
if (t->getParentStructure() != nullptr) {
std::string typeRef =
getTypeRef(t->getTypesystem(), t->getParentStructure());
structType->getAttributes().emplace("parent", typeRef);
}
// transform all attributes.
for (auto &a : t->getOwnAttributes()) {
Rooted attribute =
transformStructTypeEntry(structType, fieldTagName, t, a, P);
structType->addChild(attribute);
}
return structType;
}
static Rooted transformEnumType(Handle parent,
Handle e, TransformParams &P)
{
// create an xml element for the enum type itself.
Rooted enumType{new Element(P.mgr, parent, "enum")};
addNameAttribute(e, enumType->getAttributes());
// add all entries.
for (std::string &name : e->names()) {
Rooted enumEntry{new Element(P.mgr, enumType, "entry")};
enumType->addChild(enumEntry);
Rooted enumName{new Text(P.mgr, enumEntry, name)};
enumEntry->addChild(enumName);
}
return enumType;
}
static Rooted transformConstant(Handle parent,
Handle t,
Handle c, TransformParams &P)
{
// create an xml element for the constant.
Rooted constant{new Element(P.mgr, parent, "constant")};
addNameAttribute(c, constant->getAttributes());
// add the type reference
{
std::string typeRef = getTypeRef(t, c->getType());
constant->getAttributes().emplace("type", typeRef);
}
// add the value
constant->getAttributes().emplace("value", toString(c->getValue(), P));
return constant;
}
Rooted transformTypesystem(Handle parent,
Handle t, TransformParams &P)
{
// do not transform the system typesystem.
if (t->isa(&RttiTypes::SystemTypesystem)) {
return nullptr;
}
// only transform this typesystem if it was not transformed already.
if (t->getLocation().getSourceId() != P.documentId) {
// also: store that we have serialized this ontology.
if (!P.serialized.insert(t->getLocation().getSourceId()).second) {
return nullptr;
}
}
if (P.flat) {
// transform all referenced typesystems if we want a standalone version.
for (auto t2 : t->getTypesystemReferences()) {
Rooted refTypes = transformTypesystem(parent, t2, P);
if (refTypes != nullptr) {
parent->addChild(refTypes);
}
}
}
// transform the typesystem itself.
// create an XML element for the ontology.
Rooted typesystem{new Element(P.mgr, parent, "typesystem")};
addNameAttribute(t, typesystem->getAttributes());
// transform all types
for (auto tp : t->getTypes()) {
Rooted type;
if (tp->isa(&RttiTypes::StructType)) {
type = transformStructType(typesystem, "struct", "field",
tp.cast(), P);
} else if (tp->isa(&RttiTypes::EnumType)) {
type = transformEnumType(typesystem, tp.cast(), P);
} else {
P.logger.warning(std::string("Type ") + tp->getName() +
" can not be serialized, because it is neither a "
"StructType nor an EnumType.");
}
if (type != nullptr) {
typesystem->addChild(type);
}
}
// transform all constants.
for (auto c : t->getConstants()) {
Rooted constant = transformConstant(typesystem, t, c, P);
typesystem->addChild(constant);
}
// return the transformed Ontology.
return typesystem;
}
/*
* DocumentEntity attributes transform functions.
*/
std::map transformAttributes(const std::string &name,
DocumentEntity *entity,
TransformParams &P)
{
// copy the attributes.
Variant attrs = entity->getAttributes();
// build them.
entity->getDescriptor()->getAttributesDescriptor()->build(attrs, P.logger);
// get the array representation.
Variant::arrayType attrArr = attrs.asArray();
// transform them to string key-value pairs.
NodeVector as =
entity->getDescriptor()->getAttributesDescriptor()->getAttributes();
std::map xmlAttrs;
// Write the element name if one was given
if (!name.empty()) {
xmlAttrs.emplace("name", name);
}
// Write other user defined properties
for (size_t a = 0; a < as.size(); a++) {
xmlAttrs.emplace(as[a]->getName(), toString(attrArr[a], P));
}
return xmlAttrs;
}
void addNameAttribute(Handle n,
std::map &attrs)
{
// copy the name attribute.
if (!n->getName().empty()) {
attrs.emplace("name", n->getName());
}
}
/*
* StructureNode transform functions.
*/
void transformChildren(DocumentEntity *parentEntity, Handle parent,
TransformParams &P)
{
NodeVector fieldDescs =
parentEntity->getDescriptor()->getFieldDescriptors();
for (size_t f = 0; f < fieldDescs.size(); f++) {
NodeVector field = parentEntity->getField(f);
Rooted fieldDesc = fieldDescs[f];
// if this is not the default field create an intermediate node for it.
Rooted par = parent;
if (fieldDesc->getFieldType() != FieldDescriptor::FieldType::TREE) {
par = Rooted{
new Element(P.mgr, parent, fieldDesc->getName())};
parent->addChild(par);
}
if (!fieldDesc->isPrimitive()) {
for (auto c : field) {
// transform each child.
Rooted child;
if (c->isa(&RttiTypes::StructuredEntity)) {
child = transformStructuredEntity(
par, c.cast(), P);
} else {
assert(c->isa(&RttiTypes::Anchor));
child = transformAnchor(par, c.cast(), P);
}
if (child != nullptr) {
par->addChild(child);
}
}
} else {
// if the field is primitive we expect a single child.
if (field.empty()) {
continue;
}
assert(field.size() == 1);
assert(field[0]->isa(&RttiTypes::DocumentPrimitive));
Rooted prim = field[0].cast();
// transform the primitive content.
Rooted text =
transformPrimitive(par, fieldDesc->getPrimitiveType(), prim, P);
if (text != nullptr) {
par->addChild(text);
}
}
}
}
Rooted transformStructuredEntity(Handle parent,
Handle s,
TransformParams &P)
{
// create the XML element itself.
Rooted elem{new Element{
P.mgr, parent, s->getDescriptor()->getName(),
transformAttributes(s->getName(), s.get(), P),
s->getDescriptor()->getParent().cast()->getName()}};
// then transform the children.
transformChildren(s.get(), elem, P);
return elem;
}
Rooted transformAnchor(Handle parent, Handle a,
TransformParams &P)
{
Rooted elem;
if (a->isStart()) {
// if this is the start anchor we append all the additional information
// of the annotation here.
// transform the attributes.
auto attrs = transformAttributes("", a->getAnnotation().get(), P);
elem = Rooted{new Element(
P.mgr, parent, a->getAnnotation()->getDescriptor()->getName(),
attrs, "a:start")};
// and handle the children.
transformChildren(a->getAnnotation().get(), elem, P);
} else if (a->isEnd()) {
/*
* in principle !a->isStart() should imply a->isEnd() but if no
* annotation is set both is false, so we check it to be sure.
* In case of an end anchor we just create an empty element with the
* annotation name.
*/
std::map attrs;
addNameAttribute(a->getAnnotation(), attrs);
elem = Rooted{new Element(
P.mgr, parent, a->getAnnotation()->getDescriptor()->getName(),
attrs, "a:end")};
} else {
P.logger.warning("Ignoring disconnected Anchor", *a);
}
return elem;
}
/*
* Primitive transform functions.
*/
std::string toString(Variant v, TransformParams &P)
{
if (v.isString()) {
return v.asString();
} else {
return VariantWriter::writeOusiaToString(v, P.pretty);
}
}
Rooted transformPrimitive(Handle parent, Handle type,
Handle p, TransformParams &P)
{
// transform the primitive content.
Variant content = p->getContent();
if (!type->build(content, P.logger)) {
return nullptr;
}
// special treatment for struct types because they get built as arrays,
// which is not so nice for output purposes.
if (type->isa(&RttiTypes::StructType)) {
Variant::mapType map;
Variant::arrayType arr = content.asArray();
size_t a = 0;
for (Handle attr :
type.cast()->getAttributes()) {
map.emplace(attr->getName(), arr[a++]);
}
content = std::move(map);
}
Rooted text{new Text(P.mgr, parent, toString(content, P))};
return text;
}
}
}