Initial realization of validation.
No changes to CMakeLists yet, need to talk with mordante about priniting error messages.
This commit is contained in:
parent
c39a96c3e5
commit
a010f9fc5c
2 changed files with 383 additions and 0 deletions
257
src/serialization/schema_validator.cpp
Normal file
257
src/serialization/schema_validator.cpp
Normal file
|
@ -0,0 +1,257 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2011 - 2011 by Sytyi Nick <nsytyi@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
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.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "serialization/schema_validator.hpp"
|
||||
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "foreach.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
#include "wml_exception.hpp"
|
||||
|
||||
namespace schema_validation{
|
||||
|
||||
static lg::log_domain log_validation("validation");
|
||||
|
||||
#define ERR_VL LOG_STREAM(err, log_validation)
|
||||
#define WRN_VL LOG_STREAM(warn, log_validation)
|
||||
#define LOG_VL LOG_STREAM(info, log_validation)
|
||||
|
||||
std::string at(const std::string & file, int line){
|
||||
std::ostringstream ss;
|
||||
ss << line << " " << file;
|
||||
return ::lineno_string(ss.str());
|
||||
}
|
||||
|
||||
void extra_tag_error(const std::string & file, int line,
|
||||
const std::string & name){
|
||||
WRN_VL << at(file,line) << ": extra tag "<< name << "\n";
|
||||
}
|
||||
void wrong_tag_error(const std::string & file, int line,
|
||||
const std::string & name){
|
||||
std::ostringstream ss;
|
||||
ss <<at(file,line) << ": wrong tag "<< name << "\n";
|
||||
WRN_VL << ss.str();
|
||||
//throw twml_exception("Validation error",ss.str ());
|
||||
}
|
||||
void missing_tag_error(const std::string & file, int line,
|
||||
const std::string & name){
|
||||
WRN_VL <<at(file,line) << ": missing tag "<< name << "\n";
|
||||
}
|
||||
void extra_key_error(const std::string & file, int line,
|
||||
const std::string & tag,const std::string & key
|
||||
){
|
||||
WRN_VL << at(file,line) << ": In tag "<< tag
|
||||
<< " which begins here, " << "key "<< key << " wasn't allowed\n";
|
||||
}
|
||||
void missing_key_error(const std::string & file, int line,
|
||||
const std::string & tag,const std::string & key
|
||||
){
|
||||
WRN_VL << at(file,line) << ": In tag "<< tag
|
||||
<< " which begins here, " << " missing key "<< key << "\n";
|
||||
}
|
||||
void wrong_value_error(const std::string & file, int line,
|
||||
const std::string & tag,const std::string & key,
|
||||
const std::string & value){
|
||||
WRN_VL << at(file,line) << ": In tag "<< tag
|
||||
<< " which begins here, " << "key "<< key << " have wrong value "
|
||||
<< value << "\n";
|
||||
}
|
||||
|
||||
schema_validator::schema_validator():config_read_(false),stack_(){
|
||||
ERR_VL << "No schema file\n";
|
||||
throw abstract_validator::error("No schema file\n");
|
||||
}
|
||||
|
||||
schema_validator::~schema_validator(){}
|
||||
|
||||
schema_validator::schema_validator(const std::string & config_file_name)
|
||||
:stack_(){
|
||||
config_read_ = read_config_file(config_file_name);
|
||||
if (! config_read_) {
|
||||
ERR_VL << "Schema file "<< config_file_name << " was not read.\n";
|
||||
throw abstract_validator::error("Schema file "+ config_file_name
|
||||
+ " was not read.\n");
|
||||
}else{
|
||||
stack_.push(&root_);
|
||||
counter_.push(cnt_map());
|
||||
cache_.push(message_map());
|
||||
root_.expand_all(root_);
|
||||
LOG_VL << "Schema file "<< config_file_name << " was read.\n"
|
||||
<< "Validator initialized\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool schema_validator::read_config_file(const std::string &filename){
|
||||
config cfg;
|
||||
try {
|
||||
preproc_map preproc(
|
||||
game_config::config_cache::instance().get_preproc_map());
|
||||
scoped_istream stream = preprocess_file(filename, &preproc);
|
||||
read(cfg, *stream);
|
||||
} catch(config::error&) {
|
||||
return false;
|
||||
}
|
||||
foreach (const config &g, cfg.child_range("wml_schema")) {
|
||||
foreach (const config &schema, g.child_range("tag")) {
|
||||
if (schema["name"].str() == "root"){
|
||||
//@NOTE Don't know, maybe merging of roots needed.
|
||||
root_ = class_tag (schema);
|
||||
}
|
||||
}
|
||||
foreach (const config &type, g.child_range("type")) {
|
||||
try{
|
||||
types_[type["name"].str()] = boost::regex( type["value"].str());
|
||||
}
|
||||
catch (std::exception){
|
||||
// Need to check all type values in schema-generator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* Please, @Note that there is some magic in pushing and poping to/from stacks.
|
||||
* assume they all are on their place due to parser algorithm
|
||||
* and validation logic
|
||||
*/
|
||||
void schema_validator::open_tag(const std::string & name,int start_line,
|
||||
const std::string &file){
|
||||
if (! stack_.empty()){
|
||||
const class_tag * tag = NULL;
|
||||
if (stack_.top()){
|
||||
tag = stack_.top()->find_tag(name,root_);
|
||||
if (! tag){
|
||||
wrong_tag_error(file,start_line,name);
|
||||
}else{
|
||||
counter & cnt = counter_.top()[name];
|
||||
++ cnt.cnt;
|
||||
}
|
||||
}
|
||||
stack_.push(tag);
|
||||
}else{
|
||||
stack_.push(NULL);
|
||||
}
|
||||
counter_.push(cnt_map());
|
||||
cache_.push(message_map());
|
||||
}
|
||||
|
||||
void schema_validator::close_tag(){
|
||||
stack_.pop();
|
||||
counter_.pop();
|
||||
//cache_ is cleared in another place.
|
||||
}
|
||||
|
||||
bool schema_validator::validate(const config & cfg, const std::string & name,
|
||||
int start_line,
|
||||
const std::string &file){
|
||||
bool retval = false;
|
||||
//close previous errors and print them to output.
|
||||
message_map::iterator cache_it = cache_.top().begin();
|
||||
for (;cache_it != cache_.top().end();++cache_it){
|
||||
for (message_list::iterator i = cache_it->second.begin();
|
||||
i != cache_it->second.end(); ++i){
|
||||
print(*i);
|
||||
}
|
||||
}
|
||||
cache_.pop();
|
||||
// clear cache
|
||||
cache_it = cache_.top().find(&cfg);
|
||||
if (cache_it != cache_.top().end()){
|
||||
cache_it->second.clear();
|
||||
}
|
||||
// Please note that validating unknown tag keys the result will be false
|
||||
if (!stack_.empty() && stack_.top() && config_read_){
|
||||
retval = true;
|
||||
// checking existing keys
|
||||
foreach (const config::attribute & attr, cfg.attribute_range()){
|
||||
const class_key * key =stack_.top()->find_key(attr.first);
|
||||
if (key){
|
||||
std::map<std::string,boost::regex>::iterator itt =
|
||||
types_.find(key->get_type());
|
||||
if (itt!= types_.end()){
|
||||
boost::smatch sub;
|
||||
bool res = boost::regex_match(attr.second.str(),
|
||||
sub,itt->second);
|
||||
if (!res ) {
|
||||
cache_.top()[&cfg].push_back(
|
||||
message_info(WRONG_VALUE,file,start_line,
|
||||
stack_.top()->get_name(),
|
||||
key->get_name(),
|
||||
attr.second.str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
cache_.top()[&cfg].push_back(
|
||||
message_info(EXTRA_KEY,file,start_line,name,attr.first));
|
||||
retval = false;
|
||||
}
|
||||
}
|
||||
// Checking all elements counters.
|
||||
class_tag::all_const_tag_iterators p = stack_.top()->tags();
|
||||
for (class_tag::const_tag_iterator tag = p.first;
|
||||
tag != p.second ; ++tag){
|
||||
int cnt = counter_.top()[tag->first].cnt;
|
||||
if (tag->second.get_min() > cnt){
|
||||
cache_.top()[&cfg].push_back(
|
||||
message_info(MISSING_TAG,file,start_line,tag->first ));
|
||||
continue;
|
||||
}
|
||||
if (tag->second.get_max() < cnt){
|
||||
cache_.top()[&cfg].push_back(
|
||||
message_info(EXTRA_TAG,file,start_line,tag->first ));
|
||||
}
|
||||
}
|
||||
// Checking if all mandatory keys are present
|
||||
class_tag::all_const_key_iterators k = stack_.top()->keys();
|
||||
for (class_tag::const_key_iterator key = k.first;
|
||||
key != k.second ; ++key){
|
||||
if (key->second.is_mandatory()){
|
||||
if (cfg.get(key->first) == NULL){
|
||||
cache_.top()[&cfg].push_back(
|
||||
message_info(MISSING_KEY,file,start_line,
|
||||
stack_.top()->get_name(),key->first ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void schema_validator::print(message_info & el){
|
||||
switch (el.type){
|
||||
case WRONG_TAG:
|
||||
wrong_tag_error(el.file,el.line,el.tag);
|
||||
break;
|
||||
case EXTRA_TAG:
|
||||
extra_tag_error(el.file,el.line,el.tag);
|
||||
break;
|
||||
case MISSING_TAG:
|
||||
missing_tag_error(el.file,el.line,el.tag);
|
||||
break;
|
||||
case EXTRA_KEY:
|
||||
extra_key_error(el.file,el.line,el.tag,el.key);
|
||||
break;
|
||||
case WRONG_VALUE:
|
||||
wrong_value_error(el.file,el.line,el.tag,el.key,el.value);
|
||||
break;
|
||||
case MISSING_KEY:
|
||||
missing_key_error(el.file,el.line,el.tag,el.key);
|
||||
}
|
||||
}
|
||||
}//namespace schema_validation{
|
126
src/serialization/schema_validator.hpp
Normal file
126
src/serialization/schema_validator.hpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#ifndef SCHEMA_VALIDATOR_HPP
|
||||
#define SCHEMA_VALIDATOR_HPP
|
||||
|
||||
#include "serialization/validator.hpp"
|
||||
#include "tools/schema/tag.hpp"
|
||||
|
||||
#include "config.hpp"
|
||||
#include "config_cache.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
|
||||
|
||||
|
||||
#include "boost/regex.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <stack>
|
||||
|
||||
/** @file
|
||||
* One of the realizations of serialization/validator.hpp abstract validator.
|
||||
*/
|
||||
namespace schema_validation{
|
||||
/**
|
||||
* Realization of serialization/validator.hpp abstract validator.
|
||||
* Based on stack. Uses some stacks to store different info.
|
||||
*/
|
||||
class schema_validator : public abstract_validator{
|
||||
public:
|
||||
schema_validator();
|
||||
virtual ~schema_validator();
|
||||
/**
|
||||
* Initializes validator from file.
|
||||
* Throws abstract_validator::error if any error.
|
||||
*/
|
||||
schema_validator(const std::string & filename);
|
||||
/**
|
||||
* Prints schema tree to stream
|
||||
* Usually used to testing, whether schema config was read and expanded
|
||||
*/
|
||||
virtual void open_tag(const std::string & name,int start_line=0,
|
||||
const std::string &file="");
|
||||
virtual void close_tag();
|
||||
virtual bool validate(const config & cfg, const std::string & name,
|
||||
int start_line, const std::string &file);
|
||||
|
||||
private:
|
||||
// types section
|
||||
// Just some magic to ensure zero initialization.
|
||||
struct counter{
|
||||
int cnt;
|
||||
counter(): cnt(0){}
|
||||
};
|
||||
/**
|
||||
* Counters are mapped by tag name
|
||||
*/
|
||||
typedef std::map<std::string,counter> cnt_map;
|
||||
|
||||
/**
|
||||
* And counter maps are organize in stack.
|
||||
*/
|
||||
typedef std::stack<cnt_map> cnt_stack;
|
||||
|
||||
enum message_type{WRONG_TAG,EXTRA_TAG,MISSING_TAG,
|
||||
EXTRA_KEY,MISSING_KEY,WRONG_VALUE};
|
||||
//error_cache
|
||||
/**
|
||||
* Messages are cached.
|
||||
* The reason is next: in file where [tag]...[/tag][+tag]...[/tag]
|
||||
* config object will be validated each [/tag]. So message can be as wrong
|
||||
* (when [+tag] section contains missed elements) as duplicated.
|
||||
*
|
||||
* Messages are mapped by config*. That ensures uniqueness.
|
||||
* Also message-maps are organized in stack to avoid memory leaks.
|
||||
*/
|
||||
struct message_info{
|
||||
message_type type;
|
||||
std::string file;
|
||||
int line;
|
||||
std::string tag;
|
||||
std::string key;
|
||||
std::string value;
|
||||
message_info(message_type t,
|
||||
const std::string& file,
|
||||
int line = 0,
|
||||
const std::string& tag = "",
|
||||
const std::string& key = "",
|
||||
const std::string& value = "")
|
||||
:type(t),file(file),line(line),tag(tag),key(key),
|
||||
value(value){}
|
||||
};
|
||||
typedef std::deque<message_info> message_list;
|
||||
typedef std::map<const config *, message_list> message_map;
|
||||
|
||||
void print(message_info &);
|
||||
/**
|
||||
* Reads config from input.
|
||||
*/
|
||||
bool read_config_file(const std::string & filename);
|
||||
/**
|
||||
* Shows, if validator is intialized with schema file;
|
||||
*/
|
||||
bool config_read_;
|
||||
|
||||
/**
|
||||
* Root of schema information
|
||||
*/
|
||||
class_tag root_;
|
||||
|
||||
std::stack<const class_tag *> stack_;
|
||||
/**
|
||||
* Contains number of children
|
||||
*/
|
||||
cnt_stack counter_;
|
||||
/**
|
||||
* Caches error messages.
|
||||
*/
|
||||
std::stack<message_map> cache_;
|
||||
/**
|
||||
* Type validators.
|
||||
*/
|
||||
std::map<std::string,boost::regex> types_;
|
||||
};
|
||||
}//namespace schema_validation{
|
||||
|
||||
#endif // SCHEMA_VALIDATOR_HPP
|
Loading…
Add table
Reference in a new issue