//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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.
//
// Remere's Map Editor 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 "main.h"
#include "otml.h"
#include
OTMLException::OTMLException(const OTMLNodePtr& node, const std::string& error) {
std::stringstream ss;
ss << "OTML error";
if(!node->source().empty())
ss << " in '" << node->source() << "'";
ss << ": " << error;
m_what = ss.str();
}
OTMLException::OTMLException(const OTMLDocumentPtr& doc, const std::string& error, int line) {
std::stringstream ss;
ss << "OTML error";
if(doc && !doc->source().empty()) {
ss << " in '" << doc->source() << "'";
if(line >= 0)
ss << " at line " << line;
}
ss << ": " << error;
m_what = ss.str();
}
OTMLNodePtr OTMLNode::create(std::string tag, bool unique) {
OTMLNodePtr node(new OTMLNode);
node->setTag(tag);
node->setUnique(unique);
return node;
}
OTMLNodePtr OTMLNode::create(std::string tag, std::string value) {
OTMLNodePtr node(new OTMLNode);
node->setTag(tag);
node->setValue(value);
node->setUnique(true);
return node;
}
bool OTMLNode::hasChildren() const {
int count = 0;
for(OTMLNodeList::const_iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
if(!child->isNull())
count++;
}
return count > 0;
}
OTMLNodePtr OTMLNode::get(const std::string& childTag) const {
for(OTMLNodeList::const_iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
if(child->tag() == childTag && !child->isNull())
return child;
}
return OTMLNodePtr();
}
OTMLNodePtr OTMLNode::getIndex(int childIndex) const {
if(childIndex < size() && childIndex >= 0)
return m_children[childIndex];
return OTMLNodePtr();
}
OTMLNodePtr OTMLNode::at(const std::string& childTag) {
OTMLNodePtr res;
for(OTMLNodeList::iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
if(child->tag() == childTag && !child->isNull()) {
res = child;
break;
}
}
if(!res) {
std::stringstream ss;
ss << "child node with tag '" << childTag << "' not found";
throw OTMLException(shared_from_this(), ss.str());
}
return res;
}
OTMLNodePtr OTMLNode::atIndex(int childIndex) {
if(childIndex >= size() || childIndex < 0) {
std::stringstream ss;
ss << "child node with index '" << childIndex << "' not found";
throw OTMLException(shared_from_this(), ss.str());
}
return m_children[childIndex];
}
void OTMLNode::addChild(const OTMLNodePtr& newChild) {
if(newChild->hasTag()) {
for(OTMLNodeList::iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& node = *it;
if(node->tag() == newChild->tag() && (node->isUnique() || newChild->isUnique())) {
newChild->setUnique(true);
if(node->hasChildren() && newChild->hasChildren()) {
OTMLNodePtr tmpNode = node->clone();
tmpNode->merge(newChild);
newChild->copy(tmpNode);
}
replaceChild(node, newChild);
OTMLNodeList::iterator it = m_children.begin();
while(it != m_children.end()) {
OTMLNodePtr node = (*it);
if(node != newChild && node->tag() == newChild->tag()) {
node->setParent(OTMLNodePtr());
it = m_children.erase(it);
}
else
++it;
}
return;
}
}
}
m_children.push_back(newChild);
newChild->setParent(shared_from_this());
}
bool OTMLNode::removeChild(const OTMLNodePtr& oldChild) {
OTMLNodeList::iterator it = std::find(m_children.begin(), m_children.end(), oldChild);
if(it != m_children.end()) {
m_children.erase(it);
oldChild->setParent(OTMLNodePtr());
return true;
}
return false;
}
bool OTMLNode::replaceChild(const OTMLNodePtr& oldChild, const OTMLNodePtr& newChild) {
OTMLNodeList::iterator it = std::find(m_children.begin(), m_children.end(), oldChild);
if(it != m_children.end()) {
oldChild->setParent(OTMLNodePtr());
newChild->setParent(shared_from_this());
it = m_children.erase(it);
m_children.insert(it, newChild);
return true;
}
return false;
}
void OTMLNode::copy(const OTMLNodePtr& node)
{
setTag(node->tag());
setValue(node->rawValue());
setUnique(node->isUnique());
setNull(node->isNull());
setSource(node->source());
clear();
for(OTMLNodeList::iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
addChild(child->clone());
}
}
void OTMLNode::merge(const OTMLNodePtr& node) {
for(OTMLNodeList::iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
addChild(child->clone());
}
setTag(node->tag());
setSource(node->source());
}
void OTMLNode::clear() {
for(OTMLNodeList::iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
child->setParent(OTMLNodePtr());
}
m_children.clear();
}
OTMLNodeList OTMLNode::children() const {
OTMLNodeList children;
for(OTMLNodeList::const_iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
if(!child->isNull())
children.push_back(child);
}
return children;
}
OTMLNodePtr OTMLNode::clone() const {
OTMLNodePtr myClone(new OTMLNode);
myClone->setTag(m_tag);
myClone->setValue(m_value);
myClone->setUnique(m_unique);
myClone->setNull(m_null);
myClone->setSource(m_source);
for(OTMLNodeList::const_iterator it = m_children.begin(), end = m_children.end(); it != end; ++it) {
const OTMLNodePtr& child = *it;
myClone->addChild(child->clone());
}
return myClone;
}
std::string OTMLNode::emit() {
return OTMLEmitter::emitNode(shared_from_this(), 0);
}
template
T OTMLNode::value() {
T ret;
if(!otml_util::cast(m_value, ret))
throw OTMLException(shared_from_this(), "failed to cast node value");
return ret;
}
template
T OTMLNode::valueAtIndex(int childIndex) {
OTMLNodePtr node = atIndex(childIndex);
return node->value();
}
bool OTMLNode::valueAt(const std::string& childTag) {
OTMLNodePtr node = at(childTag);
return node->value();
}
std::string OTMLNode::valueAt(const std::string& childTag, const std::string& def) {
if(OTMLNodePtr node = get(childTag))
if(!node->isNull())
return node->value();
return def;
}
template
T OTMLNode::valueAtIndex(int childIndex, const T& def) {
if(OTMLNodePtr node = getIndex(childIndex))
return node->value();
return def;
}
template
void OTMLNode::write(const T& v) {
m_value = otml_util::safeCast(v);
}
template
void OTMLNode::writeAt(const std::string& childTag, const T& v) {
OTMLNodePtr child = OTMLNode::create(childTag);
child->setUnique(true);
child->write(v);
addChild(child);
}
template
void OTMLNode::writeIn(const T& v) {
OTMLNodePtr child = OTMLNode::create();
child->write(v);
addChild(child);
}
OTMLDocumentPtr OTMLDocument::create() {
OTMLDocumentPtr doc(new OTMLDocument);
doc->setTag("doc");
return doc;
}
OTMLDocumentPtr OTMLDocument::parse(const std::string& fileName) {
std::ifstream fin(fileName.c_str());
if(!fin.good()) {
std::stringstream ss;
ss << "failed to open file " << fileName;
throw OTMLException(ss.str());
}
return parse(fin, fileName);
}
OTMLDocumentPtr OTMLDocument::parse(std::istream& in, const std::string& source) {
OTMLDocumentPtr doc(new OTMLDocument);
doc->setSource(source);
OTMLParser parser(doc, in);
parser.parse();
return doc;
}
std::string OTMLDocument::emit() {
return OTMLEmitter::emitNode(shared_from_this()) + "\n";
}
bool OTMLDocument::save(const std::string& fileName) {
m_source = fileName;
std::ofstream fout(fileName.c_str());
if(fout.good()) {
fout << emit();
fout.close();
return true;
}
return false;
}
std::string OTMLEmitter::emitNode(const OTMLNodePtr& node, int currentDepth) {
std::stringstream ss;
if(currentDepth >= 0) {
for(int i = 0; ihasTag()) {
ss << node->tag();
if(node->hasValue() || node->isUnique() || node->isNull())
ss << ":";
}
else
ss << "-";
if(node->isNull())
ss << " ~";
else if(node->hasValue()) {
ss << " ";
std::string value = node->rawValue();
if(value.find("\n") != std::string::npos) {
if(value[value.length() - 1] == '\n' && value[value.length() - 2] == '\n')
ss << "|+";
else if(value[value.length() - 1] == '\n')
ss << "|";
else
ss << "|-";
for(std::size_t pos = 0; pos < value.length(); ++pos) {
ss << "\n";
for(int i = 0; isize(); ++i) {
if(currentDepth >= 0 || i != 0)
ss << "\n";
ss << emitNode(node->atIndex(i), currentDepth + 1);
}
return ss.str();
}
void OTMLParser::parse() {
if(!in.good())
throw OTMLException(doc, "cannot read from input stream");
while(!in.eof())
parseLine(getNextLine());
}
std::string OTMLParser::getNextLine() {
currentLine++;
std::string line;
std::getline(in, line);
return line;
}
int OTMLParser::getLineDepth(const std::string& line, bool multilining) {
std::size_t spaces = 0;
while(line[spaces] == ' ')
spaces++;
int depth = spaces / 2;
if(!multilining || depth <= currentDepth) {
if(line[spaces] == '\t')
throw OTMLException(doc, "indentation with tabs are not allowed", currentLine);
if(spaces % 2 != 0)
throw OTMLException(doc, "must indent every 2 spaces", currentLine);
}
return depth;
}
void OTMLParser::trim(std::string& string)
{
auto view = std::ranges::views::all(string)
| std::ranges::views::drop_while(isspace)
| std::ranges::views::reverse
| std::ranges::views::drop_while(isspace)
| std::ranges::views::reverse;
std::string result{view.begin(), view.end()};
string = std::move(result);
}
std::vector OTMLParser::split(const char *str, char ch /*= ' '*/)
{
std::vector result;
do
{
const char *begin = str;
while(*str != ch && *str)
{
str++;
}
result.push_back(std::string(begin, str));
} while (0 != *str++);
return result;
}
void OTMLParser::parseLine(std::string line) {
int depth = getLineDepth(line);
if(depth == -1)
return;
trim(line);
if(line.empty())
return;
if(line.substr(0, 2) == "//")
return;
if(depth == currentDepth + 1) {
currentParent = previousNode;
}
else if(depth < currentDepth) {
for(int i = 0; iparent();
}
else if(depth != currentDepth)
throw OTMLException(doc, "invalid indentation depth, are you indenting correctly?", currentLine);
currentDepth = depth;
parseNode(line);
}
void OTMLParser::parseNode(const std::string& data) {
std::string tag;
std::string value;
std::size_t dotsPos = data.find_first_of(':');
int nodeLine = currentLine;
if(!data.empty() && data[0] == '-') {
value = data.substr(1);
trim(value);
}
else if(dotsPos != std::string::npos) {
tag = data.substr(0, dotsPos);
if(data.size() > dotsPos + 1)
value = data.substr(dotsPos + 1);
}
else {
tag = data;
}
trim(tag);
trim(value);
if(value == "|" || value == "|-" || value == "|+") {
std::string multiLineData;
do {
size_t lastPos = in.tellg();
std::string line = getNextLine();
int depth = getLineDepth(line, true);
if(depth > currentDepth) {
multiLineData += line.substr((currentDepth + 1) * 2);
}
else {
trim(line);
if(!line.empty()) {
in.seekg(lastPos, std::ios::beg);
currentLine--;
break;
}
}
multiLineData += "\n";
} while(!in.eof());
if(value == "|" || value == "|-") {
int lastPos = multiLineData.length();
while(multiLineData[--lastPos] == '\n')
multiLineData.erase(lastPos, 1);
if(value == "|")
multiLineData.append("\n");
}
value = multiLineData;
}
OTMLNodePtr node = OTMLNode::create(tag);
node->setUnique(dotsPos != std::string::npos);
node->setTag(tag);
node->setSource(doc->source() + ":" + otml_util::safeCast(nodeLine));
if(value == "~")
node->setNull(true);
else {
if(std::ranges::starts_with(value, "[") && std::ranges::ends_with(value, "]")) {
std::string tmp = value.substr(1, value.length() - 2);
for (const auto word : split(tmp.c_str())) {
std::string v = word;
trim(v);
node->writeIn(v);
}
}
else
node->setValue(value);
}
currentParent->addChild(node);
previousNode = node;
}