1 // PropertyUriHandler.cxx -- a web form interface to the property tree
3 // Written by Torsten Dreyer, started April 2014.
5 // Copyright (C) 2014 Torsten Dreyer
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "PropertyUriHandler.hxx"
23 #include "SimpleDOM.hxx"
24 #include <Main/fg_props.hxx>
25 #include <simgear/debug/logstream.hxx>
26 #include <simgear/misc/strutils.hxx>
27 #include <boost/lexical_cast.hpp>
38 namespace flightgear {
41 // copied from http://stackoverflow.com/a/24315631
42 static void ReplaceAll(std::string & str, const std::string & from, const std::string & to)
45 while((start_pos = str.find(from, start_pos)) != std::string::npos) {
46 str.replace(start_pos, from.length(), to);
47 start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
51 static const std::string specialChars[][2] = {
59 static inline std::string htmlSpecialChars( const std::string & s )
62 for( size_t i = 0; i < sizeof(specialChars)/sizeof(specialChars[0]); ++i )
63 ReplaceAll( reply, specialChars[i][0], specialChars[i][1] );
67 class SortedChilds : public simgear::PropertyList {
69 SortedChilds( SGPropertyNode_ptr node ) {
70 for (int i = 0; i < node->nChildren(); i++)
71 push_back(node->getChild(i));
72 std::sort(begin(), end(), CompareNodes());
77 bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
78 int r = strcmp(a->getName(), b->getName());
79 return r ? r < 0 : a->getIndex() < b->getIndex();
84 static const char * getPropertyTypeString( simgear::props::Type type )
87 case simgear::props::NONE: return "";
88 case simgear::props::ALIAS: return "alias";
89 case simgear::props::BOOL: return "bool";
90 case simgear::props::INT: return "int";
91 case simgear::props::LONG: return "long";
92 case simgear::props::FLOAT: return "float";
93 case simgear::props::DOUBLE: return "double";
94 case simgear::props::STRING: return "string";
95 case simgear::props::UNSPECIFIED: return "unspecified";
96 case simgear::props::EXTENDED: return "extended";
97 case simgear::props::VEC3D: return "vec3d";
98 case simgear::props::VEC4D: return "vec4d";
103 DOMElement * createHeader( const string & prefix, const string & propertyPath )
105 using namespace simgear::strutils;
107 string path = prefix;
109 DOMNode * root = new DOMNode( "div" );
110 root->setAttribute( "id", "breadcrumb" );
112 DOMNode * headline = new DOMNode( "h3" );
113 root->addChild( headline );
114 headline->addChild( new DOMTextElement("FlightGear Property Browser") );
117 DOMNode * div = new DOMNode("div");
119 div->addChild( new DOMNode("span"))->addChild( new DOMTextElement("Path:"));
120 div->addChild( new DOMNode("span"))->addChild( new DOMTextElement( propertyPath ) );
121 div->addChild( new DOMNode("a"))->
122 setAttribute("href",string("/json/")+propertyPath+"?i=y")->
123 addChild( new DOMTextElement( "As JSON" ) );
126 DOMNode * breadcrumb = new DOMNode("ul");
127 root->addChild( breadcrumb );
129 DOMNode * li = new DOMNode("li");
130 breadcrumb->addChild( li );
131 DOMNode * a = new DOMNode("a");
133 a->setAttribute( "href", path );
134 a->addChild( new DOMTextElement( "[root]" ) );
136 string_list items = split( propertyPath, "/" );
137 for( string_list::iterator it = items.begin(); it != items.end(); ++it ) {
138 if( (*it).empty() ) continue;
139 path.append( *it ).append( "/" );
141 li = new DOMNode("li");
142 breadcrumb->addChild( li );
143 a = new DOMNode("a");
145 a->setAttribute( "href", path );
146 a->addChild( new DOMTextElement( (*it) ) );
152 static DOMNode * createLabeledRadioButton( const char * label, const std::string & name, bool checked )
154 DOMNode * root = new DOMNode("span");
155 root->setAttribute( "class", "radiobutton-container" );
157 root->addChild( new DOMNode("span"))->addChild( new DOMTextElement(label) );
158 DOMNode * radio = root->addChild(new DOMNode( "input" ))
159 ->setAttribute( "type", "radio" )
160 ->setAttribute( "name", name )
161 ->setAttribute( "value", label );
164 radio->setAttribute( "checked", "checked" );
169 static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
171 string value = node->getStringValue();
172 int len = value.length();
174 if( len < 15 ) len = 15;
178 if( node->getType() == simgear::props::BOOL ) {
179 root = new DOMNode( "span" );
181 root->addChild( createLabeledRadioButton( "true", node->getDisplayName(), node->getBoolValue() ));
182 root->addChild( createLabeledRadioButton( "false", node->getDisplayName(), !node->getBoolValue() ));
184 } else if( len < 60 ) {
185 root = new DOMNode( "input" );
186 root->setAttribute( "type", "text" );
187 root->setAttribute( "name", node->getDisplayName() );
188 root->setAttribute( "value", htmlSpecialChars(value) );
189 root->setAttribute( "size", boost::lexical_cast<std::string>( len ) );
190 root->setAttribute( "maxlength", "2047" );
192 int rows = (len / 60)+1;
194 root = new DOMNode( "textarea" );
195 root->setAttribute( "name", node->getDisplayName() );
196 root->setAttribute( "cols", boost::lexical_cast<std::string>( cols ) );
197 root->setAttribute( "rows", boost::lexical_cast<std::string>( rows ) );
198 root->setAttribute( "maxlength", "2047" );
199 root->addChild( new DOMTextElement( htmlSpecialChars(value) ) );
205 bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
208 string propertyPath = request.Uri;
210 // strip the uri prefix of our handler
211 propertyPath = propertyPath.substr( getUri().size() );
213 // strip the querystring
214 size_t pos = propertyPath.find( '?' );
215 if( pos != string::npos ) {
216 propertyPath = propertyPath.substr( 0, pos-1 );
219 // skip trailing '/' - not very efficient but shouldn't happen too often
220 while( false == propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' )
221 propertyPath = propertyPath.substr(0,propertyPath.length()-1);
223 if( request.RequestVariables.get("submit") == "update" ) {
225 string value = request.RequestVariables.get("value");
226 SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << propertyPath << " to '" << value << "'" );
228 fgSetString( propertyPath.c_str(), value );
230 catch( string & s ) {
231 SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << propertyPath << " to '" << value << "' failed: " << s );
235 if( request.RequestVariables.get("submit") == "set" ) {
236 for( HTTPRequest::StringMap::const_iterator it = request.RequestVariables.begin(); it != request.RequestVariables.end(); ++it ) {
237 if( it->first == "submit" ) continue;
238 string pp = propertyPath + "/" + it->first;
239 SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << pp << " to '" << it->second << "'" );
241 fgSetString( pp, it->second );
243 catch( string & s ) {
244 SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << pp << " to '" << it->second << "' failed: " << s );
249 // build the response
250 DOMNode * html = new DOMNode( "html" );
251 html->setAttribute( "lang", "en" );
253 DOMNode * head = new DOMNode( "head" );
254 html->addChild( head );
257 e = new DOMNode( "title" );
259 e->addChild( new DOMTextElement( string("FlightGear Property Browser - ") + propertyPath ) );
261 e = new DOMNode( "link" );
263 e->setAttribute( "href", "/css/props.css" );
264 e->setAttribute( "rel", "stylesheet" );
265 e->setAttribute( "type", "text/css" );
267 DOMNode * body = new DOMNode( "body" );
268 html->addChild( body );
270 SGPropertyNode_ptr node;
272 node = fgGetNode( string("/") + propertyPath );
274 catch( string & s ) {
275 SG_LOG(SG_NETWORK,SG_WARN, "httpd: reading '" << propertyPath << "' failed: " << s );
277 if( false == node.valid() ) {
278 DOMNode * headline = new DOMNode( "h3" );
279 body->addChild( headline );
280 headline->addChild( new DOMTextElement( "Non-existent node requested!" ) );
281 e = new DOMNode( "b" );
282 e->addChild( new DOMTextElement( propertyPath ) );
285 response.StatusCode = 404;
287 } else if( node->nChildren() > 0 ) {
288 // Render the list of children
289 body->addChild( createHeader( getUri(), propertyPath ));
291 DOMNode * table = new DOMNode("table");
292 body->addChild( table );
294 DOMNode * tr = new DOMNode( "tr" );
295 table->addChild( tr );
297 DOMNode * th = new DOMNode( "th" );
299 th->addChild( new DOMTextElement( " " ) );
301 th = new DOMNode( "th" );
303 th->addChild( new DOMTextElement( "Property" ) );
304 th->setAttribute( "id", "property" );
306 th = new DOMNode( "th" );
308 th->addChild( new DOMTextElement( "Value" ) );
309 th->setAttribute( "id", "value" );
311 th = new DOMNode( "th" );
313 th->addChild( new DOMTextElement( "Type" ) );
314 th->setAttribute( "id", "type" );
316 SortedChilds sortedChilds( node );
317 for(SortedChilds::iterator it = sortedChilds.begin(); it != sortedChilds.end(); ++it ) {
318 tr = new DOMNode( "tr" );
319 table->addChild( tr );
321 SGPropertyNode_ptr child = *it;
322 string name = child->getDisplayName(true);
327 td = new DOMNode("td");
329 td->setAttribute( "id", "expand" );
330 if ( child->nChildren() > 0 ) {
331 DOMNode * a = new DOMNode("a");
333 a->setAttribute( "href", getUri() + propertyPath + "/" + name );
334 a->addChild( new DOMTextElement( "(+)" ));
338 td = new DOMNode("td");
340 td->setAttribute( "id", "property" );
341 DOMNode * a = new DOMNode("a");
343 a->setAttribute( "href", getUri() + propertyPath + "/" + name );
344 a->addChild( new DOMTextElement( name ) );
347 td = new DOMNode("td");
349 td->setAttribute( "id", "value" );
350 if ( child->nChildren() == 0 ) {
351 DOMNode * form = new DOMNode("form");
352 td->addChild( form );
353 form->setAttribute( "method", "GET" );
354 form->setAttribute( "action", getUri() + propertyPath );
356 e = new DOMNode( "input" );
358 e->setAttribute( "type", "submit" );
359 e->setAttribute( "value", "set" );
360 e->setAttribute( "name", "submit" );
362 form->addChild( renderPropertyValueElement( node->getNode( name ) ) );
365 td->addChild( new DOMTextElement( " " ) );
369 td = new DOMNode("td");
371 td->setAttribute( "id", "type" );
373 new DOMTextElement( getPropertyTypeString(node->getNode( name )->getType()) ) );
377 // Render a single property
378 body->addChild( createHeader( getUri(), propertyPath ));
379 e = new DOMNode( "div" );
382 e->setAttribute( "id", "currentvalue" );
383 e->addChild( new DOMTextElement( "Current Value: " ) );
384 e->addChild( new DOMTextElement( htmlSpecialChars(node->getStringValue()) ) );
386 DOMNode * form = new DOMNode("form");
387 body->addChild( form );
388 form->setAttribute( "method", "GET" );
389 form->setAttribute( "action", getUri() + propertyPath );
391 e = new DOMNode( "input" );
393 e->setAttribute( "type", "submit" );
394 e->setAttribute( "value", "update" );
395 e->setAttribute( "name", "submit" );
397 form->addChild( renderPropertyValueElement( node ) );
401 response.Content = "<!DOCTYPE html>";
402 response.Content.append( html->render() );
404 response.Header["Content-Type"] = "text/html; charset=UTF-8";
411 } // namespace flightgear