Showing posts with label PHP. Show all posts
Showing posts with label PHP. Show all posts

2017-10-03

FluentDOM 7.0, The Next Step

FluentDOM 7.0 is out, so what has changed? Well, the FluentDOM namespace got a little crowded so I moved all the DOM child classes into FluentDOM\DOM, made the Creator a top level class and collected the utility classes. If you're updating you might need to change some imports. FluentDOM now requires PHP 7 and uses scalar type hints. In other words, lots of cleanup.

FluentDOM\XMLReader\SiblingIterator

Large XML files usually consist of a list element with many record elements as its children. The whole list is to large to load into memory, but the records are small enough.

The SiblingIterator takes a XMLReader, a tag name and a filter callback. It matches the tag name and executes the filter callback. If the tag name matches and the filter callback returns TRUE it will expand the node into DOM. After the first match it will only consider following siblings. This allows you to improve the read performance.

Here is an example that read a XML sitemap including video information.

$reader = new FluentDOM\XMLReader();
$reader->open($sitemapFile);
$reader->registerNamespace(
  's', 'http://www.sitemaps.org/schemas/sitemap/0.9'
);
$reader->registerNamespace(
  'v', 'http://www.google.com/schemas/sitemap-video/1.1'
);

foreach (new FluentDOM\XMLReader\SiblingIterator($reader, 's:url') as $url) {
  /** @var FluentDOM\DOM\Element $url */
  var_dump(
    [
      $url('string(v:video/v:title)'),
      $url('string(s:loc)')
    ]
  );
}

FluentDOM\XMLWriter::collapse()

FluentDOM 7.0 adds a collapse() method to XMLWriter. It is the missing opposite of XMLReader::expand(). Using the two methods allows you to work with large XML files in a really easy way.

The collapse() method takes any DOM node or node list and will write it to the output stream. You can use the extended DOM classes, FluentDOM\Creator or FluentDOM\Query to create the record node.

$writer = new FluentDOM\XMLWriter();
$writer->openURI('php://stdout');
$writer->registerNamespace(
  '', 'http://www.sitemaps.org/schemas/sitemap/0.9'
);
$writer->registerNamespace(
  'video', 'http://www.google.com/schemas/sitemap-video/1.1'
);

$writer->setIndent(2);
$writer->startDocument();
$writer->startElement('urlset');
$writer->writeAttribute(
  'xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1'
);

$_ = FluentDOM::create();
$_->registerNamespace(
  '', 'http://www.sitemaps.org/schemas/sitemap/0.9'
);
$_->registerNamespace(
  'video', 'http://www.google.com/schemas/sitemap-video/1.1'
);

foreach ($videos as $video) {
  $writer->collapse(
    $_(
      'url',
      $_('loc', $video['url']),
      $_(
        'video:video',
        $_('video:title', $video['title'])
      )
    )
  );
}
$writer->endElement();
$writer->endDocument();

XMLWriter::setAttribute() recognizes if you write an namespace definition so it will not add it to descendant nodes.

Put Together

If you combine the expand iterator with collapse you can easily write mappers that can consume large XML files. You can basically use each record as a separate DOM document.

For example you can use it to merge XML documents and change the namespaces:

$writer = new \FluentDOM\XMLWriter();
$writer->openURI('php://stdout');
$writer->registerNamespace('p', 'urn:persons');
$writer->setIndent(2);
$writer->startDocument();
$writer->startElement('p:persons');

// iterate the example sources
foreach ($data as $sourceFile) {
  // load the source into a reader
  $reader = new \FluentDOM\XMLReader();
  $reader->open($sourceFile);

  // iterate the person elements
  $persons = new FluentDOM\XMLReader\SiblingIterator($reader, 'person');
  foreach ($persons as $person) {
    // use the transformer to move the nodes into the namespace
    $writer->collapse(
      new \FluentDOM\Transformer\Namespaces\Replace(
        $person,
        // namespaces to replace
        ['' => 'urn:persons', 'urn:example' => 'urn:persons'],
        // prefix for target namespace
        ['urn:persons' => 'p']
      )
    );
  }
}

$writer->endElement();
$writer->endDocument();

2017-07-02

FluentDOM 6.1 released - Improvements

Release: FluentDOM 6.1.0

MultiByte HTML

Thanks to some issues reported by Kyle Tse the multibyte handling for HTML was improved. It should now work properly. The HTML loader can read the encoding/charset from meta tags or you can specify as an loader option. The default is UTF-8. FluentDOM\Document::saveHTML() has got some additional logic as well.

XMLReader/XMLWriter

If you need to handle huge XML files, the XMLReader and XMLWriter APIs are the way to do it. Well you could try using SAX, but believe me THAT is no fun. XMLReader and XMLWriter are nice APIs by itself, so FluentDOM adds only slight changes for namespace handling.

XMLReader::read()/XMLReader::next()

Of the two traversing methods, only next() allows to specify a local name as a condition. FluentDOM extends the signature of both methods to allow for a tag name and a namespace URI. As a result the source reading an XML with namespaces can be simplified:

$sitemapUri = 'http://www.sitemaps.org/schemas/sitemap/0.9';
$reader = new FluentDOM\XMLReader();
$reader->open($file);
if ($reader->read('url', $sitemapUri)) {
  do {
    //...
  } while ($reader->next('url', $sitemapUri));
}

XMLReader::registerNamespace()

Additionally you can register namespaces on the XMLReader object itself. This allows it resolve namespace prefixes in tag name arguments.

Namespace definitions will be propagated to an FluentDOM\Document instance created by FluentDOM\XMLReader::expand().

$reader = new FluentDOM\XMLReader();
$reader->open($file);
$reader->registerNamespace('s', 'http://www.sitemaps.org/schemas/sitemap/0.9');
if ($reader->read('s:url')) {
  do {
    $url = $reader->expand();
    var_dump(
      $url('string(s:loc)')
    );
  } while ($reader->next('s:url'));
}

XMLWriter::registerNamespace()

The same registration is possible on an FluentDOM\XMLWriter. It keeps track track of the namespaces defined in the current context and avoid adding unnecessary definitions to the output (PHP Bug).

XMLWriter has many methods that have a tag name argument and this change allows all of them to become namespace aware.

$writer = new FluentDOM\XMLWriter();
$writer->openURI('php://stdout');
$writer->registerNamespace('', 'http://www.sitemaps.org/schemas/sitemap/0.9');
$writer->setIndent(2);
$writer->startDocument();
$writer->startElement('urlset');

foreach ($urls as $url) {
  $writer->startElement('url');
  $writer->writeElement('loc', $url['href']);
  // ...
  $writer->endElement();
}

$writer->endElement();
$writer->endDocument();

2016-12-24

FluentDOM 6.0 - What's New

FluentDOM 6.0 is released.

So a major version jump means that here are backwards compatibility breaks and major new features. File loading has changed to improve security. FluentDOM\Query has got a major overhaul to improve the integration of alternative formats. This affected the interfaces and usage. I tried to keep the breaks to a minimum and easily fixable.

Loading Files

