1
0
Fork 0
flightgear/src/Network/http/PropertyUriHandler.cxx

412 lines
12 KiB
C++
Raw Normal View History

2014-03-03 21:59:16 +01:00
// PropertyUriHandler.cxx -- a web form interface to the property tree
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "PropertyUriHandler.hxx"
#include <Main/fg_props.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <map>
#include <algorithm>
#include <cstring>
using std::string;
using std::map;
using std::vector;
namespace flightgear {
namespace http {
class DOMElement {
public:
virtual ~DOMElement() {}
virtual std::string render() const = 0;
};
class DOMTextElement : public DOMElement {
public:
DOMTextElement( const string & text ) : _text(text) {}
virtual std::string render() const { return _text; }
private:
string _text;
};
class DOMNode : public DOMElement {
public:
DOMNode( const string & name ) : _name(name) {}
virtual ~DOMNode();
virtual std::string render() const;
virtual void addChild( DOMElement * child );
virtual void setAttribute( const string & name, const string & value );
protected:
string _name;
typedef vector<const DOMElement*> Children_t;
typedef map<string,string> Attributes_t;
Children_t _children;
Attributes_t _attributes;
};
DOMNode::~DOMNode()
{
for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it )
delete *it;
}
string DOMNode::render() const
{
string reply;
reply.append( "<" ).append( _name );
for( Attributes_t::const_iterator it = _attributes.begin(); it != _attributes.end(); ++it ) {
reply.append( " " );
reply.append( it->first );
reply.append( "=\"" );
reply.append( it->second );
reply.append( "\"" );
}
if( _children.empty() ) {
reply.append( " />\r" );
return reply;
} else {
reply.append( ">" );
}
for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) {
reply.append( (*it)->render() );
}
reply.append( "</" ).append( _name ).append( ">\r" );
return reply;
}
void DOMNode::addChild( DOMElement * child )
{
_children.push_back( child );
}
void DOMNode::setAttribute( const string & name, const string & value )
{
_attributes[name] = value;
}
class SortedChilds : public simgear::PropertyList {
public:
SortedChilds( SGPropertyNode_ptr node ) {
for (int i = 0; i < node->nChildren(); i++)
push_back(node->getChild(i));
std::sort(begin(), end(), CompareNodes());
}
private:
class CompareNodes {
public:
bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
int r = strcmp(a->getName(), b->getName());
return r ? r < 0 : a->getIndex() < b->getIndex();
}
};
};
static const char * getPropertyTypeString( simgear::props::Type type )
{
switch( type ) {
case simgear::props::NONE: return "";
case simgear::props::ALIAS: return "alias";
case simgear::props::BOOL: return "bool";
case simgear::props::INT: return "int";
case simgear::props::LONG: return "long";
case simgear::props::FLOAT: return "float";
case simgear::props::DOUBLE: return "double";
case simgear::props::STRING: return "string";
case simgear::props::UNSPECIFIED: return "unspecified";
case simgear::props::EXTENDED: return "extended";
case simgear::props::VEC3D: return "vec3d";
case simgear::props::VEC4D: return "vec4d";
default: return "?";
}
}
DOMElement * createHeader( const string & prefix, const string & propertyPath )
{
using namespace simgear::strutils;
string path = prefix;
DOMNode * root = new DOMNode( "div" );
root->setAttribute( "id", "breadcrumb" );
DOMNode * headline = new DOMNode( "h3" );
root->addChild( headline );
headline->addChild( new DOMTextElement("FlightGear Property Browser") );
DOMNode * breadcrumb = new DOMNode("ul");
root->addChild( breadcrumb );
DOMNode * li = new DOMNode("li");
breadcrumb->addChild( li );
DOMNode * a = new DOMNode("a");
li->addChild( a );
a->setAttribute( "href", path );
a->addChild( new DOMTextElement( "[root]" ) );
string_list items = split( propertyPath, "/" );
for( string_list::iterator it = items.begin(); it != items.end(); ++it ) {
if( (*it).empty() ) continue;
path.append( *it ).append( "/" );
li = new DOMNode("li");
breadcrumb->addChild( li );
a = new DOMNode("a");
li->addChild( a );
a->setAttribute( "href", path );
a->addChild( new DOMTextElement( (*it) ) );
}
return root;
}
static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
{
string value = node->getStringValue();
int len = value.length();
if( len < 15 ) len = 15;
DOMNode * root;
if( len < 60 ) {
root = new DOMNode( "input" );
root->setAttribute( "type", "text" );
root->setAttribute( "name", node->getDisplayName() );
root->setAttribute( "value", value );
root->setAttribute( "size", boost::lexical_cast<std::string>( len ) );
root->setAttribute( "maxlength", "2047" );
} else {
int rows = (len / 60)+1;
int cols = 60;
root = new DOMNode( "textarea" );
root->setAttribute( "name", node->getDisplayName() );
root->setAttribute( "cols", boost::lexical_cast<std::string>( cols ) );
root->setAttribute( "rows", boost::lexical_cast<std::string>( rows ) );
root->setAttribute( "maxlength", "2047" );
root->addChild( new DOMTextElement( value ) );
}
return root;
}
bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response )
{
string propertyPath = request.Uri;
// strip the uri prefix of our handler
propertyPath = propertyPath.substr( getUri().size() );
// strip the querystring
size_t pos = propertyPath.find( '?' );
if( pos != string::npos ) {
propertyPath = propertyPath.substr( 0, pos-1 );
}
// skip trailing '/' - not very efficient but shouldn't happen too often
while( false == propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' )
propertyPath = propertyPath.substr(0,propertyPath.length()-1);
if( request.RequestVariables.get("submit") == "update" ) {
// update leaf
string value = request.RequestVariables.get("value");
SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << propertyPath << " to '" << value << "'" );
fgSetString( propertyPath.c_str(), value );
}
if( request.RequestVariables.get("submit") == "set" ) {
for( HTTPRequest::StringMap::const_iterator it = request.RequestVariables.begin(); it != request.RequestVariables.end(); ++it ) {
if( it->first == "submit" ) continue;
string pp = propertyPath + "/" + it->first;
SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << pp << " to '" << it->second << "'" );
fgSetString( pp, it->second );
}
}
// build the response
DOMNode * html = new DOMNode( "html" );
html->setAttribute( "lang", "en" );
DOMNode * head = new DOMNode( "head" );
html->addChild( head );
DOMNode * e;
e = new DOMNode( "title" );
head->addChild( e );
e->addChild( new DOMTextElement( string("FlightGear Property Browser - ") + propertyPath ) );
e = new DOMNode( "link" );
head->addChild( e );
e->setAttribute( "href", "/css/props.css" );
e->setAttribute( "rel", "stylesheet" );
e->setAttribute( "type", "text/css" );
DOMNode * body = new DOMNode( "body" );
html->addChild( body );
SGPropertyNode_ptr node = fgGetNode( string("/") + propertyPath );
if( false == node.valid() ) {
DOMNode * headline = new DOMNode( "h3" );
body->addChild( headline );
headline->addChild( new DOMTextElement( "Non-existent node requested!" ) );
e = new DOMNode( "b" );
e->addChild( new DOMTextElement( propertyPath ) );
// does not exist
body->addChild( e );
} else if( node->nChildren() > 0 ) {
// Render the list of children
body->addChild( createHeader( getUri(), propertyPath ));
DOMNode * table = new DOMNode("table");
body->addChild( table );
DOMNode * tr = new DOMNode( "tr" );
table->addChild( tr );
DOMNode * th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "&nbsp;" ) );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Property" ) );
th->setAttribute( "id", "property" );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Value" ) );
th->setAttribute( "id", "value" );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Type" ) );
th->setAttribute( "id", "type" );
SortedChilds sortedChilds( node );
for(SortedChilds::iterator it = sortedChilds.begin(); it != sortedChilds.end(); ++it ) {
tr = new DOMNode( "tr" );
table->addChild( tr );
SGPropertyNode_ptr child = *it;
string name = child->getDisplayName(true);
DOMNode * td;
// Expand Link
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "expand" );
if ( child->nChildren() > 0 ) {
DOMNode * a = new DOMNode("a");
td->addChild( a );
a->setAttribute( "href", getUri() + propertyPath + "/" + name );
a->addChild( new DOMTextElement( "(+)" ));
}
// Property Name
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "property" );
DOMNode * a = new DOMNode("a");
td->addChild( a );
a->setAttribute( "href", getUri() + propertyPath + "/" + name );
a->addChild( new DOMTextElement( name ) );
// Value
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "value" );
if ( child->nChildren() == 0 ) {
DOMNode * form = new DOMNode("form");
td->addChild( form );
form->setAttribute( "method", "GET" );
form->setAttribute( "action", getUri() + propertyPath );
e = new DOMNode( "input" );
form->addChild( e );
e->setAttribute( "type", "submit" );
e->setAttribute( "value", "set" );
e->setAttribute( "name", "submit" );
form->addChild( renderPropertyValueElement( node->getNode( name ) ) );
} else {
td->addChild( new DOMTextElement( "&nbsp;" ) );
}
// Property Type
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "type" );
td->addChild(
new DOMTextElement( getPropertyTypeString(node->getNode( name )->getType()) ) );
}
} else {
// Render a single property
body->addChild( createHeader( getUri(), propertyPath ));
e = new DOMNode( "div" );
body->addChild( e );
e->setAttribute( "id", "currentvalue" );
e->addChild( new DOMTextElement( "Current Value: " ) );
e->addChild( new DOMTextElement( node->getStringValue() ) );
DOMNode * form = new DOMNode("form");
body->addChild( form );
form->setAttribute( "method", "GET" );
form->setAttribute( "action", getUri() + propertyPath );
e = new DOMNode( "input" );
form->addChild( e );
e->setAttribute( "type", "submit" );
e->setAttribute( "value", "update" );
e->setAttribute( "name", "submit" );
form->addChild( renderPropertyValueElement( node ) );
}
// Send the response
response.Content = "<!DOCTYPE html>";
response.Content.append( html->render() );
delete html;
response.Header["Content-Type"] = "text/html; charset=ISO-8859-1";
return true;
}
} // namespace http
} // namespace flightgear