/* 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 #include #include "DemoOutput.hpp" namespace ousia { namespace html { void DemoHTMLTransformer::writeHTML(Handle doc, std::ostream &out) { Manager &mgr = doc->getManager(); // Create an XML object tree for the document first. Rooted html{new xml::Element{ mgr, "html", {{"xlmns", "http://www.w3.org/1999/xhtml"}}}}; // add the head Element Rooted head{new xml::Element{mgr, "head"}}; html->children.push_back(head); // add the meta element. Rooted meta{ new xml::Element{mgr, "meta", {{"http-equiv", "Content-Type"}, {"content", "text/html; charset=utf-8"}}}}; head->children.push_back(meta); // add the title Element with Text Rooted title{new xml::Element{mgr, "title"}}; head->children.push_back(title); title->children.push_back( new xml::Text(mgr, "Test HTML Output for " + doc->getName())); // add the body Element Rooted body{new xml::Element{mgr, "body"}}; html->children.push_back(body); // So far was the "preamble". No we have to get to the document content. // build the start and end map for annotation processing. AnnoMap startMap; AnnoMap endMap; for (auto &a : doc->getAnnotations()) { // we assume uniquely IDed annotations, which should be checked in the // validation process. startMap.emplace(a->getStart()->getName(), a); endMap.emplace(a->getEnd()->getName(), a); } // extract the book root node. Rooted root = doc->getRoot(); if (root->getDescriptor()->getName() != "book") { throw OusiaException("The given documents root is no book node!"); } // transform the book node. Rooted book = transformSection(root, startMap, endMap); // add it as child to the body node. body->children.push_back(book); // After the content has been transformed, we serialize it. html->serialize( out, ""); } /** * This is just for easier internal handling. */ enum class SectionType { BOOK, SECTION, SUBSECTION, NONE }; SectionType getSectionType(const std::string &name) { if (name == "book") { return SectionType::BOOK; } else if (name == "section") { return SectionType::SECTION; } else if (name == "subsection") { return SectionType::SUBSECTION; } else { return SectionType::NONE; } } Rooted DemoHTMLTransformer::transformSection( Handle section, AnnoMap &startMap, AnnoMap &endMap) { Manager &mgr = section->getManager(); // check the section type. const std::string secclass = section->getDescriptor()->getName(); SectionType type = getSectionType(secclass); if (type == SectionType::NONE) { // if the input node is no section, we ignore it. return {nullptr}; } // create a div tag containing the sections content. Rooted sec{ new xml::Element{mgr, "div", {{"class", secclass}}}}; // check if we have a heading. if (section->hasField("heading") && section->getField("heading").size() > 0) { Rooted heading = section->getField("heading")[0]; std::string headingclass; switch (type) { case SectionType::BOOK: headingclass = "h1"; break; case SectionType::SECTION: headingclass = "h2"; break; case SectionType::SUBSECTION: headingclass = "h3"; break; case SectionType::NONE: // this can not happen; break; } Rooted h{new xml::Element{mgr, headingclass}}; sec->children.push_back(h); // extract the heading text, enveloped in a paragraph Element. Rooted h_content = transformParagraph(heading, startMap, endMap); // We omit the paragraph Element and add the children directly to the // heading Element for (auto &n : h_content->children) { h->children.push_back(n); } } // Then we get all the children. NodeVector mainField = section->getField(); for (auto &n : mainField) { /* * Strictly speaking this is the wrong mechanism, because we would have * to make an "isa" call here because we can not rely on our knowledge * that paragraphs can only be paragraphs or lists. There would have * to be a listener structure of transformations that check if they can * transform this specific node. */ const std::string childDescriptorName = n->getDescriptor()->getName(); Rooted child; if (childDescriptorName == "paragraph") { child = transformParagraph(n, startMap, endMap); } else if (childDescriptorName == "ul" || childDescriptorName == "ol") { child = transformList(n, startMap, endMap); } else { child = transformSection(n, startMap, endMap); } if (!child.isNull()) { sec->children.push_back(child); } } return sec; } Rooted DemoHTMLTransformer::transformList( Handle list, AnnoMap &startMap, AnnoMap &endMap) { Manager &mgr = list->getManager(); // create the list Element, which is either ul or ol (depends on descriptor) std::string listclass = list->getDescriptor()->getName(); Rooted l{new xml::Element{mgr, listclass}}; // iterate through list items. for (auto &item : list->getField()) { std::string itDescrName = item->getDescriptor()->getName(); if (itDescrName == "item") { // create the list item. Rooted li{new xml::Element{mgr, "li"}}; l->children.push_back(li); // extract the item text, enveloped in a paragraph Element. Rooted li_content = transformParagraph(item, startMap, endMap); // We omit the paragraph Element and add the children directly to // the list item for (auto &n : li_content->children) { li->children.push_back(n); } } } return l; } typedef model::AnnotationEntity::Anchor Anchor; typedef std::stack> AnnoStack; Rooted DemoHTMLTransformer::transformParagraph( Handle par, AnnoMap &startMap, AnnoMap &endMap) { Manager &mgr = par->getManager(); // create the p Element Rooted p{new xml::Element{mgr, "p"}}; // check if we have a heading. if (par->hasField("heading") && par->getField("heading").size() > 0) { Rooted heading = par->getField("heading")[0]; // put the heading in a strong xml::Element. Rooted strong{new xml::Element{mgr, "strong"}}; p->children.push_back(strong); // extract the heading text, enveloped in a paragraph Element. Rooted h_content = transformParagraph(heading, startMap, endMap); // We omit the paragraph Element and add the children directly to the // heading Element for (auto &n : h_content->children) { strong->children.push_back(n); } } // transform paragraph children to XML as well for (auto &n : par->getField()) { if (n->isa(typeOf())) { //TODO: This needs some more brain work. // // check if this is a start Anchor. // auto it = startMap.find(n->getName()); // if(it != startMap.end()){ // // if we have a start Anchor, we put another AnnotationEntity // // on top the stack. // opened.push(it->second); // // and we create an open tag. // // continue; // } // // check if this is an end Anchor. // auto it = endMap.find(n->getName()); // if(it != endMap.end()){ // /* // * Now it gets somewhat interesting: We have to close all // * tags that started after the one that is closed now and // * re-open them afterwards. So we create a lokal stack to // * temporarily store all AnnotationEntities that need to // * be re-opened. // */ // AnnoStack tmp; // Rooted< // while(!opened.empty() && ) // } // continue; } std::string childDescriptorName = n->getDescriptor()->getName(); if (childDescriptorName == "text") { Handle primitive = n->getField()[0].cast(); if (primitive.isNull()) { throw OusiaException("Text field is not primitive!"); } p->children.push_back( new xml::Text(mgr, primitive->getContent().asString())); } } return p; } } }