To load a file you will now have to explicitly allow it using the options argument. Here are two options. FluentDOM\Loader\Options::ALLOW_FILE basically restores the previous behaviour. The loader still checks if it is a file or string and will load both. FluentDOM\Loader\Options::IS_FILE means that only a file will be loaded. This has to be implemented into to respective loader. So if you notice that some loader behaves differently, please drop me a note.

All loading functions allow you to provide the option.

$fd = FluentDOM($file, 'xml', [ FluentDOM\Loader\Options::ALLOW_FILE => TRUE ]);
$document = FluentDOM::load(
  $file, 'xml', [ FluentDOM\Loader\Options::IS_FILE => TRUE ]
);

Fragment Loading / Query Content Types

Several loaders now support fragment loading. It is used by methods like FluentDOM\\Query::append(). It allows the Query API to keep the content type that you loaded. So if you load HTML, the fragment are expected to be html, if you load XML the fragments are expected to be XML, if you load JSON ... well you get the picture. :-)

$fd = FluentDOM('<form></form>', 'text/html')->find('//form');
$fd->append('<input type="text" name="example">');
echo $fd;

You can change the behaviour by setting the content type. It works with additional loaders, so if you install fluentdom/html5 you get transparent support for HTML5.

$fd = FluentDOM('<form></form>', 'text/html5')->find('//html:form');
$fd->append('<input type="text" name="example">');
echo $fd; 

The changes mean that all existing loaders need to be updated for FluentDOM 6.0.

Serializers

Serializers need to register itself on the FluentDOM class. It allows the FluentDOM\Query objects to output the same content type it loaded. Additionally you can use FluentDOM::getSerializerFactories()->createSerializer(); to get a serializer for a node by content type. I am thinking about adding something like a FluentDOM::save() function as a shortcut for that, but I am not sure about the name and implementation yet. If you have a suggestion please add it to the Issue.

Replace Whole Text

Character nodes (Text nodes and CDATA sections) have a property $wholeText. It returns the text content and the sibling character nodes. It even resolves entity references for that. The property is read only, but DOM Level 3 specifies a method replaceWholeText() as a write method for it. FluentDOM 6.0 implements that method in its extended DOM classes now.

$document = new FluentDOM\Document();
$document->loadXML(
  '<!DOCTYPE p ['."\n".
  '  <!ENTITY t "world">'."\n".
  ']>'."\n".
  '<p>Hello &t;<br/>, Hello &t;</p>'
);
/** @var \FluentDOM\Text $text */
$text = $document->documentElement->firstChild;
$text->replaceWholeText('Hi universe');
echo $document->saveXML();

Examples

The examples directory did grow a little confusing over the years. I restructured and refactored it. Some examples got removed, because the features are shown by newer examples.

What's Next?

I still have to updated some of the plugin repositories (loaders, serializers) and add some more documentation to the wiki. After that I plan to take a look into DOM Level 4. If you have suggestions please add a ticket to the issue tracker

2015-02-02

FluentDOM 5.2 - New Features

FluentDOM 5.2 is released and here some new features:

FluentDOM::load()

The new static FluentDOM::load() function allows to use the loaders to get a FluentDOM\Document instance.
$json = FluentDOM::load($json, 'text/json');
echo $json(
  'string(//phoneNumbers/*[type="home"]/number)'
);

DOM Living Standard


A large part of the DOM Living Standard is implemented. It adds properties like $element->firstElementChild and methods like $element->remove().

Query selectors are not implemented yet. Still thinking about that. If you have an opinion please add a comment to the issue.

Two New Loaders

Loader Plugins

It was already possible to use the loaders directly or to set the loaders for a FluentDOM\Query object. It is now possible to register them on the FluentDOM class. This makes them available to the FluentDOM() and FluentDOM::load() functions.

Additional packages like FluentDOM/HTML5 can register their loaders with specific content types. Just require the package with Composer and you're ready to go:
$dom = FluentDOM::load($html, 'text/html5');

FluentDOM\Query::find()

The behavior of find() has been changed to allow two modes. FIND_MODE_MATCH executes the selector directly. It should be faster and work better with XPath. FIND_MODE_FILTER filters the descendant nodes of the current context - like the jQuery documentation describes it.

2014-09-20

FluentDOM 5.1 - New Features

FluentDOM 5.1 is now available. Here are some of the highlights:

Functors 

The classes can now be called as functions to navigate in a DOM with XPath expressions. The following example fetches all link hrefs attributes from an HTML page:
$dom = new \FluentDOM\Document();
$dom->loadHTMLFile('http://fluentdom.org/');

$links = [];
foreach ($dom('//a[@href]/@href') as $href) {
  $links[] = (string)$href;
}


This works for most of the nodes in a DOM. 

Creator

The new Creator class provides short syntax to create DOM nodes. More detailed information can be found in the wiki.
$_ = FluentDOM::create();
echo $_(
  'ul',
  ['class' => 'navigation'],
  $_('li', 'FluentDOM')
);

XML To JSON

Several serializers/loaders for JSON where added. JSONML, Rayfish, BadgerFish and RabbitFish are supported.
echo "XML -> JsonML\n\n";
$json = json_encode(
  new FluentDOM\Serializer\Json\JsonML($dom), 
  JSON_PRETTY_PRINT);
echo $json;

echo "\n\nJsonML -> XML\n\n";
echo FluentDOM(
  $json, 'application/jsonml+json')->formatOutput();

The Release

2014-08-17

FluentDOM + HTML5

HTML 5 is not directly supported by PHPs DOM extension. That means FluentDOM can not understand it, too. But here is a solution. HTML5-PHP is library that can parse HTML5 into a DOM document.

Both libraries use Composer:
"require": {
  "fluentdom/fluentdom": "5.*",
  "masterminds/html5": "2.*"
}

Read HTML5 into FluentDOM:
$html5 = new Masterminds\HTML5();
$fd = FluentDOM($html5->loadHTML($html));

Or write it:
echo $html5->saveHTML($fd->document);

HTML5-PHP puts the elements into the XHTML namespace. To use XPath expressions, you will need to register a prefix for it:
$html5 = new Masterminds\HTML5();
$fd = FluentDOM($html5->loadHTML($html));
$fd->registerNamespace(
  'xhtml', 'http://www.w3.org/1999/xhtml'
);
echo $fd->find('//xhtml:p')->text();

2014-08-09

Xpath 1.0 - Quoting Strings

Strings in Xpath 1.0 can be enclosed in single or double quotes. The following expressions are equivalent.

//div[@id = 'foo']
//div[@id = "foo"]


This is nice because you can use the variant that requires less or none escaping. I prefer single quotes for PHP because they need less escaping (only single quote and backslash). I usually end up with something like this:

$xpath->evaluate('//div[@id = "foo"]');

However a problem comes up if the value is dynamic.

$xpath->evaluate('//div[@id = "'.$_GET['foo'].'"]');

If $_GET['foo'] contains a double quote, it will break the expression. It is compare able to an SQL-Injection and should be avoided, don't you think?

The Xpath 1.0 specification for a literal is:

