Showing posts with label FluentDOM. Show all posts
Showing posts with label FluentDOM. 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-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.



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.

2009-09-12

Scraping Links From HTML

Did you try to scrap content from a html document using regular expressions? This is a bad idea (Read here why!).

With FluentDOM it is easy:

Get all links

Just create and FluentDOM from the HTML string, find all links using XPath and map the nodes to an array.

<?php
require('FluentDOM/FluentDOM.php');
$html = file_get_contents('http://www.papaya-cms.com/');
$links = FluentDOM($html, 'html')->find('//a[@href]')->map(
  function ($node) {
    return $node->getAttribute('href');
  }
);
var_dump($links);
?>

Extend local urls

Need to edit the links? Pretty much the same:

<?php
require('FluentDOM/FluentDOM.php');
$url = 'http://www.papaya-cms.com/';
$html = file_get_contents($url);
$fd = FluentDOM($html, 'html')->find('//a[@href]')->each(
  function ($node) use ($url) {
    $item = FluentDOM($node);
    if (!preg_match('(^[a-zA-Z]+://)', $item->attr('href'))) {
      $item->attr('href', $url.$item->attr('href'));
    }
  }
);
$fd->contentType = 'xml';
header('Content-type: text/xml');
echo $fd;
?>

2009-07-14

FluentDOM Loaders

We are still improving and experimenting with FluentDOM. We removed the constructor and added a load() method. The reason was to allow the creation of new documents with FluentDOM.


Now this is possible:

$fd = new FluentDOM();
$fd->append($fd->document->createElement('html'))
   ->append($fd->document->createElement('body'))
   ->append('<h1>Hell World</h1>');
echo $fd;

FluentDOM uses loader objects (Thanks for the idea Toby) and supports different types of sources. You can load HTML or XML , files or strings or define your own custom loaders. To load a HTML file you can just use the FluentDOM function or the load() method:

$fd = FluentDOM($fileName, 'text/html');

$fd = new FluentDOM();
$fd->load($fileName, 'text/html');

Or you define your own loader object:

$fd = new FluentDOM();
$fd->setLoaders(array(new MyFluentDOMLoader()));
$fd->load($source, $contentType);

You can find an example for inifiles in ~/examples/iniloader/.

2009-06-17

FluentDOM 1.0 Release

We just released FluentDOM 1.0. The package contains the two classes (FluentDOM and FluentDOMStyle) and a lot of examples.

FluentDOM is a test driven project. The tests are included in the package of course.

We decided to use The MIT License for the project. Test it, use it und please give us some feedback.

2009-06-13

FluentDOM.org

FluentDOM got an own webpage at http://fluentdom.org.

You can now find nightly builds at http://nightly.fluentdom.org. A PHPUnit log file and the code coverage report for the latest nightly build is provided, too.

Status Update

We added a FluentDOMStyle class. This class extends FluentDOM and adds support for manipulation of the style attributes.

$items = FluentDOMStyle($xhtml)->find('//div');
$items->css(
  array(
    'text-align' => 'center',
    'color' => 'black'
  )
);
x