// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2004 Alistair Riddoch
//
// 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 2 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, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// $Id: cyconvertrules.cpp,v 1.13 2007-11-20 02:40:37 alriddoch Exp $
/// \page cyconvertrules_index
///
/// \section Introduction
///
/// cyconvertrules is a non-interactive commandline tool to convert rules files
/// to the current format. For information on the usage, please see the unix
/// manual page. The manual page is generated from docbook sources, so can
/// also be converted into other formats.
#include "common/globals.h"
#include <Atlas/Message/DecoderBase.h>
#include <Atlas/Message/Element.h>
#include <Atlas/Codecs/XML.h>
#include <string>
#include <fstream>
#include <iostream>
using Atlas::Message::Element;
using Atlas::Message::MapType;
using Atlas::Message::ListType;
/// Class to read old cyphesis rules on standard input, and output in new
/// standard format.
class FileConverter : public Atlas::Message::DecoderBase {
private:
/// Input file
std::fstream m_file;
/// Atlas codec for decoding input
Atlas::Codecs::XML m_codec;
/// Counter for rules read from input
int m_count;
/// Counter to keep track of indenting the output
int m_indent;
virtual void messageArrived(const MapType &);
public:
FileConverter(const std::string & filename) :
m_file(filename.c_str(), std::ios::in),
m_codec(m_file, *this), m_count(0), m_indent(0)
{
}
/// Write the atlas header to standard out
void openOutput() {
std::cout << "<atlas>" << std::endl;
m_indent = 4;
}
/// Write the atlas tail to standard out
void closeOutput() {
m_indent = 0;
std::cout << "</atlas>" << std::endl;
}
/// Read input file to atlas codec.
void read() {
while (!m_file.eof()) {
m_codec.poll();
}
}
/// Report how many class we converted
void report() {
std::cerr << m_count << " classes read and converted."
<< std::endl << std::flush;
}
/// Indicate if the input file has been opened successfully
bool isOpen() {
return m_file.is_open();
}
/// Write the contents of this element recursively to standard out
void outputValue(const Element &);
/// Write the contents of this atlas list recursively to standard out
void output(const ListType &);
/// Write the contents of this atlas map recursively to standard out
void output(const MapType &);
/// Return a character string corresponding to this atlas element type
const char * typeToStr(Element::Type t) const;
};
void FileConverter::messageArrived(const MapType & omap)
{
// We have read a map from the file. Old format rules files contained
// one map, which contained all the rules as maps within the top
// level map. Iterate over them, and convert into new format;
MapType::const_iterator Iend = omap.end();
for (MapType::const_iterator I = omap.begin(); I != Iend; ++I) {
MapType newObject;
// The id of the new rule is the key of this rule in the old map
newObject["id"] = I->first;
const MapType & ent = I->second.asMap();
// Old format parent attribute was a single string. This is replaced
// in the new format by the more conventional atlas list of strings.
MapType::const_iterator J = ent.find("parent");
MapType::const_iterator Jend = ent.end();
if (J == Jend || !J->second.isString()) {
std::cerr << "Rule \"" << I->first << "\" has no parent."
<< std::endl << std::flush;
continue;
}
newObject["parents"] = ListType(1, J->second.asString());
// Default attribute values, which straightforward values in the old
// format, are converted to more complex structures defining
// atlas attribute inheritance, server side visibility and default
// value
J = ent.find("attributes");
if (J != Jend) {
if (!J->second.isMap()) {
std::cerr << "Rule \"" << I->first
<< "\" has attributes which are not a map."
<< std::endl << std::flush;
continue;
}
newObject["attributes"] = MapType();
MapType & newAttrs = newObject["attributes"].Map();
const MapType & attributes = J->second.Map();
MapType::const_iterator aend = attributes.end();
for(J = attributes.begin(); J != aend; ++J) {
MapType & attr = (newAttrs[J->first] = MapType()).Map();
attr["default"] = J->second;
attr["visibility"] = "public";
}
}
// The script attribute of the rule is converted unchanged, largely
// because additional requirements have not yet been identified.
J = ent.find("script");
if (J != Jend) {
if (!J->second.isMap()) {
std::cerr << "Rule \"" << I->first
<< "\" has script which are not a map."
<< std::endl << std::flush;
continue;
}
newObject["script"] = J->second;
}
// The mind attribute of the rule is converted unchanged, largely
// because additional requirements have not yet been identified.
J = ent.find("mind");
if (J != Jend) {
if (!J->second.isMap()) {
std::cerr << "Rule \"" << I->first
<< "\" has mind which are not a map."
<< std::endl << std::flush;
continue;
}
newObject["mind"] = J->second;
}
// The playable attribute of the rule is converted unchanged
// Perhaps this should in time become an internal attribute?
J = ent.find("playable");
if (J != Jend) {
if (!J->second.isInt()) {
std::cerr << "Rule \"" << I->first
<< "\" has playable which are not an int."
<< std::endl << std::flush;
continue;
}
newObject["playable"] = J->second;
}
// Output the newly converted rule, and continue.
std::cout << " <map>" << std::endl;
output(newObject);
std::cout << " </map>" << std::endl << std::endl;
++m_count;
}
}
const char * FileConverter::typeToStr(Element::Type t) const
{
switch (t) {
case Element::TYPE_INT:
return "int";
case Element::TYPE_FLOAT:
return "float";
case Element::TYPE_STRING:
return "string";
case Element::TYPE_MAP:
return "map";
case Element::TYPE_LIST:
return "list";
case Element::TYPE_NONE:
default:
return "NONE";
};
}
void FileConverter::outputValue(const Element & e)
{
switch (e.getType()) {
case Element::TYPE_INT:
std::cout << e.Int();
break;
case Element::TYPE_FLOAT:
std::cout << e.Float();
break;
case Element::TYPE_STRING:
std::cout << e.String();
break;
case Element::TYPE_MAP:
std::cout << std::endl;
m_indent += 2;
output(e.Map());
m_indent -= 2;
for(int i = 0; i < m_indent; ++i) {
std::cout << " ";
}
break;
case Element::TYPE_LIST:
std::cout << std::endl;
m_indent += 2;
output(e.List());
m_indent -= 2;
for(int i = 0; i < m_indent; ++i) {
std::cout << " ";
}
break;
case Element::TYPE_NONE:
default:
std::cout << e.asInt();
break;
};
}
void FileConverter::output(const MapType & o)
{
MapType::const_iterator Iend = o.end();
for(MapType::const_iterator I = o.begin(); I != Iend; ++I) {
for(int i = 0; i < m_indent; ++i) {
std::cout << " ";
}
const char * type = typeToStr(I->second.getType());
std::cout << "<" << type << " name=\""
<< I->first << "\">";
outputValue(I->second);
std::cout << "</" << type << ">";
std::cout << std::endl;
}
}
void FileConverter::output(const ListType & o)
{
ListType::const_iterator Iend = o.end();
for(ListType::const_iterator I = o.begin(); I != Iend; ++I) {
for(int i = 0; i < m_indent; ++i) {
std::cout << " ";
}
const char * type = typeToStr(I->getType());
std::cout << "<" << type << ">";
outputValue(*I);
std::cout << "</" << type << ">";
std::cout << std::endl;
}
}
static void usage(char * prgname, std::ostream & stream = std::cerr)
{
stream << "usage: " << prgname << " <old rule file>"
<< std::endl << std::flush;
return;
}
int main(int argc, char ** argv)
{
// We require the user to specify the input file
if (argc != 2) {
usage(argv[0]);
return 1;
}
if (argv[1][0] == '-') {
if (strcmp(argv[1], "--version") == 0) {
reportVersion(argv[0]);
return 0;
}
if (strcmp(argv[1], "--help") == 0) {
usage(argv[0], std::cout);
return 0;
}
usage(argv[0]);
return 1;
}
// Attempt to open input
FileConverter f(argv[1]);
if (!f.isOpen()) {
std::cerr << "ERROR: Unable to open file " << argv[1]
<< std::endl << std::flush;
return 1;
}
// Output header elements
f.openOutput();
// Read input file, which will be converted on the fly and copied
// to standard out.
f.read();
// Output tail elements
f.closeOutput();
// Report how many rules converted to stderr
f.report();
}
syntax highlighted by Code2HTML, v. 0.9.1