Literal   ::=   '"' [^"]* '"'


| "'" [^']* "'"

It disallows the use of the enclosing quote in the literal itself, here is no way to escape it.

Hint: This is different in Xpath 2.0. You can duplicate the quotes to escape them.

Deciding Which Quote To Use

 The first and obvious step is to check the value for quotes and use the one that it does not contain:

function quote($value) {
  $char = strpos($value, '"') === FALSE ? '"' : "'";
  return $char.$value.$char;
}


But a value could contain both kind of quotes. This would still break the expression.

Divide And Conquer

If is not possible to quote the whole value because it contains both kind of quotes you need to divide it into parts that can be quoted. You can then use the Xpath function concat() to rebuild the orignal value again:

//div[. = concat("Singe Quote: '", 'Double Quote: "')]

Matching text structures is the domain of regular expression. So lets use them:

preg_match_all('("[^\']*|[^"]+)', 'Double Quote ", Single Quote \'', $matches);
var_dump($matches);


Output:

array(1) {
  [0]=>
  array(3) {
    [0]=>
    string(13) "Double Quote "
    [1]=>
    string(16) "", Single Quote "
    [2]=>
    string(1) "'"
  }
}

The pattern matches any string that start with a double quote and contains no single quote or any string that does not contain any double quote.

All that is left is quoting the parts and join them back together:

foreach ($matches[0] as $part) {
  $quoteChar = (substr($part, 0, 1) == '"') ? "'" : '"';
  $result .= ", ".$quoteChar.$part.$quoteChar;
}
return 'concat('.substr($result, 2).')';

Put Together

It does not make sense to create the function call for a single argument. So the check is still needed:

  1. If the value contains no single quote, use single quotes
  2. If the value contains no double quote, use the double quotes
  3. Otherwise divide the string and use concat()

A complete implementation can be found in FluentDOM\Xpath::quote().

2014-08-06

FluentDOM 5 + XML Namespaces

FluentDOM 5 allows to register namespaces on the DOM document class. These are not the namespaces of a loaded document, but a definition of namespaces for your programming logic.

Namespaces In An XML File

People mistake the namespace prefixes for the namespaces often. The namespace definitions are the xmlns:* Attributes. Let's take a really simple example:

<atom:feed xmlns:atom="http://www.w3.org/2005/Atom"/>

This is a feed element in the Atom namespace. The actual namespace is "http://www.w3.org/2005/Atom". Because this would be difficult to read and write the prefix/alias 'atom' is defined for the namespace. 

You can read the element name as '{http://www.w3.org/2005/Atom}:feed'

It is possible to define a default namespace for elements in an XML. 

<feed xmlns="http://www.w3.org/2005/Atom"/> 

This should still be interpreted as '{http://www.w3.org/2005/Atom}:feed'. The namespace prefix in the source document is not relevant. You need a way to match the namespace itself and not the alias.

Hint 1: Namespace prefixes can be redefined on any element node in the document.

Hint 2: Attributes always need a prefix to use a namespace. Any attribute without a prefix is in the "none/empty" namespace.

Namespaces in XPath

An XPath expresssion that could match the Atom feed element would need to use a prefix and needs a way to resolve that prefix into the actual namespace. 

A PHP Example:

$xpath = new DOMXpath($document);
$xpath->registerNamespace('a', 'http://www.w3.org/2005/Atom');
$nodes = $xpath->evaluate('/a:feed');


In PHP the DOMXpath class provides the evaluate() method and the namespace resolver. You register your own namespace prefixes. '/a:feed' gets resolved to '/{http://www.w3.org/2005/Atom}:feed'. The prefixes in the document and the expression can be different, but the resolved namespace has to be the same.

A JavaScript example:

var xmlns = {
  namespaces : {
    'a' : 'http://www.w3.org/2005/Atom'
  },
  lookupNamespaceURI : function(prefix) {
    return this.namespaces[prefix] || null;
  }
};
var nodes = xmlDocument.evaluate('/a:feed', xmlDocument, xmlns, XPathResult.ANY_TYPE, null); 


In JavaScript the document object provides the evaluate() method and the namespace resolver is an argument for it. This will work in most of the current browsers, but not IE. 

Namespaces in FluentDOM

In FluentDOM 5 the document and the element classes both have an evaluate() method. To define your namespace mapping you register the namespace on the document object:

$document->registerNamespace('a', 'http://www.w3.org/2005/Atom')
$nodes = $document->evaluate('/a:feed');


Now because the document has a way to resolve namespaces, it can do this for other methods, too. Basically for all methods a have a namespace aware variant, like 'createElement()' and 'createElementNS()' or 'setAttribute()' and 'setAttributeNS()'. If FluentDOM can resolve a tagname it will call the namespace aware variant of the method.

// Standard DOM
$feed = $document->createElementNS(
  'http://www.w3.org/2005/Atom', 'atom:feed'
);
// FluentDOM
$document->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
$feed = $document->createElement('atom:feed');

Default Namespace 

FluentDOM adds the concept of a default element namespace, too.

// Standard DOM
$feed = $document->createElementNS(
  'http://www.w3.org/2005/Atom', 'feed'
);
// FluentDOM
$document->registerNamespace('#default', 'http://www.w3.org/2005/Atom');
$feed = $document->createElement('feed');

appendElement()


FluentDOM\Document and FluentDOM\Element implement an appendElement() method. It is a shortcut for serveral methods. It creates the element, adds attributes and a text node and appends it to the parent node. Together with the namespace handling, creating an XML document becomes a lot simpler. You can find an example in the wiki.


2014-08-01

FluentDOM 5

FluentDOM 5.0.0 is now released.

Up to version 4.1, FluentDOM was an implementation of the jQuery Traversing and Manipulation APIs in PHP. Version 5 is a complete rewrite and adds a secondary focus. FluentDOM now provides extended variants of PHPs DOM classes, too. This allows workarounds for bugs, syntax sugar and shortcuts.

Bugs

#39521

The second argument to DOMDocument::createElement() it breaks if it contains an entity. FluentDOM avoids that by creating and appending a text node. Additionally it adds a third argument to provide attributes.

#55700

By default PHP registers the namespace definitions of the current context. This uses the namespace prefixes as identifiers, but they are not. They are allowed to change (even on different elements in the same document). You should always register your own prefixes so you do not depend on the prefixes in the document - they are just not relevant. So the automatic registration costs performance without a real gain in the best case. In the worst case it overrides a your namespace registration and you can not fetch the data you want.

FluentDOM adds a property to change this behavior. The automatic namespace registration is disabled by default and can be activated using the property or the third argument for evaluate()/query().

Syntax Sugar

Cast To String


Most of the nodes can be cast to string. for example the following will return the whole text content of an document.

echo $dom->documentElement;

Iterator For Child Nodes


FluentDOM\Element is iterate-able. Using foreach() on it will iterate over the child nodes. The Iterator is a RecursiveIterator, too.

ArrayAccess

FluentDOM\Element allows array syntax. A numeric key like $element[1] will access the child node. An qualified name string like $element['href'] will access the attribute.

Namespaces

FluentDOM\Document allows to register namespaces on the document. If methods like setAttribute() or createElement() recognize a colon in the tag name, they will resolve the namespace prefix an call their namespace aware variant.

Shortcuts

FluentDOM\Document::createElement() allows to provided content and attributes. FluentDOM\Element::appendElement() allows to create and append an element with a single call.

Other methods of the FluentDOM\Element class are variants of the document variants using the element node as default context. 

Backwards Compatbility

FluentDOM 5 is mostly backwards compatible. The FluentDOM() function still exists and the returned FluentDOM\Query instance has the same jQuery like API. Only the loaders where changed.

CSS Selectors

The FluentDOM\Query class allows to set an callback function that is used to convert the provided selectors to xpath expressions. FluentDOM::QueryCss() returns a FluentDOM\Query instance that supports CSS selectors. You will need to have Carica PhpCss or Symfony CSS-Selector installed in you project.



2013-11-17

Building A Sensor Phalanx With PHP

Sensors are fun. They report from the physical world into the digital. But getting the signal into php is only the first part, you will have to get them out again. This post shows how to get data from analog sensors pushed to the browser. It uses Carica Chip, if you haven't read my previous blog post you should do it first.

Carica Io implements its own event loop, but if it finds ReactPHP installed, it will base it on this implementation, making it compatible and allowing to use the libraries. One of the libraries for ReactPHP is Ratchet, a websocket server implementation.

In this example two servers are used. An http server for file delivery (html and javascript for the UI) and a websocket server that pushes the sensor values to the browser. Smootie Charts displays the data in the browser as a running line chart, not unlike an oscilloscope.

Source

The latest source for this example can be found on Bitbucket:
https://bitbucket.org/ThomasWeinert/carica-sensor-phalanx

Project Setup

Clone Carica Chip Skeleton to create a new project and add Ratchet to the dependencies.

composer create-project carica/chip-skeleton SensorPhalanx \
  --stability=dev 

cd SensorPhalanx

composer require cboden/ratchet

copy dist.configuration.php configuration.php

Edit configure.php to match your hardware setup.

The HTTP Server

In the LED example, the http server had only one route to deliver the html file. Now we need to deliver the javascript, too. An additional route allows to deliver static files from a subdirectory.

$route = new Http\Route();
$route->match('/', new Http\Route\File('index.html'));
$route->startsWith('/files', new Http\Route\Directory(__DIR__));
$httpServer = new Http\Server($route);
$httpServer->listen(8080);

The Sensor Phalanx

The Sensor Phalanx object encapsulates the Ratchet websocket server and provides the callbacks for it. It needs an array of sensors. Port 8080 is already used for the http server, so it should listen on port 8081. An interval triggers the update of the clients.

// create the phalanx with some sensors
include(__DIR__.'/class/SensorPhalanx.php');
$phalanx = new Carica\SensorPhalanx(
  [
    'lightsensor' => new Chip\Sensor\Analog($board->pins[15]),
    'potentiometer' => new Chip\Sensor\Analog($board->pins[16])
  ]
);
// tell the websocket server to listen
$phalanx->listen(8081);

// update clients connected to the phalanx with the current sensor data
$loop->setInterval(
  function () use ($phalanx) {
    $phalanx->update();
  },
  200
);

The SensorPhalanx Class

The SensorPhalanx class needs to implement two interfaces. Carica\Io\Event\HasLoop defines access to the Carica Io event loop. It can be implemented using the trait Carica\Io\Event\Loop\Aggregation.

Ratchet\MessageComponentInterface defines the callback methods for the websocket server.
  • onOpen() - add a client
  • onClose() - remove a client
  • onMessage() - handle data recieved from the browser
  • onError() - log/handle errors
public function onOpen(Ratchet\ConnectionInterface $connection) {
  $this->_clients[spl_object_hash($connection)] = $connection;
}

public function onClose(Ratchet\ConnectionInterface $connection) {
  unset($this->_clients[spl_object_hash($connection)]);
}

public function onMessage(Ratchet\ConnectionInterface $connection, $message) {
}

public function onerror(Ratchet\ConnectionInterface $connection, \Exception $e) {
  echo "Error: ", $e->getMessage(), "\n";
  $connection->close();
}

Create And Listen

The constructor of the class just validates the sensor objects, and stores them. Listen creates the needed subobjects. Unlike the examples on the Ratchet website, the factory methods can not be used. They would create a new event loop. The script already has one, used for the Arduino board and the http server. The $this->loop() method returns the Carica Io event loop and in a second step allows access to the actual ReactPHP event loop.

public function __construct(array $sensors) {
  foreach ($sensors as $index => $sensor) {
    if ($sensor instanceOf Chip\Sensor\Analog) {
      $this->_sensors[$index] = $sensor;
    }
  }
}

public function listen($port = 8081) {
  $socket = new \React\Socket\Server($this->loop()->loop());
  $socket->listen($port);
  $this->_server = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
      new Ratchet\WebSocket\WsServer(
        $this
      )
    ),
    $socket,
    $this->loop()->loop()
  );
}

