#define CPPCMS_SOURCE
#include "form.h"
#include "config.h"
#include "encoding.h"
#include "regex.h"
#include "filters.h"
#include "cppcms_error.h"
#include <stack>
#ifdef CPPCMS_USE_EXTERNAL_BOOST
# include <boost/format.hpp>
#else // Internal Boost
# include <cppcms_boost/format.hpp>
namespace boost = cppcms_boost;
#endif
namespace cppcms {
struct form_context::data {};
form_context::form_context() :
html_type_(as_html),
html_list_type_(as_p),
widget_part_type_(first_part),
output_(0)
{
}
form_context::form_context(std::ostream &out,form_context::html_type t,form_context::html_list_type hlt) :
html_type_(t),
html_list_type_(hlt),
widget_part_type_(first_part),
output_(&out)
{
}
void form_context::html(html_type t)
{
html_type_=t;
}
void form_context::html_list(html_list_type t)
{
html_list_type_ = t;
}
void form_context::widget_part(widget_part_type t)
{
widget_part_type_ = t;
}
void form_context::out(std::ostream &out)
{
output_ = &out;
}
std::ostream &form_context::out() const
{
if(!output_)
throw cppcms_error("Can't use form context without assigned output");
return *output_;
}
form_flags::html_type form_context::html() const
{
return static_cast<html_type>(html_type_);
}
form_flags::html_list_type form_context::html_list() const
{
return static_cast<html_list_type>(html_list_type_);
}
form_flags::widget_part_type form_context::widget_part() const
{
return static_cast<widget_part_type>(widget_part_type_);
}
form_context::~form_context()
{
}
base_form::base_form()
{
}
base_form::~base_form()
{
}
// Meanwhile -- empty
struct form::data {
// TOADD
};
form::form() : parent_(0)
{
}
form::~form()
{
for(unsigned i=0;i<elements_.size();i++) {
if(elements_[i].second)
delete elements_[i].first;
}
}
void form::parent(base_form *parent)
{
parent_=&dynamic_cast<form &>(*parent);
}
form *form::parent()
{
return parent_;
}
void form::render(form_context &context)
{
for(unsigned int i=0;i<elements_.size();i++) {
elements_[i].first->render(context);
}
}
void form::load(http::context &cont)
{
for(unsigned int i=0;i<elements_.size();i++) {
elements_[i].first->load(cont);
}
}
bool form::validate()
{
bool result=true;
for(unsigned int i=0;i<elements_.size();i++) {
result = elements_[i].first->validate() & result;
}
return result;
}
void form::clear()
{
for(unsigned int i=0;i<elements_.size();i++) {
elements_[i].first->clear();
}
}
void form::add(widgets::base_widget &form)
{
elements_.push_back(widget_type(&form,false));
form.parent(this);
}
void form::add(form &subform)
{
elements_.push_back(widget_type(&subform,false));
subform.parent(this);
}
void form::attach(form *subform)
{
elements_.push_back(widget_type(subform,true));
subform->parent(this);
}
void form::attach(widgets::base_widget *subform)
{
elements_.push_back(widget_type(subform,true));
subform->parent(this);
}
struct form::iterator::data {};
form::iterator::iterator() : current_(0), offset_(0)
{
}
form::iterator::iterator(form &f) : current_(&f), offset_(0)
{
next();
}
form::iterator::~iterator()
{
}
form::iterator::iterator(form::iterator const &other) :
return_positions_(other.return_positions_),
current_(other.current_),
offset_(other.offset_),
d(other.d)
{
}
form::iterator const &form::iterator::operator=(form::iterator const &other)
{
if(this != &other) {
return_positions_ = other.return_positions_;
current_ = other.current_;
offset_=other.offset_;
d=other.d;
}
return *this;
}
bool form::iterator::equal(form::iterator const &other) const
{
return current_ == other.current_
&& offset_ == other.offset_
&& return_positions_ == other.return_positions_;
}
void form::iterator::zero()
{
current_ = 0;
offset_ = 0;
}
void form::iterator::next()
{
for(;;) {
if(!current_)
return;
if(offset_ >=current_->elements_.size()) {
if(return_positions_.empty()) {
zero();
return;
}
offset_ = return_positions_.top();
return_positions_.pop();
current_ = current_->parent();
}
else if(dynamic_cast<widgets::base_widget *>(current_->elements_[offset_].first)!=0) {
offset_++;
return;
}
else {
// Elements can be only base_widget or form... so it should be safe
current_ = static_cast<form *>(current_->elements_[offset_].first);
return_positions_.push(offset_+1);
offset_=0;
}
}
}
widgets::base_widget *form::iterator::get() const
{
return static_cast<widgets::base_widget *>(current_->elements_[offset_ - 1].first);
}
form::iterator form::begin()
{
return form::iterator(*this);
}
form::iterator form::end()
{
return form::iterator();
}
namespace widgets {
////////////////////////////////
// widgets::base_widget
////////////////////////////////
struct base_widget::data {};
base_widget::base_widget() :
parent_(0),
is_valid_(1),
is_set_(0),
is_disabled_(0),
is_generation_done_(0),
has_message_(0),
has_error_(0),
has_help_(0)
{
}
base_widget::~base_widget()
{
}
bool base_widget::has_message()
{
return has_message_;
}
bool base_widget::has_help()
{
return has_help_;
}
bool base_widget::has_error_message()
{
return has_error_;
}
void base_widget::parent(base_form *parent)
{
parent_=&dynamic_cast<form &>(*parent);
}
form *base_widget::parent()
{
return parent_;
}
bool base_widget::set()
{
return is_set_;
}
bool base_widget::valid()
{
return is_valid_;
}
std::string base_widget::id()
{
return id_;
}
std::string base_widget::name()
{
return name_;
}
locale::message base_widget::message()
{
return message_;
}
locale::message base_widget::error_message()
{
return error_message_;
}
locale::message base_widget::help()
{
return help_;
}
void base_widget::set(bool v)
{
is_set_=v;
}
void base_widget::valid(bool v)
{
is_valid_=v;
}
void base_widget::id(std::string v)
{
id_=v;
}
void base_widget::name(std::string v)
{
name_=v;
}
void base_widget::message(locale::message const &v)
{
has_message_ = 1;
message_=v;
}
void base_widget::error_message(locale::message const &v)
{
has_error_ = 1;
error_message_=v;
}
void base_widget::help(locale::message const &v)
{
has_help_ = 1;
help_=v;
}
void base_widget::message(std::string v)
{
has_message_ = 1;
message_=locale::message("#NOTRANS#" + v);
}
void base_widget::error_message(std::string v)
{
has_error_ = 1;
error_message_=locale::message("#NOTRANS#" + v);
}
void base_widget::help(std::string v)
{
has_help_ = 1;
help_=locale::message("#NOTRANS#" + v);
}
void base_widget::attributes_string(std::string v)
{
attr_=v;
}
bool base_widget::disabled()
{
return is_disabled_;
}
void base_widget::disabled(bool v)
{
is_disabled_=v;
}
std::string base_widget::attributes_string()
{
return attr_;
}
void base_widget::generate(int position,form_context *context)
{
if(is_generation_done_)
return;
if(name_.empty()) {
name_ = (boost::format("_%1%",std::locale::classic()) % position).str();
}
is_generation_done_ = 1;
}
void base_widget::auto_generate(form_context *context)
{
if(is_generation_done_)
return;
if(parent() == 0) {
generate(1,context);
return;
}
form *top;
for(top = parent();top->parent();top=top->parent())
;
int i=1;
form::iterator p=top->begin(),e=top->end();
while(p!=e) {
p->generate(i,context);
++p;
++i;
}
}
void base_widget::render(form_context &context)
{
auto_generate(&context);
std::ostream &output = context.out();
switch(context.html_list()) {
case as_p: output<<"<p>"; break;
case as_table: output<<"<tr><th>"; break;
case as_ul: output<<"<li>"; break;
case as_dl: output<<"<dt>"; break;
default: ;
}
if(has_message()) {
if(id_.empty())
output << filters::escape(message());
else
output<<"<label for=\"" << id() << "\">" << filters::escape(message()) <<":</label> ";
}
else {
output<<" ";
}
switch(context.html_list()) {
case as_table: output<<"</th><td>"; break;
case as_dl: output<<"</dt><dd>"; break;
default: ;
}
if(!valid()) {
output<<"<span class=\"cppcms_form_error\">";
if(has_error_message())
output<<filters::escape(error_message());
else
output<<"*";
output<<"</span> ";
}
else {
output<<" ";
}
output<<"<span class=\"cppcms_form_input\">";
context.widget_part(first_part);
render_input(context);
output<<attr_;
context.widget_part(second_part);
render_input(context);
output<<"</span>";
if(has_help()) {
output<<"<span class=\"cppcms_form_help\">"<<filters::escape(help())<<"</span>";
}
switch(context.html_list()) {
case as_p: output<<"</p>\n"; break;
case as_table: output<<"</td><tr>\n"; break;
case as_ul: output<<"</li>\n"; break;
case as_dl: output<<"</dd>\n"; break;
case as_space:
if(context.html() == as_xhtml)
output<<"<br />\n";
else
output<<"<br>\n";
break;
default: ;
}
}
void base_widget::render_attributes(form_context &context)
{
auto_generate(&context);
std::ostream &output = context.out();
if(!id_.empty()) output << "id=\"" << id_ << "\" ";
if(!name_.empty()) output << "name=\"" << name_ << "\" ";
if(disabled()) {
if(context.html() == as_xhtml)
output << "disabled=\"disabled\" ";
else
output << "disabled ";
}
}
void base_widget::clear()
{
set(false);
}
bool base_widget::validate()
{
valid(true);
return true;
}
////////////////////////////////
// widgets::base_text
////////////////////////////////
struct base_text::data {};
base_text::base_text() : low_(0),high_(-1),validate_charset_(true)
{
}
base_text::~base_text()
{
}
std::string base_text::value()
{
if(!set())
throw cppcms_error("Value was not loaded");
return value_;
}
void base_text::value(std::string v)
{
set(true);
value_=v;
}
void base_text::non_empty()
{
limits(1,-1);
}
void base_text::limits(int min,int max)
{
low_=min;
high_=max;
}
std::pair<int,int> base_text::limits()
{
return std::make_pair(low_,high_);
}
void base_text::validate_charset(bool v)
{
validate_charset_=v;
}
bool base_text::validate_charset()
{
return validate_charset_;
}
void base_text::load(http::context &context)
{
auto_generate();
value_.clear();
code_points_ = 0;
set(false);
valid(true);
if(name().empty()) {
return;
}
http::request::form_type::const_iterator p;
p=context.request().post_or_get().find(name());
if(p==context.request().post_or_get().end()) {
return;
}
value_=p->second;
set(true);
if(validate_charset_) {
code_points_ = 0;
if(!encoding::valid(context.locale(),value_.data(),value_.data()+value_.size(),code_points_))
valid(false);
}
else {
code_points_=value_.size();
}
}
bool base_text::validate()
{
if(!valid())
return false;
if(!set() && low_==0 && high_==-1) {
valid(true);
return true;
}
if(code_points_ < size_t(low_) || (high_ >=0 && code_points_ > size_t(high_))) {
valid(false);
return false;
}
return true;
}
//////////////////////////////////////////////
/// widgets::text
/////////////////////////////////////////////
struct text::data {};
text::~text() {}
text::text(): base_html_input("text"), size_(-1) {}
text::text(std::string const &type): base_html_input(type), size_(-1) {}
void text::render_value(form_context &context)
{
if(set()) {
context.out() << " value=\"" << util::escape(value()) << "\"";
}
}
void text::render_attributes(form_context &context)
{
base_widget::render_attributes(context);
std::ostream &output = context.out();
if(size_ >= 0)
output << boost::format("size=\"%1%\" ",std::locale::classic()) % size_;
std::pair<int,int> lm=limits();
if(lm.second >= 0 && validate_charset()) {
output << boost::format("maxlength=\"%1%\" ",std::locale::classic()) % lm.second;
}
}
struct base_html_input::data{};
base_html_input::base_html_input(std::string const &type) : type_(type) {}
base_html_input::~base_html_input() {}
void base_html_input::render_input(form_context &context)
{
auto_generate(&context);
std::ostream &output=context.out();
if(context.widget_part() == first_part) {
output<<"<input type=\""<<type_<<"\" ";
render_attributes(context);
render_value(context);
}
else {
if(context.html() == as_xhtml)
output<<" />";
else
output<<" >";
}
}
//////////////////////////////////////////////
/// widgets::textarea
/////////////////////////////////////////////
struct textarea::data {};
textarea::textarea() : rows_(-1), cols_(-1) {}
textarea::~textarea() {}
int textarea::rows() { return rows_; }
int textarea::cols() { return cols_; }
void textarea::rows(int n) { rows_ = n; }
void textarea::cols(int n) { cols_ = n; }
void textarea::render_input(form_context &context)
{
std::ostream &output = context.out();
if(context.widget_part() == first_part)
{
output<<"<textarea ";
render_attributes(context);
if(rows_ >= 0) {
output<<boost::format("rows=\"%1%\"",std::locale::classic()) % rows_;
}
if(cols_ >= 0) {
output<<boost::format("cols=\"%1%\"",std::locale::classic()) % cols_;
}
}
else {
if(set()) {
output << ">"<<util::escape(value())<<"</textarea>";
}
else {
output << "></textarea>";
}
}
}
////////////////////////
/// Password widget ///
////////////////////////
struct password::data {};
password::password() : text("password"), password_to_check_(0)
{
}
password::~password()
{
}
void password::check_equal(password &p2)
{
password_to_check_ = &p2;
}
bool password::validate()
{
if(!text::validate()) {
value("");
return false;
}
if(password_to_check_) {
if(!password_to_check_->set() || !set() || password_to_check_->value()!=value()) {
valid(false);
value("");
password_to_check_->value("");
return false;
}
}
return true;
}
struct regex_field::data {};
regex_field::regex_field() : expression_(0) {}
regex_field::regex_field(util::regex const &e) : expression_(&e) {}
regex_field::~regex_field() {}
void regex_field::regex(util::regex const &e)
{
expression_ = &e;
}
bool regex_field::validate()
{
if(!text::validate())
return false;
valid(set() && expression_->match(value()));
return valid();
}
struct email::data {};
namespace {
util::regex email_regex("^[^@]+@[^@]+$");
}
email::email() : regex_field(email_regex) {}
email::~email() {}
struct checkbox::data {};
checkbox::checkbox(std::string const &type) :
base_html_input(type),
identification_("y"),
value_(false)
{
set(true);
}
checkbox::checkbox() :
base_html_input("checkbox"),
identification_("y"),
value_(false)
{
set(true);
}
checkbox::~checkbox()
{
}
bool checkbox::value()
{
return value_;
}
void checkbox::value(bool v)
{
value_=v;
}
void checkbox::identification(std::string const &id)
{
identification_=id;
}
std::string checkbox::identification()
{
return identification_;
}
void checkbox::render_value(form_context &context)
{
if(value()) {
if(context.html() == as_xhtml)
context.out() << " checked=\"checked\" ";
else
context.out() << " checked ";
}
context.out() << "value=\""<<util::escape(identification_)<<"\" ";
}
void checkbox::load(http::context &context)
{
std::pair<http::request::form_type::const_iterator,http::request::form_type::const_iterator>
range=context.request().post_or_get().equal_range(name());
value(false);
while(range.first != range.second) {
if(range.first->second == identification_) {
value(true);
break;
}
++range.first;
}
}
struct select_multiple::data{};
select_multiple::element::element():
selected(0),
need_translation(0),
original_select(0)
{
}
select_multiple::element::element(std::string const &val,locale::message const &msg,bool sel) :
selected(sel),
need_translation(1),
original_select(sel),
id(val),
tr_option(msg)
{
}
select_multiple::element::element(std::string const &val,std::string const &msg,bool sel) :
selected(sel),
need_translation(0),
original_select(sel),
id(val),
str_option(msg)
{
}
select_multiple::select_multiple() :
low_(0),
high_(std::numeric_limits<unsigned>::max()),
rows_(0)
{
}
select_multiple::~select_multiple()
{
}
void select_multiple::add(std::string const &opt,std::string const &id,bool selected)
{
elements_.push_back(element(id,opt,selected));
}
void select_multiple::add(locale::message const &opt,std::string const &id,bool selected)
{
elements_.push_back(element(id,opt,selected));
}
void select_multiple::add(std::string const &opt,bool selected)
{
std::string id=(boost::format("%1%",std::locale::classic()) % elements_.size()).str();
elements_.push_back(element(id,opt,selected));
}
void select_multiple::add(locale::message const &opt,bool selected)
{
std::string id=(boost::format("%1%",std::locale::classic()) % elements_.size()).str();
elements_.push_back(element(id,opt,selected));
}
std::vector<bool> select_multiple::selected_map()
{
std::vector<bool> flags(elements_.size(),false);
for(unsigned i=0;i<elements_.size();i++)
flags[i]=elements_[i].selected;
return flags;
}
std::set<std::string> select_multiple::selected_ids()
{
std::set<std::string> ids;
for(unsigned i=0;i<elements_.size();i++)
if(elements_[i].selected)
ids.insert(elements_[i].id);
return ids;
}
void select_multiple::clear()
{
for(unsigned i=0;i<elements_.size();i++){
elements_[i].selected=elements_[i].original_select;
}
}
void select_multiple::load(http::context &context)
{
auto_generate();
std::pair<http::request::form_type::const_iterator,http::request::form_type::const_iterator>
range=context.request().post_or_get().equal_range(name());
std::set<std::string> keys;
for(http::request::form_type::const_iterator p=range.first;p!=range.second;++p)
keys.insert(p->second);
for(unsigned i=0;i<elements_.size();i++) {
elements_[i].selected= keys.find(elements_[i].id) != keys.end();
}
}
bool select_multiple::validate()
{
unsigned count=0;
for(unsigned i=0;i<elements_.size();i++)
count += elements_[i].selected;
if(low_ <= count && count <= high_) {
valid(true);
return true;
}
else {
valid(false);
return false;
}
}
void select_multiple::render_input(form_context &context)
{
auto_generate(&context);
std::ostream &out=context.out();
if(context.widget_part() == first_part) {
if(context.html() == as_xhtml)
out<<"<select multiple=\"multiple\" ";
else
out<<"<select multiple ";
if(rows_ > 0)
out << boost::format(" size=\"%1%\" ",std::locale::classic()) % rows_;
render_attributes(context);
}
else {
out<<" >\n";
for(unsigned i=0;i<elements_.size();i++) {
element &el=elements_[i];
out << "<option value=\"" << util::escape(el.id) <<"\" ";
if(el.selected) {
if(context.html() == as_xhtml)
out << "selected=\"selected\" ";
else
out << "selected ";
}
out << ">";
if(el.need_translation)
out << filters::escape(el.tr_option);
else
out << util::escape(el.str_option);
out << "</option>\n";
}
out << "</select>";
}
}
void select_multiple::non_empty()
{
at_least(1);
}
void select_multiple::at_least(unsigned l)
{
low_ = l;
}
void select_multiple::at_most(unsigned h)
{
high_ = h;
}
unsigned select_multiple::at_least()
{
return low_;
}
unsigned select_multiple::at_most()
{
return high_;
}
unsigned select_multiple::rows()
{
return rows_;
}
void select_multiple::rows(unsigned v)
{
rows_=v;
}
////////////////////
// Select base
///////////////////
struct select_base::data {};
struct select_base::element::data {};
select_base::element::element() : need_translation(0)
{
}
select_base::element::element(std::string const &v,std::string const &msg) :
need_translation(0),
id(v),
str_option(msg)
{
}
select_base::element::element(std::string const &v,locale::message const &msg) :
need_translation(1),
id(v),
tr_option(msg)
{
}
select_base::select_base() : selected_(-1), default_selected_(-1), non_empty_(0)
{
}
select_base::~select_base()
{
}
void select_base::add(std::string const &str,std::string const &id)
{
elements_.push_back(element(id,str));
}
void select_base::add(locale::message const &str,std::string const &id)
{
elements_.push_back(element(id,str));
}
void select_base::add(std::string const &str)
{
std::string id=(boost::format("%1%",std::locale::classic()) % elements_.size()).str();
elements_.push_back(element(id,str));
}
void select_base::add(locale::message const &str)
{
std::string id=(boost::format("%1%",std::locale::classic()) % elements_.size()).str();
elements_.push_back(element(id,str));
}
void select_base::selected(int no)
{
if(no >= int(elements_.size()))
throw cppcms_error("select_base::invalid index");
else if(no < 0) no = -1;
selected_ = no;
default_selected_ = no;
}
void select_base::selected_id(std::string id)
{
if(id.empty()) {
selected_ = -1;
default_selected_ = -1;
return;
}
for(unsigned i=0;i<elements_.size();i++) {
if(id==elements_[i].id) {
selected_ = i;
default_selected_ = i;
return;
}
}
throw cppcms_error("Select base::invalid index: " + id);
}
void select_base::clear()
{
selected_ = default_selected_;
}
void select_base::non_empty()
{
non_empty_ = 1;
}
int select_base::selected()
{
return selected_;
}
std::string select_base::selected_id()
{
if(selected_ < 0 || selected_ >= int(elements_.size()))
return "";
return elements_[selected_].id;
}
bool select_base::validate()
{
if(non_empty_ && selected_ == -1)
valid(false);
else
valid(true);
return valid();
}
void select_base::load(http::context &context)
{
auto_generate();
std::pair<http::request::form_type::const_iterator,http::request::form_type::const_iterator>
range=context.request().post_or_get().equal_range(name());
selected_ = -1;
http::request::form_type::const_iterator p=range.first++;
if(p!=range.second && range.first==range.second) {
std::string key = p->second;
for(unsigned i=0;i<elements_.size();i++) {
if(elements_[i].id == key) {
selected_ = i;
break;
}
}
}
}
////////////////
/// select
///////////////////
struct select::data {};
select::select() {}
select::~select() {}
void select::render_input(form_context &context)
{
auto_generate(&context);
std::ostream &out=context.out();
if(context.widget_part() == first_part) {
out<<"<select ";
render_attributes(context);
}
else {
out<<" >\n";
for(unsigned i=0;i<elements_.size();i++) {
element &el=elements_[i];
out << "<option value=\"" << util::escape(el.id) <<"\" ";
if(int(i) == selected()) {
if(context.html() == as_xhtml)
out << "selected=\"selected\" ";
else
out << "selected ";
}
out << ">";
if(el.need_translation)
out << filters::escape(el.tr_option);
else
out << util::escape(el.str_option);
out << "</option>\n";
}
out << "</select>";
}
}
///////////////////////////
/// Radio
/////////////////////////
struct radio::data {};
radio::radio() : vertical_(1)
{
}
radio::~radio() {}
void radio::vertical(bool v)
{
vertical_ = v;
}
bool radio::vertical()
{
return vertical_;
}
void radio::render_input(form_context &context)
{
auto_generate(&context);
std::ostream &out=context.out();
if(context.widget_part() == first_part) {
out<<"<div class=\"cppcms_radio\" ";
if(!id().empty()) out << "id=\"" << id() << "\" ";
}
else {
out<<" >\n";
for(unsigned i=0;i<elements_.size();i++) {
element &el=elements_[i];
out << "<input type=\"radio\" value=\""
<< util::escape(el.id) <<"\" ";
if(!name().empty()) {
out<< "name=\"" << name() << "\" ";
}
if(int(i) == selected()) {
if(context.html() == as_xhtml)
out << "checked=\"checked\" ";
else
out << "checked ";
}
if(disabled()) {
if(context.html() == as_xhtml)
out << "disabled=\"disabled\" ";
else
out << "disabled ";
}
if(context.html() == as_xhtml)
out << "/> ";
else
out << "> ";
if(el.need_translation)
out << filters::escape(el.tr_option);
else
out << util::escape(el.str_option);
if(vertical_)
if(context.html() == as_xhtml)
out << "<br/>\n";
else
out << "<br>\n";
else
out << "\n";
}
out << "</div>";
}
}
//////////////
// Submit
////////////////
struct submit::data {};
submit::submit() :
base_html_input("submit"),
pressed_(false)
{
set(true);
}
submit::~submit()
{
}
void submit::value(std::string val)
{
value_=locale::message("#NOTRANS#" + val);
}
void submit::value(locale::message const &msg)
{
value_=msg;
}
void submit::render_value(form_context &context)
{
context.out() << "value=\"" << filters::escape(value_) << "\" ";
}
void submit::load(http::context &context)
{
pressed_ = context.request().post_or_get().find(name()) != context.request().post_or_get().end();
}
bool submit::value()
{
return pressed_;
}
} // widgets
} // cppcms