Building Dapper Transformers
Thank you for your interest in building a Dapper transformer. A transformer takes the output of a Dapp (XML) and transforms it into some other format. You can see the transformer library for examples.
Getting Started: Code and Class Structure
Every transformer is implemented as a PHP 5 class which extends (inherits from) a parent class called Transformer. The parent class, upon instantiation by Dapper, runs the Dapp with all the settings defined by the user and stores the XML in a class variable called xmlContents. This means your class need not deal with any of the connectivity issues and gets called into action only after the Dapp has been run and the XML is available. Two functions, transform() and getDetails() must be implemented.
A Simple Example
Here is a very simple example of a transformer. This transformer generates HTML which contains information about the outputted XML.
<?php
require_once('Transformer.php');
class Example extends Transformer {
public function transform() {
header('Content-type: text/html; charset=UTF-8');
// start HTML
?>
<html>
<head>
<title>
Information about the Dapp
</title>
</head>
<body>
<?php
// get the size of the XML
echo 'The size of the XML is: ';
echo number_format(strlen($this->xmlContents));
echo ' bytes';
// end HTML
?>
</body>
</html>
<?php
}
public static function getDetails() {
$details = array();
$details['description'] = 'Generates HTML from the Dapp and reports the '.
'size of the XML.';
return $details;
}
}
?>
The transform() Function
This required function takes no arguments and returns nothing. It is responsible for doing the transformation. The Dapp's XML is available to the function in the class variable $this->xmlContents.
Arguments can be passed to the transformer and the method for doing so is described below in the getDetails() function description. As described below, you can access the values that the user supplies for your arguments by using the $this->extraArgs array.
The getDetails() Function
This is a required static function that takes no arguments and returns an associative array describing the transformer's functionality. This function determines what will be shown in Dapper's user interface and allows a user to configure the settings for the transformer.
The array should contain two keys: description and args. The former's value should be a string containing the description you want displayed on the Dapper website. The latter's value should be an associative array where the keys are names of arguments you want the user to supply and which will be passed to the transformer. Let's look at an example for a transformer that creates a map from addresses in the output of a Dapp:
<?php
public static function getDetails() {
$details = array();
$details['args']['mapTitle'] =
array('type' => USER_FREEFORM,
'required' => true,
'display' => 'The title of the map');
$details['args']['addressField'] =
array('type' => DAPP_FIELD,
'required' => false,
'display' => 'The field that contains an address');
$details['args']['description'] =
array('type' => DAPP_FIELD_MULTI,
'required' => false,
'display' => 'The fields that contain descriptions');
$details['args']['link'] =
array('type' => DAPP_FIELD_OR_ATTRIBUTE,
'required' => true,
'display' => 'The fields that contain the link',
'valid' => '^http://');
$details['args']['mapType'] =
array('type' => USER_SELECT,
'required' => false,
'display' => 'Map type',
'select' => array('Yahoo' => 'y',
'Google' => 'g')
);
$details['args']['largeFonts'] =
array('type' => USER_CHECKBOX,
'required' => false,
'value' => 1,
'display' => 'Use large fonts');
$details['description'] = 'An example transformer';
return $details;
}
}
?>
The arguments defined above will be presented to the user in Dapper's user interface (specifically on the "How to Use" page for a Dapp). The above code would generate an interface that looks like the following:
As shown above, there are different types of arguments you can have the user supply. These are the available types:
- USER_FREEFORM: This generates a text box that in which the user can supply any text.
- USER_SELECT: This generates a drop down with the values you specify (see above) so that the user can select one of them.
- USER_CHECKBOX: This generates a checkbox that the user can check. If it is checked, the value you specify (see above) is passed to the transformer. Otherwise nothing is passed for that argument.
- DAPP_FIELD: This generates a drop down with the names of all the fields in the Dapp. This lets the user select the name of one field as the value for an argument.
- DAPP_FIELD_MULTI: This generates a drop down with the names of all the fields in the Dapp. This lets the user select the name of one or more fields as the values for an argument. The values are passed in an array (instead of a string, like all other arguments).
- DAPP_FIELD_OR_ATTRIBUTE: This generates a drop down with the names of all the fields and all the possible attributes (e.g. href and src) in the Dapp.
- DAPP_ATTRIBUTE: This generates a drop down with the names of all the possible attributes (e.g. href and src) in the Dapp for each field. It forces the user to select an attribute, as opposed to the text value of a field.
In your transform() function, you can access the values that the user supplies for your arguments by using the $this->extraArgs array. If, like above, you called your argument mapType, you could access the value of the variable in $this->extraArgs['mapType'].
Putting it Together: A More Sophisticated Example
The following transformer takes address and descriptions from Dapp XML and puts them on a GoogleMap. It makes extensive use of the DOM and XPath, both very valuable tools that are worth using for handling the XML. More about these tools can be found at the PHP website.
<?php
require_once('Transformer.php');
class GoogleMaps extends Transformer {
private $detailsFields = array();
public function transform() {
// make sure there is an address field
if (empty($this->extraArgs['addressField'])) {
throw new MissingArgumentsException(array('addressField'));
}
// create a DOM object and an xpath object
$xmlDoc = new DOMDocument();
$xmlDoc->loadXML($this->xmlContents);
$xmlRoot = $xmlDoc->documentElement;
$xpath = new DOMXPath($xmlDoc);
// get the values of the fields
$groupsOrFields = $xmlRoot->childNodes;
$groupNodes = array();
$fieldNodes = array();
for ($i=0; $i<$groupsOrFields->length; $i++) {
$childNode = $groupsOrFields->item($i);
if ($childNode->nodeType != XML_ELEMENT_NODE)
continue;
if ($childNode->getAttribute('type') == 'group')
$groupNodes[] = $childNode;
elseif ($childNode->getAttribute('type') == 'field')
$fieldNodes[] = $childNode;
}
// set up the key
$key = 'YourGoogleMapsKey';
// start HTML
header('Content-type: text/html; charset=UTF-8');
?>
<html>
<head>
<title>
Google Maps:
<?php echo $xpath->query("//elements/dapper/dappTitle")->item(0)->nodeValue;?>
</title>
<script src="/prototype.js" type="text/javascript"></script>
<script src="http://maps.google.com/maps?file=api&v=2&key=<?php echo $key;?>" type="text/javascript"></script>
</head>
<body onload="addAddresses();">
<?php
if (!empty($this->extraArgs['pageTitle']))
echo '<h1>'.$this->extraArgs['pageTitle'].'</h1>';
?>
<center>
<div id="mapc"></div>
</center>
<script type="text/javascript">
var first = true;
var geocoder = new GClientGeocoder();
map = new GMap2(document.getElementById("mapc"));
map.addControl(new GLargeMapControl());
map.addControl(new GMapTypeControl());
map.setCenter(new GLatLng(36.315125,-32.695312), 2);
function addAddress(address, details) {
geocoder.getLatLng(
address,
function(point) {
if (point) {
addPointToMap(point, details);
}
}
);
}
function addPointToMap(point, details) {
var marker = new GMarker(point);
map.addOverlay(marker);
if (typeof details != 'undefined') {
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(details);
});
}
if (first) {
first = false;
map.setCenter(point, 11);
}
}
function addAddresses() {
<?php
$addressField = $this->extraArgs['addressField'];
$detailsFields = !empty($this->extraArgs['detailsField']) ? $this->extraArgs['detailsField'] : array();
if (!is_array($detailsFields))
$detailsFields = array($detailsFields);
$this->detailsFields = $detailsFields;
foreach ($groupNodes as $groupNode) {
$fields = $groupNode->childNodes;
$groupFields = array();
for ($j=0; $j<$fields->length; $j++) {
$fNode = $fields->item($j);
if ($fNode->nodeType != XML_ELEMENT_NODE)
continue;
if ($fNode->tagName == $addressField || in_array($fNode->tagName,$this->detailsFields)) {
$groupFields[$fNode->tagName] = $fNode;
}
}
$addressNode = null;
$detailsNodes = null;
if (!empty($groupFields[$addressField]))
$addressNode = $groupFields[$addressField];
foreach ($this->detailsFields as $detailsField) {
if (!empty($groupFields[$detailsField])) {
$detailsNodes[] = $groupFields[$detailsField];
}
}
if (isset($addressNode)) {
$this->addMarker($addressNode,$detailsNodes);
}
}
if (sizeof($fieldNodes)) {
foreach ($fieldNodes as $fieldNode) {
$this->addMarker($fieldNode);
}
}
?>
}
</script>
</body>
</html>
<?php
}
private function addMarker($addressNode, $detailsNodes=null) {
if ($addressNode->nodeType != XML_ELEMENT_NODE)
return;
if ($addressNode->getAttribute('type') == 'field') {
$display = $addressNode->nodeValue;
// if this is the address field, then get it's long/lat and make pin
if ($addressNode->tagName == $this->extraArgs['addressField']) {
$address = preg_replace("/'/","\\'",$display);
$address = preg_replace("/\n/s"," ",$address);
$address = preg_replace("//",",",$address);
$this->markerIndex++;
if (sizeof($this->detailsFields) && isset($detailsNodes) && sizeof($detailsNodes)) {
$detailsArray = array();
foreach ($detailsNodes as $detailsNode) {
if (in_array($detailsNode->tagName,$this->detailsFields)) {
$details = preg_replace("/'/","\\'",$detailsNode->nodeValue);
$details = preg_replace("/\n/","\\n",$details);
$detailsArray[] = $details;
}
}
$detailsDisplay = join("<br/>", $detailsArray);
echo "addAddress('".$address."','".$detailsDisplay."');\n";
}
else
echo "addAddress('".$address."');\n";
}
}
}
public static function getDetails() {
$details = array();
$details['args']['addressField'] = array('type' => DAPP_FIELD,
'required' => true,
'display' => 'Address Field');
$details['args']['detailsField'] = array('type' => DAPP_FIELD_MULTI,
'required' => false,
'display' => 'Details Fields');
$details['args']['pageTitle'] = array('type' => USER_FREEFORM,
'required' => false,
'display' => 'Page Title'
);
$details['description'] = 'Transforms the output of a Dapp into a Google Map.';
return $details;
}
}
?>
Testing and Debugging
To test and debug your transformer before submtiting it to Dapper, you should download the following files:
Transformer.php - this is the parent class from
which your class will inherit.
transform.php - this is the script that instantiates
your transformer object and calls your transform() function.
Put the files in a web-accessible directory on your web server. Then call transform.php from your web browser with the following GET arguments: dappName and transformerName. Also, if you work with any arguments (as defined in getDetails(), then supply them to transform.php as extraArg_ARGNAME, where ARGNAME is the name of the argument as you defined it in the getDetails() function.
Submit Your Transformer
Submit your finished transformer here and we'll review it. Provided that it functions properly, we'll post it quickly and offer it to all our users. Please let us know how you would like to be credited, and if you'd like your email address or website to be made available.