Update

Update collects the data from all sensors into an array, encodes it to json and sends it to all connected clients.

public function update($log = TRUE) {
  $values = ['type' => 'sensors'];
  foreach ($this->_sensors as $index => $sensor) {
    $values['sensors'][$index] = $sensor->get();
  }
  $json = json_encode($values);
  if ($log) {
    echo $json, "\n";
  }
  foreach ($this->_clients as $client) {
    $client->send($json);
  }
}

User Interface

The html file uses Smoothie Charts. It defines some CSS and a canvas for the chart. The chart is created and connected to the canvas. A list of colors provides a different color for each line.

The websocket server connects and gets a callback. If a message is received it loops over the sensor values. If a line for the index already exists the value is appended. For an unknown index a new line is created and the assigned color is removed from the internal list.

var smoothie = new SmoothieChart(
  {
    maxValue : 1,
    minValue: 0
  }
);
smoothie.streamTo(document.getElementById('monitorCanvas'), 250);

var colors = ['#00FF00', '#FF0000', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
var lines = [];

websocket = new WebSocket('ws://localhost:8081/');
websocket.onmessage = function(event) {
  var data = JSON.parse(event.data);
  if (data && data.type == 'sensors') {
    for (index in data.sensors) {
      if (typeof lines[index] == 'undefined') {
        var color = colors.shift();
        if (!color) {
          color = '#FFFFFF';
        }
        lines[index] = new TimeSeries();
        smoothie.addTimeSeries(
          lines[index],
          {
            lineWidth: 2,
            strokeStyle: color
          }
        );
      }
      lines[index].append(new Date().getTime(), data.sensors[index]);
    }
  }
};

In Action

2013-11-04

Carica Chip 101 - Controlling An LED With PHP

Some time ago, in this blog post, I explained the basic stuff about Arduino, Firmata and PHP. Now it is time for the next step. Carica Io and Carica Firmata have grown and got a third layer called Carica Chip.
  1. Carica Io - Non-Blocking I/O for PHP
  2. Carica Firmata - An implementation of the Firmata protocol
  3. Carica Chip - PHP classes representing hardware devices
Carica Chip provides an easy way to control a device. So let's start with an "Interactive LED" example.

First Step: Project Initialization

Carica Chip uses Composer. Make sure that it is installed and open a console. Go into your projects directory and execute the following line (it will create a new subdirectory "led"):

composer create-project carica/chip-skeleton led \
  --stability=dev

Second Step: Create An HTML Interface

For the interface a simple html file is used. Just two links with an iframe set as the target. This is a basic version, some Javascript and CSS should be used to make it nicer and more usable.

<html>
  <head>
    <title>Led Switch</title>
  </head>
  <body>
    <a href="./switch/on" target="iframe">
      On
    </a>
    <a href="./switch/off" target="iframe">
      Off
    </a>
    <iframe name="iframe" src="about:blank"></iframe>
  </body>
</html>

Store the html source as "index.html" in the project root.

Third Step: Create The PHP Server

This will be a step by step description, the complete file is below and on Gist.

The skeleton includes a bootstrap file that returns a board depending on your configuration. Copy "dist.configuration.php" to "configuration.php" and change it if needed.

Now open "server.php". At the top "bootstrap.php" is included to fetch a Firmata board. If the board is activated, it executes a callback. Carica Chip provides a "Led" class. For the example an instance is created. The constructor needs the pin the led is connected to. Most Arduinos have a led that is connected to pin #13 on board.
$led = new Chip\Led($board->pins[13]);

We need to deliver the html interface to the browser. Carica Io includes a http server and file routings. Add "use Carica\Io\Network\Http as Http;" to the namespace definition, create a new route and add a file delivery for "index.html".
$route = new Http\Route();
// Define html file delivery for /
$route->match(
  '/', 
  new Http\Route\File(__DIR__.'/index.html')
);

A second route defines the switch actions. This is specific and not much source, so an anonymous function is used. For more extensive logic I suggest functors, objects that implement the magic method "__invoke()", like the file handler. The state parameter is fetched from the path. Depending on the parameter the led is switched on or off. A response is created and a string with the new state is used as content. If you don't return a response, a 404 would be send to the browser.
$route->match(
  '/switch/{state}',
  function (Http\Request $request, array $parameters)
    use ($led) {
    $ledOn = ($parameters['state'] == 'on');
    if ($ledOn) {
      $led->on();
    } else {
      $led->off();
    }
    $response = $request->createResponse(
      new Http\Response\Content\String(
        $ledOn ? 'ON' : 'OFF', 
        'text/plain; charset=utf-8'
      )
    );
    return $response;
  }
);

Last the http server is created and started.
$server = new Carica\Io\Network\Http\Server($route);
$server->listen(8080);

Finished!

That's all. You can now start the script on the command line and open the URL in a browser. Clicking the links will (de)activate the led.

I posted the full source of "server.php" including comments on Gist.

2013-06-20

Basics: Using Arduino From PHP

Own Protocol Inside A PHP Page.

This is the version mostly described on the net. For example on Instructables. Basically you write an Arduino Scetch that listens and send plain text data on the serial port. A PHP Script triggered by a webserver request opens the port communicates with the Arduino board an outputs some HTML.

The major downside of this approach is that many Arduino boards reset if the serial port is opened. So the startup and configuration has to be done any time the PHP scripts reads some data or triggers an action. Because the PHP script stops after the page request any data has to be stored in a database, cache or file.

Firmata

Firmata is a generic sketch for Arduino boards that implements an MIDI based protocol. Here are client libraries for several languages. For example Johnny Five for Javascript (Node.js) implements abstraction not only for the communication but for hardware elements. For example you get an led object with blink() and fade() methods.

Firmata Inside A PHP Page. 

Well, It is possible but you don't want to do that. Firmata has a little more complex startup process that takes about two seconds. To much to use it in a web request.

Firmata Over TCP

The official version of Firmata uses the serial port. But here are forks that can use TCP. You need an compatible network shield for your Arduino board and the version may be a little behind the offical one.

Non-Blocking I/O

Node.js uses an concept called Non-blocking I/O or Asynchronous I/O. Basically it means that you add actions to an event loop, trigger events from you objects that execute attached callbacks. Doesn't sound familiar? If you ever programmed some Javascript for your web pages, you used this concept. Each browser window has an event loop. Functions like "window.setTimeout()" add event to the loop. The browser fires event on the page elements that execute callbacks, if you attached them, too.

So using this concept you describe what should happen if an events is triggered and a certain conditions are meet. In classic PHP you work with the current state. In Non-Blocking I/O the result has yet to be calculate at a future time.

Procedual Programming:
If the result of action x is value y now do that.
Non-Blocking I/O
If the result of action x will be y you will have to do that.

Thinking like this is much better suited to hardware control, at the moment you are programming you do not now if somebody will ever press the button on your Arduino device or which values the sensor your connected will send.

Non-Blocking I/O in PHP

PHP has no default event loop implemented but here are several possibilities for it. First it whould be possible just using an while loop and usleep(). This would not be very efficient. Better is an combination of this with stream_select(). The "stream_select()" function  is like a usleep() that monitors streams and return imminently if a stream is changed. It even says which stream is changed how, so it is possible to trigger the exact events for this stream.

Event more efficient is using libevent. PHP has an extension for this event loop implementation. It is used inside the Chrome browser browser and Node.js, too.

Libraries For PHP

Here are libraries in PHP that implement event loops.

ReactPHP
ReactPHP is the most mature project implementing Non-Blocking I/O for PHP. It a general purpose library and has additional components. You can read more about it on reactphp.org.

Carica Io
Caica Io is an implementation I created as a base for Carica Firmata, a client library for the Firmata protocol. It can used separately if you're going to implement your own protocol, too. Here is an basic example with timers:


The event loop uses the API not unlike the Browser. The main difference to Javascript in the Browser or Node.js is that you have to run the event loop explicitly.

Carica Firmata

The Firmata test application
in Carica Firmata
Based on Carica Io, it allows you to create programs that control the Arduino board. They run without an webserver (like Apache HTTP, Nginx, ...). They can even contain an (simple) webserver to control the script from a browser. The php program is started from the command line and runs continuously until you stop it (or it crashes :-) ). The serial connection to the board is opened only once, no reset happens and the start up time is not an issue.

Blink

The "Hello World" for an Arduino is an blinking led. Wait for the board to activate, and add an interval (repeated timer) that switches it on and off.


Dimmer

Carica Firmata can read data, too. Simple attach an callback to the board. On the analog pin this could be a potentiometer dimming an led. The examples uses an OOP api for the pins. The main difference to calling the methods on the board object directly is that values and the mode are only send if the are changed.

On Windows

On Windows the  serial port is seriously broken at the moment. It can not work non-blocking. Any reading action will block your script until it recieves data. One solution is Serpoxy, a little program that maps the serial port to tcp.

Links

2012-07-31

What Iterators Can Do For You

Basically Iterators provide a list interface for an object. Like all interfaces they are a contract how something can be used. If you use an interface it is not relevant how it is implemented - the implementation logic is encapsulated.

It is of course relevant on the integration level. A bad implementation can impact the performance of you application. Even an good implementation may need special resources (like a database). But all this does not impact how you use it. Your code using the object with the Iterator interface stays the same.

Let's start with a simple example that outputs a list.

$elements = array(
  'line one', 'line two'
);

foreach ($lines as $key => $value) {
  echo $key.': '.$value."\n";
}


If we transfer this into an object it would like that:

class MyProjectLineOutput {
  private $_lines = NULL;

  public function __construct($lines) {
    $this->_lines = $lines;
  }

  public function __invoke() {
    foreach ($this->_lines as $key => $value) {
      echo $key.': '.$value."\n";
    }
  }
}

$output = new MyProjectLineOutput(
  array(
    'line one', 'line two'
  )
);
$output();


On the first glance that looks like a lot more work but it isn't. The code includes two tasks. Get the lines and output them. The class encapsulates the output task and makes it reusable. In this simple example that may look superfluous but think a little larger. Like output an select-field or csv.

Encapsulate file()


Still we haven't used an Iterator, but just encapsulated the output. PHP provides several default iterators and one of them is the ArrayIterator.

$output = new MyProjectLineOutput(
  new ArrayIterator(
    array(
      'line one', 'line two'
    )
  )
);
$output();


The ArrayIterator just takes an array and makes it an Iterator. It is mostly used to implement another interface - IteratorAggregate. Both the "Iterator" and the "IteratorAggregate" interfaces inherit from a common ancestor named "Traversable". You can not implement "Traversable" directly but use it to validate if an object is traversable or in other words can be used with foreach.

Now let's load the lines from a file.

class MyProjectFile implements IteratorAggregate {
  private $_file;

  public function __construct($file) {
    $this->_file = $file;
  }

  public function getIterator() {
    return new ArrayIterator(file($this->_file));
  }
}

$output = new MyProjectLineOutput(
  new MyProjectFile('sample.txt')
);
$output();


The main difference between using file directly to this is that the file() is accessed later in the process. The foreach() inside the MyProjectLineOutput::output() method calls MyProjectFile::getIterator(). Until then we can pass the instance of MyProjectFile around without loading the file into memory. Unlike a direct call to file() we don't pass the concrete data around but an information how it can be obtained.

Iterating A Text File


Implementing Iterator we can make sure that only a part of the file needs to be loaded.

class MyProjectFileUnbuffered implements Iterator {
  private $_file;
  private $_handle = NULL;
  private $_key = -1;
  private $_current = NULL;

  public function __construct($file) {
    $this->_file = $file;
  }

  public function __destruct() {
    if (is_resource($this->_handle)) {
      fclose($this->_handle);
    }
  }

  public function rewind() {
    if (!is_resource($this->_handle)) {
      $this->_handle = fopen($this->_file, 'r');
    } else {
      fseek($this->_handle, 0);
    }
    $this->_key = -1;
    $this->next();
  }

  public function next() {
    if ($this->_key > 0 or $this->_current !== FALSE) {
      $this->_current = fgets($this->_handle);
      $this->_key++;
    }
  }

  public function key() {
    return $this->_key;
  }

  public function current() {
    return $this->_current;
  }

  public function valid() {
    return $this->_current !== FALSE;
  }
}


This is more source then implementing it directly. Mostly because of the class and function declarations. But you have to write this only once. And it can be improved or replaced without affecting the usage.

Map Elements

Using iterators you can encapsulate mapping actions, like using array_map() on an array but only for elements that are read. Let's say you need to chop all trailing whitespaces from the lines.

Step One: Map Iterator:


class MyProjectMapIterator implements OuterIterator {

  private $_innerIterator = NULL;
  private $_callback = NULL;

  public function __construct(Iterator $innerIterator, $callback) {
    $this->_innerIterator = $innerIterator;
    $this->_callback = $callback;
  }

  public function map($current, $key) {
    return call_user_func($this->_callback, $current, $key);
  }

  public function getInnerIterator() {
    return $this->_innerIterator;
  }

  public function rewind() {
    $this->getInnerIterator()->rewind();
  }

  public function next() {
    $this->getInnerIterator()->next();
  }

  public function key() {
    return $this->getInnerIterator()->key();
  }

  public function current() {
    return $this->map(
      $this->getInnerIterator()->current(),
      $this->getInnerIterator()->key()
    );
  }

  public function valid() {
    return $this->getInnerIterator()->valid();
  }
}


OuterIterator is an interface for iterators that wraps other iterators, it is defined in the SPL. It extends the Iterator interface. The MapIterator is an iterator of that kind, so it is cleaner to implement it that way.

Step Two: Using The Map Iterator:

Using the map iterator is not unlike using array_map.

$output = new MyProjectLineOutput(
  new MyProjectMapIterator(
    new MyProjectFile('sample.txt'),
    function($current, $key) {
      return chop($current);
    }
  )
);
$output();


The file() function has an option to do this. But it is limited to exactly this task. With MapIterator you get a lot more flexibility. You could even extend the MapIterator to get reusable mappings.

Step Three: Extending the Map Iterator

class MyProjectMapIteratorUpper extends MyProjectMapIterator {

  public function __construct(Iterator $innerIterator) {
    parent::__construct(
      $innerIterator,
      function($current, $key) {
        return strToUpper($current);
      }
    );
  }
}

Filter Elements:

Here is another option for file(), that skips empty lines. This would be filter task and here is already an superclass for that in SPL.

class MyProjectFilterIteratorSkipEmptyLines extends FilterIterator {

  public function accept() {
    return trim($this->getInnerIterator()->current()) !== '';
  }
}

Conclusion

Iterators are not about writing less code at one time. But they help you to write source that is encapsulated, easy to test and reusable. Because of this over time you will end up with less code.

2012-07-30

Using The PHP 5.4 Webserver On Windows

Starting a server from the command line
PHP 5.4 has an built-in webserver. For local development it is not necessary to install Apache Httpd or another webserver anymore. You can just start an server from the command line.

Change the directory to your project directory. The argument -S starts the webserver. You need to specify a host and a port. All requests are shown in the console window.

A Shortcut

To make things a little easier you can create a windows shortcut. This allows you to change the console window size, font and color, too. The following pictures show the particular steps:
Step 1: Create A Shortcut
Step 2: Select the PHP binary
Step 3: Name the shortcut
Step 4: Change command and working directory
Step 5: Change the window size

2011-07-13

Papaya Callbacks

papaya CMS got a new class PapayaObjectCallbacks recently. The class can be used to define and handle callbacks for other classes and relies heavily on the magic methods. It addresses several problems.
  1. Code duplication if you have several callbacks in one class
  2. Validation before you can use the callback
  3. Easy to use, self speaking API for callbacks

PapayaObjectCallbacks is defined as a sub object with lazy initialization. An array is used to define the callbacks functions and the default return value. If you have a complex/large list of callback functions, I suggest to define a new class that extends PapayaObjectCallbacks. With a special class you can define the dynamic properties/methods using PHPDoc comments.

public function callbacks(PapayaObjectCallbacks $callbacks = NULL) {
  if (isset($callbacks)) {
    $this->_callbacks = $callbacks;
  } elseif (is_null($this->_callbacks)) {
    $this->_callbacks = new PapayaObjectCallbacks(
      array(
        'onEventOne' => TRUE,
        'onEventTwo' => 'default'
      )
    );
  }
  return $this->_callbacks;
}


To call the function is really simple. Just do it!

$result = $this->callbacks()->onEventOne($argument);

If no function was assigned to the callback the defined default will be returned. Otherwise the assigned function is called and its return value will be used. So no validation of the assignment is needed.

The user (of your class with the callbacks) can assign the callback function, now.

$someObject->callbacks()->onEventOne = array($this, 'someCallbackHandler');

In PHP equal or greater 5.3 an anonymous function is possible:

$someObject->callbacks()->onEventOne = function($context, $argumentOne) { ... };

The first argument of the callback is always a context object. The reason for this is, that we still have to be PHP 5.2 compatible, so we can not use the PHP syntax for it. In addition you can edit the context without a local variable. By default this is an instance of stdClass but you can assign any object.

$someObject->callbacks()->onEventOne->context->someProperty = 'some value';

If you like to see the implementation, you can find it in the papaya SVN.

2010-05-09

Highlight Words In HTML

I got an interesting question on IRC recently. How can you highlight some words/word parts in an HTML document?

The Challenge

  • Wrap given words in text content with a span.
  • Add a class to the span depending on the word.
  • Do not touch elements, attributes, comments or processing instructions.
  • Do it case insensitive.
  • Do it the safe way.

Select The Text Content

Well this is the easy part. Get some FluentDOM object, find the part of the document to edit, select all text nodes in it.

$fd = FluentDOM($html, 'html')
  ->find('/html/body')
  ->find('descendant-or-self::text()');

I used two Xpath expressions because it are two steps. This way I can separate them later. In a single expression I could use the short syntax for the axis, shortening it to "/html/body//text()".

Loop

FluentDOM provides an "each()" method, expecting a callback for argument. The callback is executed for each node (in this case each text node). The first argument of the callback is the node itself.

$fd->each(
  function ($node) use ($check, $highlights) {
    ...
  }
);

Prepare The Words

$highlights = array(
  'word' => 'classNameOne',
  'word_two' => 'classNameTwo'
);

I need to check each node against the words and split it at the words. Is is a text value now, so the tool of choice are PCRE. To build a pattern from the words I sort them by length first, then loop, escape and concatinate them. The sorting is important if one word is part of another.

uksort(
  $highlights,
  function ($stringOne, $stringTwo) {
    $lengthOne = strlen($stringOne);
    $lengthTwo = strlen($stringTwo);
    if ($lengthOne > $lengthTwo) {
      return -1;
    } elseif ($lengthOne < $lengthTwo) {
      return 1;
    } else {
      return strcmp($stringOne, $stringTwo);
    }
  }
);
$check = '';
foreach ($highlights as $string => $class) {
  $check .= '|'.preg_quote(strtolower($string));
}
$check = '(('.substr($check, 1).'))iS';

Check And Divide

This pattern can now be used to check, as well to divide the text. A direct replace would be a bad idea, because I need to insert a new element node (the span). Creating nodes using the DOM functions takes care of any special chars.

if (preg_match($check, $node->nodeValue)) {
  $parts = preg_split(
    $check, $node->nodeValue, -1, PREG_SPLIT_DELIM_CAPTURE
  );
  ...
}

The option PREG_SPLIT_DELIM_CAPTURE puts the submatch into the $parts array, too. So it is possible to loop over all parts in their original order.

To Wrap Or Not To Wrap

The $parts array contains the words as well as the text around in separate strings. For each word, a span with the class is needed, all other become separate text nodes.

foreach ($parts as $part) {
  $string = strtolower($part);
  if (isset($highlights[$string])) {
    $span = $node
      ->ownerDocument
      ->createElement('span');
    $items[] = FluentDOM($span)
      ->addClass($highlights[$string])
      ->text($part)
      ->item(0);
  } else {
    $items[] = $node
      ->ownerDocument
      ->createTextNode($part);
  }
}

You now see the reason why I used lowercase versions of the words for keys in the $highlights array. It is easy to check if the $part is a word and get the class for the span.

Replace The Text

The last step is easy again, replace the node with the list of created ones.

FluentDOM($node)->replaceWith($items);

More

This is the basic solution and will only work with PHP 5.3, but I created another version defining a class. You can find the full source of the class example in the FluentDOM SVN at svn://svn.fluentdom.org in examples/tasks/highlightWords.php or on Gist.

2010-04-10

Using PHP DOM With XPath

Often I hear people say "We use SimpleXML, because DOM is so noisy and complex". Well, I don't think so. This article explains how you can parse a XML (an Atom feed) using the PHP DOM extension. No other libraries are involved.

Load the feed

To load the feed, you need to create an new DOMDocument document using it's load() method. This works with the PHP stream wrappers, so you can load local files or urls. DOMdocument, has dedicated methos for XML strings and HTML files and strings, too.

$feed = new DOMDocument();
$feed->load('http://www.a-basketful-of-papayas.net/feeds/posts/default');
...

If here is any problem with the resource, PHP will output error messages. You can use libxml_use_internal_errors() to block them. With libxml_clear_errors() the internal error list is cleared, libxml_get_errors() returns them so you could implemented you own error handling. Just ignore them for now:

$errorSetting = libxml_use_internal_errors(TRUE);
$feed = new DOMDocument();
$feed->load('http://www.a-basketful-of-papayas.net/feeds/posts/default');
libxml_clear_errors();
libxml_use_internal_errors($errorSetting);
...

In the next step you should check if you got some content. I use the documentElement property for this. If it is not here, the feed has to be invalid because any XML needs at least one element node.

if (isset($feed->documentElement)) {
  ...
} else {
  echo 'Invalid feed.';
}

Initialize XPath

Now a XPath object is needed to execute expressions. Atom feeds make use of namespaces, often declaring the atom namespace as default. But in XPath you have no default namespace, you need to register the namespace with an arbitrary prefix. It does not have to be the same prefix used in the XML file. It can't for the default namespace obviously because it has no prefix in the XML file.

...
$xpath = new DOMXPath($feed);
$xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
...

If you load HTML into the DOMDocument using the special methods, all namespaces are ignored. You can skip the registration in this case.

Executing XPath Expressions

The DOMXPath object has two methods for executing xpath expressions. One is query(), it always returns a DOMNodelist. You should use the second one: evaluate(). It will return DOMNodelist objects by default, but depending on the expression it can return other types, too. With evaluate() you have direct access to the title text, it will return an empty string if the feed has no title.

The code selects the element nodes in the registered namespace and casts them to string.

...
echo $xpath->evaluate('string(/atom:feed/atom:title)'), "\n";
echo $xpath->evaluate('string(/atom:feed/atom:subtitle)'), "\n";
...

Next we will loop over all entries. A DOMNodelist works with foreach, the expression will return an empty list if it does not match, so no additional checking is needed. Inside the loop the entry node is used as a context argument for evaluate().

...
foreach ($xpath->evaluate('//atom:entry') as $entryNode) {
  echo $xpath->evaluate('string(atom:title)', $entryNode), "\n";
  echo $xpath->evaluate(
    'string(atom:link[@rel="alternate" and @type="text/html"][1]/@href)',
    $entryNode
    ), "\n";
  echo "\n";
}
...

Conditions

XPath expression can be conditions. It can be used to check if a entry has categories (tags). The return value of the following expression is a boolean value.

... if ($xpath->evaluate('count(atom:category) > 0', $entryNode)) { ... } ...

Loop over attributes

Each entry can have several categories. The title of the category is in it's attribute "term". You can select these attributes directly into a list.

echo 'Categories: ';
foreach ($xpath->evaluate('atom:category/@term', $entryNode) as $index => $categoryAttribute) {
  if ($index > 0) {
    echo ', ';
  }
  echo $categoryAttribute->value;
}
echo "\n";

Complete Example

Here is the full script. Be aware that it outputs text. If you execute it using a webserver (and not the command line), you should add a header('Content-Type: text/plain') to the top.

<?php
$errorSetting = libxml_use_internal_errors(TRUE);
$feed = new DOMDocument();
$feed->load('http://www.a-basketful-of-papayas.net/feeds/posts/default');
libxml_clear_errors();
libxml_use_internal_errors($errorSetting);

if (isset($feed->documentElement)) {
  $xpath = new DOMXPath($feed);
  $xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
  echo $xpath->evaluate('string(/atom:feed/atom:title)'), "\n";
  echo $xpath->evaluate('string(/atom:feed/atom:subtitle)'), "\n";
  echo str_repeat('*', 72), "\n\n";
  foreach ($xpath->evaluate('//atom:entry') as $entryNode) {
    echo $xpath->evaluate('string(atom:title)', $entryNode), "\n";
    if ($xpath->evaluate('count(atom:category) > 0', $entryNode)) {
      echo 'Categories: ';
      foreach ($xpath->evaluate('atom:category/@term', $entryNode) as $index => $categoryAttribute) {
        if ($index > 0) {
          echo ', ';
        }
        echo $categoryAttribute->value;
      }
      echo "\n";
    }
    echo $xpath->evaluate(
      'string(atom:link[@rel="alternate" and @type="text/html"][1]/@href)',
      $entryNode
    ), "\n";
    echo "\n";
  }
} else {
  echo 'Invalid feed.';
}
?>

I hope, I could show you that DOM is really comfortable if you're using XPath. If you want it easier, try FluentDOM. It combines the power and comfort of XPath with the jQuery fluent interface.

2010-04-07

CSS Selectors And XPath Expressions

In this article I will try to show XPath expression counterparts for CSS selectors and explain some of the major differences. Most web developers are familiar with CSS selectors from creating webpages or using Javascript libraries like jQuery.

"Selectors are patterns that match against elements in a tree, and as such form one of several technologies that can be used to select nodes in an XML document. Selectors have been optimized for use with HTML and XML, and are designed to be usable in performance-critical code." - The W3C about CSS 3 selectors:

Here are two good reasons for using CSS selectors and not XPath expressions in the browser. You already know them from writing CSS files and XPath does not work in all browsers. IE supports it only on documents returned from a XHR or generated by XSLT in the browser.

On the server side the situation is different. In PHP XPath 1 is already implemented in the DOM extension. You don't need an additional library to use it. XPath is more specific and flexible. It is not only for selecting elements, but all kind of nodes and values. So how can you convert a CSS selector into an XPath expression?

A little sample HTML:

<html>
  <head></head>
  <body>
    <ul>
      <li class="first">Item One</li>
      <li>Item Two
        <ol>
          <li class="first last">SubItem One</li>
        </ol>
      </li>
      <li class="last">Item Three</li>
    </ul>
  </body>
</html>

Specific ancestor relationships

Let's say we need all li inside ul (but not ol). In CSS this whould be like "ul>li". The > is important. It defines that the li has to be a child of the ul. A simple "ul li" whould match the "SubItem One", too.

The XPath whould be "//ul/li". If the first char of an XPath expression is a slash the context of the expression is always the document. "/html" whould be the root element in the example. The double slash defines that any elements are allowed between the context and the match. The third slash defines the parent child relationship between ul and li. The expression "//ul//li" whould match the "SubItem One", too.

Multiple selectors/expressions

Both CSS selectors and XPath allow multiple selectors/expressions. You can use "," in CSS and "|" in XPath. Let's say we like to match all lists. In CSS selectors this whould be "ul,ol". XPath whould be "//ul|//ol". XPath always return unique nodes in document order, CSS selectors depend on the implementation.

Context

In CSS files context is not important, all selectors are defined for the document. But the context handling is one of the differences between CSS selectors and XPath.

CSS selectors match the current element like it's descendants. XPath has a specific handling for the current element. A dot can represent the current element in a XPath expression. In many cases the dot is optional. The expression "./li" does the same like "li" and matches only children of the current element. The CSS selector "li" whould match the current element if it would be a li and any li that has the current element as an ancestor. Translated to XPath this whould be "name()='li'|.//li". The XPath function name() allows to compare the tag name with a string.

Matching classes

CSS selector have special syntax for classes and token lists. A selector for a class is a dot followed by the class name. ".first" whould match two li elements from the sample html. The attribute selector "[class~=first]" whould do the same. Unfortunately XPath does not have a special syntax for token lists, but it can be emulated using XPath functions.

  1. Normalize the whitespaces in the class attribute to single spaces: normalize-space(@class)
  2. Add single spaces to the begin and end of the normalized attribute: concat(' ', normalize-space(@class), ' ')
  3. Check if the result contains the class name: contains(concat(' ', normalize-space(@class), ' '), ' first ')
  4. Select all nodes matching the condition: //*[contains(concat(' ', normalize-space(@class), ' '), ' first ')]

Namespaces

XPath has no default namespace, each selector without a namespaces matches only on elements that have no namespace. Namespace and tag name are separated with a colon ("html:div"). The * matches any element node in any namespace, but "*:div" is not possible.

CSS selectors have an universial selector, that can be an empty string or an asterisk. Namespace and tag name are separated using a pipe ("html|div"). "*|div" will match div tags in any namespace.

Overview: Short variants for namespace and element matches
Namespace Element XPath CSS
any any * empty string
none "div" div none
"html" "div" html:div html|div
any "div" *[local-name()='div'] div

Axes

Axes are a feature of XPath and not available in CSS selectors. The most selectors work like the "descendant-or-self" axis. This contains the current element and all children, children of the children and so on. The only exception are the sibling combinators.

The default axis in XPath is "child", which contains all children of the current element. A lot of the axes have a short syntax "." is the "self" axis, ".." the "parent" axis. The axis defines which group of nodes are matched.

To match all li in all namespaces the CSS selector whould be "li". In XPath we can use the "descendant-or-self" axis to simulate this. Because of the namespace the result whould be:

descendant-or-self::*[local-name() = 'li']

This can be combined with the class name check:

descendant-or-self::*[local-name() = 'li' and contains(concat(' ', normalize-space(@class), ' '), ' first ')]

Now I've shown that the CSS selector "li.first" is a lot easier then it's XPath expression counterpart :-). But this is the one to one translation of the CSS selector. It's something an automatic converter should generate. The namespace problem is not relevant, because if you load HTML, PHP ignores them and if you load XML you want to use them. An element in one namespace is not the same like in another - even if they have the same local name.

The token list matching (for classes) is something I need from time to time. But XML formats don't have class or similar attributes often.

I think any CSS selector can be converted into a XPath expression. The result might be large and/or complex but it should work.

Why use XPath?

Actually you can do a lot of things with XPath expressions that are not possible with CSS selectors and whould need you to program additional application logic. You can select text nodes, attributes or aggregate values directly.

But this article is already long enough so I will write more about that in another one.

x