Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

2008-03-03

XCS Syntax

Extended Cascading Stylesheets (XCS) is a facilitating extension to standard CSS language. Since it is handled by a preprocessor before it is handed out to a CSS-fluent client (in a way similar to, say, how C sources are handled), it is useful to think of it as a macro language, rather then as of an actual extension of the standard. As noted in The List, new keywords and semantics are introduced, and I tried hard to keep the syntax as close to the spirit of CSS as possible.

A new @ rule - @require keyword

Much as in PHP, the new keyword includes the referenced file inline, during the preprocessing. A parenthesized string representing a valid path is required either immediately after the keyword, or preceded with any number of whitespace characters. Example:

@require(../etc/defines.css);

The parenthesized string is examined as a PHP path string - so, slashes will work in Windows environment as well.

Single-line comments

When the string // is encountered anywhere within an stylesheet, the rest of the line, up to ending newline character will be considered an XCS comment, and will be converted to standard CSS /* ... */ comment in place.

Constants

XCS understands the concept of constants rather then variables, declared through assigning. Once declared, the values can be cascaded overshadowing each other. However, the actual value is expanded in the stylesheet only after the cascading is done with. Variable names must be preceded by a variable-indicator string or character (! by default, but that can be changed) in order to be identified by the preprocessor. The value is always in global scope. The assigning is done by tying a variable name to a value:

!green = #0000FF;
!heavy = 4em;
!light = 0.5em;
!green = #FF0000;

It is important to note that all occurrences of !green in the rules within a stylesheet will be replaced with the string #FF0000 (red), because of value cascading. The assignment must be done within a single line, and the ending semicolon must not be omitted.

Simple math

Simple math calculations involving either two declared variables or a variable and a CSS value (or a numeric constant) can be conducted either in variable assignment, or in a rule itself. These simple expressions automatically inherit the CSS unit from the second operand (or from the first, in case the second operand is a numeric constant). Examples:

!red = #ff0000;
!green = #0000FF;
!border = !heavy-!light solid !green;
!heavy = 4em;
!light = 0.5em;

body{
   color: !red-!green; // Colors can be calculated as well
   border: !border;
   bla: !heavy/2;
}

expr Expressions

A more complex form of value expansion is the expr keyword, denoting a required parenthesized expression' value. The expression can contain any number of XCS constants, and even PHP variables. Note: since that is a security disaster just begging to happen in an uncontrolled environment, the expression evaluation can be entirely disabled. The expr keyword must always be followed by a parenthesized expression, either immediately, or after any number of whitespace characters. Examples:

!const = 12;
!size = expr((!const * 2)/24)em; // expands to '1'
!date = expr('10 Dec 2007');
!server = expr($_SERVER['SCRIPT_FILENAME']);
!newSize = expr(!const*22)px; // Can NOT use `expr` values in expr

It is important to note that the values assigned by expression expanding can not be used within expressions recursively, as seen in the last line of the example. The expanded values can be either strings or numerics, whatever, the preprocessor doesn't care, as long as they can be expanded to a finite value.

Rule inheritance

Just like CSS understands element inheritance, XCS understands rule inheritance as well. Rule inheritance can be triggered by using an extends keyword in the selector part of the rule. The extends must be followed by a parenthesized existing rule selector string, either immediately, or after any number of whitespace characters. The rule indicated by the parenthesized selector string is then prepended to the current rule, thus allowing any cascading effect to take place. Example:

p.emphasis {
   font-style: italic;
}
p.strong-emphasis extends (p.emphasis) {
   font-weight: bold;
}

All HTML p elements with class strong-emphasis will be rendered in both bold and italic font.

Defined preprocessor behavior in erroneous situations

The preprocessor acts as suggested by the CSS standard specification for compliant engines: as long as a stylesheet is syntactically correct, the preprocessor will attempt to understand it's semantics, leaving as-is anything it can't understand, or can't understand fully. So, as long as everything looks OK to the parser, the preprocessor won't barf at you - however, there might be, as in CSS, semantic errors that sneaked in.

2008-02-29

Extended Cascading Stylesheets - the beginning

1 What?

Extended Cascading Stylesheets (from now on, XCS) is an attempt at implementing some development-facilitating features to standard Cascading Stylesheets language (CSS) while avoiding the unnecessary language pollution by introducing an intermediate parser, thus keeping the output standards-compliant.
1.1 What?!?
In more simple terms, I tried to make my own time spent with CSS more pleasant, by relying on an intermediate layer (parser) instead on the quirks of the standard.

2 Why?

While working with CSS, I found a lot of annoying stuff - and I'm not talking about browser quirks here, but the language itself. For instance, it has always been annoying for me that there is no single-line comment in CSS (an equivalent to, say, // or # in some other languages) - thus, no quick'n'easy way to kick an entire line out of your current sheet. I realize this is quite individual and really not all that important - however, I'm just marking an example of a trivial feature that made my life suck a bit more.
2.1 Origins

While working on some changes for a friend's site, I noticed I was, essentially, doing the same thing all over again - the layout type of the site was the same (3 columns - 2 fixed width, one liquid), typography was the same, only the color scheme and the fixed column width changed. Most of the CSS changes I did involved similar actions - the base was sound, only minor changes should be implemented. These changes, however, included lots of line hunting, either manually or facilitated by sed or a similar tool in the current editing environment.

2.1.1 Zeitgeist

At about the same time, CSS frameworks became all the rage, with all their pros and cons baggage. Online tools for making layouts based on this or that framework were came to life. New Blogger templates (layouts) with CSS constants support were already introduced, allowing inexperienced users to easily change some aspects of a chosen template. It all seemed to be interconnected somehow, in an effort to make the users/developers lives more pleasant. So, when reading this article...

2.1.2 It all clicked together

It really did. It offered the idea of a DIY tool that could relieve me of all the trivial (and not-so-trivial) aches I faced with CSS - one that would allow me to express myself more easily, while using the familiar CSS syntax, sugar-coated for easier swallowing on my side, plain old for browsers to consume.

3 How?

The first thing I decided to leave out of the original concept is the implementation language. A PHP class was the way to go for me, because a) I don't necessarily always have a Ruby interpreter around and, more importantly, b) a PHP class would lend itself well to making a plugin for embedding into existing CMS solutions. The concept of CSS constants is a definite keeper - this alone would save a whole lot of time normally spent on search-and-replace. Some other concepts were excellent but some didn't really seemed all that important, and some features I considered handy were not there at all. So I sat down and make myself...
3.1 The List
The List of the stuff I wanted to have:
  • CSS variables
  • Extended Math expression support for both colors and measurements - perhaps even strings
  • require-a-like keyword, for inline, compile-time inclusions
  • Single-line comments
  • CSS rule inheritance by extending (as opposed to regular CSS element inheritance by cascading)
  • Easily customizable syntax for the newly introduced features
  • Various levels of pretty-printing of the resulting CSS, coupled with some basic compression
3.2 Implementation plan
Once settled down on the features I'd like to have, I considered my options for concrete implementation. I was told that PHP4 was dead and that the sooner we all start doing stuff with PHP5, the sooner everybody else will follow. So PHP5 then it is. Of course, this severely cuts down on the number of CM systems one could embed this in right now - however, this will change very soon, as hosting services start adopting PHP5 because of lacking PHP4 support. And PHP5 is more fun, anyway.
3.3 The result
Take a look at the result at phpclasses.org.

2008-02-27

Working with GtkScintilla

First thing to note, I was working with PHP-GTK2 (php-gtk-2.0.0 beta) and, naturally, was following the reference on that (PHP-GTK2 reference). However, GtkScintilla section in PHP-GTK2 reference really is not all that informative. Since the GtkScintilla API undergone very little changes from the last version (as far as I can tell), you may be better off using the older reference for GtkScintilla.

Another thing to note, some stuff is missing from the older reference as well - most notably, descriptions (and even names) of nearly all defines are missing, as well as for some of the methods - those related to search functionality provided by GtkScintilla class, for an example. For instance, this is what the reference for set_search_flags method looks like:

PHP-GTK: void set_search_flags(int flags);
PHP-GTK2: void set_search_flags(flags);

OK. Even a somewhat more informative reference didn't take me very far. So, the flags are int. Great. Now what? And how do I do the search, anyways?

Use the source

Well, as it turned out, lacking documentation for the constants wasn't such a problem, because none of search-related constants are defined anyway. So, what's a man to do, except to dive into the source code?

After some greping through the GtkScintilla sources, I've found these values in ext\scintilla\libscintilla\include\Scintilla.h:

#define SCFIND_WHOLEWORD 2
#define SCFIND_MATCHCASE 4
#define SCFIND_WORDSTART 0x00100000
#define SCFIND_REGEXP 0x00200000 

And so that's what I used for my search flags:

@define ("SCINTILLA_FIND_DOWN", 1);
@define ("SCINTILLA_FIND_WHOLE_WORDS", 2);
@define ("SCINTILLA_FIND_MATCH_CASE", 4);
@define ("SCINTILLA_FIND_WORD_START", 0x00100000);
@define ("SCINTILLA_FIND_REGEXP", 0x00200000);

The @s in front of each define are a future safe short-circuit error guard, in case a particular define actually exist. I could surround each statement with ifs, yeah, but this seems like a much nicer way of expressing the same thing - if a define exists, an error occurs and the new define statement is not executed. The error gets suppressed thanks to @, and we're on our merry way. Well, that covered the search-related defines, but I still didn't know how to actually conduct the search.

Putting it to (good) use

After some trial-and error, I've managed to poke my way through. So, in a nutshell:

// $current is a GtkScintilla instance
if (!$firstTime) {
    $pos = $current->get_selection_end();
    $current->set_selection_start($pos);
    $current->goto_pos($pos);
}
$current->search_anchor();
// Search forward
$result = $current->search_next($searchFlags, $searchTerm);
// Search backwards
// $result = $current->search_prev($searchFlags, $searchTerm);
if ($result > 0) echo("Match found at $result");

The if clause determines if we had a previous match and if so, resets the selection and sets the current cursor position at the end of it. Then we anchor the start of the search to the current cursor position by calling GtkSintillas search_anchor method. Then we can actually perform the search, by calling either search_next (search forward) or search_prev (search backwards) methods and passing the appropriate arguments (an integer bitmask $searchFlags and a string to look for, $searchTerm).

Of course, you should have something like this above the code I just introduced:

$firstTime = true; // or false
$searchTerm = "blah"; // whatever your search term is
$searchFlags = SCINTILLA_FIND_MATCH_CASE & SCINTILLA_FIND_WHOLE_WORDS; // or whatever

If successful, both methods set the selection around the search term for you and return the position in the text where the match occured. If the return value is 0, the match wasn't found.

2008-01-21

PHP-GTK experience

Recently, I've been extensively involved in desktop application development using PHP-GTK.

PHP-GTK logo

At first, I just needed a cross-platform, Markdown/Texy-capable desktop blogging tool that would publish to (and open/batch backup from) Blogger, preferably Scintilla-based. I looked around quite a bit and didn't like what I found, so I decided to do myself a favor and just sit down and make it already. Then I weighed some choices on how to actually do it:

First Choice - HOW DO I DO IT?

Quickly, hack together a bunch of batch files
Blah. Obvious. Ugly. Not cross platform.
Make some pretty witty shell script(s) instead
Blah. Uninspired. Not cross platform.
Do it properly
Hmm ... OK, let's try that. Sigh, more choices.

Second Choice - AGAIN, HOW DO I DO IT?

C/C++
Nah.
C#, Mono
Nyeee ... I'm not very familiar with neither the language itself, nor Mono.
Java
Nyeee ... an option. Or is it, really? An option?
Python GTK/QT
Hmmm ... definitely an option.
PHP GTK/QT
Well, let's take a look at that first.

Once settled upon that, the choice between QT or GTK bindings wasn't really much of a choice, actually. QT bindings for PHP are still quite young in development, plus the documentation is still virtually non-existent. Furthermore, as attached as I used to be to QT appearance, I recently switched to GNOME (at least until KDE 4.1) and learned to actually like it, so a GTK interface would be a natural choice for me right now.

Enough. Let's get on with it already

Now, once again, I had but a rhetorical choice between PHP-GKT versions 1 and 2. In its second version it provides a good OO API, some new components and quite stable foundation - although still in beta - so I obviously decided for V2. The new components (at least GtkHtml and MozEmbed, as I wanted to have HTML preview) turned out to be either non-existant, not (yet) fully cross platform, or quite buggy, so I left them out for now. No HTML preview. Pah.

What I did got, free of any charge, is the excellent GtkScintilla component - however, it turned out to be poorly documented and still somewhat rough around the edges. Oh, well. I used some of this, some of that, some looking at the GtkScintilla source coupled with some blind luck, and it turned out OK - which I hope to explain more thoroughly in a future post.

Anyway, the ease of use really amazed me - without Glade or any other GUI-editing IDE, it took just about a day or two to get a fully functional editor/blogging tool. The OO API allowed for easy custom control creation which greatly improved the speed of development. So much so, that I decided to use it for a pending project for a client.

2007-11-01

10 Dos and Don'ts When Using Microformats Parser

MicroformatParser has actually been used in real world (ie. out of my sandbox testing grounds) for some time now, and I've been getting valuable feedback from developers. During that time, some of the most common problems - and some of the best practices to circumvent them - have emerged, and I thought it would be nice to collect them all in one place to share with others.

Dos

Please, do:

... use Tidy

The web is filthy, and you do need something to keep you clean. You can't just assume that you're working with well-formed XML from an external source- 9 out of 10 times the XML parser will choke and your script will croak because of that assumption.

What you can do is try and decrappify the input using Tidy. For a kick- start on using Tidy with PHP, you may want to check out this post as well.

... check your PHP version

For PHP4, everything should just work right out of the box. However, for PHP5 you'll need this script, by Alexandre Alapetite. He's done a great job of wrapping DOM XML extension API, making it available to PHP5 users.

... check xArray documentation

It may be tempting to just call toArray() method on the result and work with a familiar datatype. However, xArray is specifically crafted to facilitate working with collections of objects, such as your parsing results. The documentation is included in the package, and you can re-run PhpDocumentor over the source file to get it in a format you prefer. For more info on xArray you can also check out the documentation wiki. It is a work in progress, but some valuable info is already there.

Also, there is a new xArray version on the way (v0.2), which will make handling complex trees of data even easier.

... check if (bool)FALSE is returned

On error, MicroformatParser returns (bool)FALSE instead of an xArray object. So make sure that everything went OK before you try to do anything further with the result:

if($microformatsResult) ...
... use caching

Actual fetching of the remote page will most likely be the slowest part of your script (if it's not, something is seriously wrong). So, to shorten the execution time, implement some sort of caching mechanism in order to keep remote page fetching to minimum.

... contact me

This isn't really a "best practice" thing, but I think it's still worth keeping in mind. If you find a bug or just keep hitting the wall, don't hesitate to contact me. I'll try to help as much as I can.

Don'ts

There aren't as many of those, but they're just as important. So, please don't:

... assume you're parsing well-formed XHTML

Because it's just not true, most of the time.

... use PHP5 DOM XML extension

As of PHP 5.0, the required DOM XML extension is not bundled with PHP anymore. There is one available from PECL, but you don't want to use that. Thanks to deneme's patience and valuable input we discovered that you can't really plug it in and expect everything to work. You should keep away from it and use Alexandre Alapetite's solution instead.

... use it for something malicious

I can't really tell you what to do with it, but please don't use it for something bad, like email scraping. Would you like your name and email listed in some new directory handed down to generations of spammers? No, I bet you wouldn't. So don't do it to others, either.

... output invalid XML (XHTML included)

This is not strictly related to MicroformatParser usage, but it's a good advice nevertheless. Please, don't do that. The rest of the web will thank you for your effort.

2007-08-13

Spaghetti dinner: the beginning

After the initial idea on possibilities of PHP metaprogramming with templates, I pondered quite a bit on the subject. The thing is, I'm not too keen on template languages for a number of reasons - most of them being aptly disclosed in the comment thread by this guy (e):

Template languages are nice, but they are never as expressive as the language that are implemented in. Perhaps more importantly, they're harder to learn, and have much less documentation, meaning that any poor sap that has to maintain your templated solution is in for a world of hurt.

Generally, I agree with every single word. However, I wondered if being much less expressive is exactly the reason why the template languages can be useful in a particular context - more precisely, could a small, simple project (a couple of pages in size) benefit from such a solution?

Using a full- blown framework is an overkill for such a project. Apart from inevitable execution speed compromises, you don't gain much in speed and ease of development either - you still have to write all your controllers, models and views, which is 3 times the number of spaghetti scripts you'd do. On the flip side, this way you at least have a maintainable application - revisiting spaghetti code can be a real pain.

That's where code generation from a template might come into play. It could provide at least some much needed project structuring, alleviate the common tasks (e.g. setting up the database, fetching and looping through results, stuff like that) and shift the development focus towards the UI1. Also, you never, ever touch the generated spaghetti code.

Anyway, that was the theoretical reasoning I decided to inquire. After some trial and error, I made a parser for an XML - based language, slightly resembling the new Blogger template language. Specifically, there are loop tags for iterating through arrays, function/method results or database results (which one is generated depending on the attributes provided), data tags for echoing variables and/or function/method results, fetch tags for fetching single database results, set tags for setting variables, if and else tags for... well, branching.

For generic tag case, expr: attribute name prefix indicates echoing result of the expression in the attribute value. Also, there are var: attribute value prefix that denotes a variable, and data: attribute value prefix that denotes echoing a variable - either one can appear anywhere in the attribute value.

Anything that exceeds these simple commands should go in action files (auto-included if present) - a global actions.php, plus a specific *_actions.php for every template file. For an example, that is where all the function and class definitions go.

My primary goal was to come up with a language that's simple and restrictive enough to facilitate the common tasks and enforce some code separation, without loosing too much in expressiveness. I feel I'm still not there yet, though things look better with every iteration. I haven't give much thought to generated project infrastructure just yet, but that's the next thing on my list. For an example, generated scripts are the user entry points right now (the old scripting standard - blah.php you request, blah.php you're gonna get). However, it may be better to move them away from the webroot and have a single entry point (a single index.php, dispatching the user requests to particular scripts). Among other things, that way it would be easier to set up "clean" URLs. On the other hand, that may seem a bit too much for the targeted scope.


  1. While the last one might sound irrelevant to some, I learned that UI is one of most important aspects of a project, especially a small- scale one. Making an efficient UI is just as important as efficient backend processing, and easily becomes the most important part when there isn't much backend processing to begin with, as is often the case in small projects. 

2007-05-31

PHP spaghetti

Last couple of days I've been toying with the idea of XML template-based metaprogramming. Essentially, the idea is to create a simple HTML page with some namespaced XML template elements (similar to Blogger new layout tags) that would compile down to plain old PHP spaghetti before deploying. In other words, to have something like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:tpl='http://malatestapunk-stuff.blogspot.com'  
      xml:lang="en" 
      lang="en">
    <tpl:set data="a_variable" value="a value" />
    <tpl:set data="another_variable" action="FunctionName('arg1', 'arg2')" />

    <tpl:if cond="data:a_variable == data:another_variable">
        <tpl:data source="YetAnotherVarFromIncludedFile" />
    </tpl:if>

    <head>
        <title><tpl:fetch source="TableArticles" data="Title" cond="id=1" /></title>
    </head>
    <body>
        <tpl:loop source="TableArticles" cond="author='anyone'" as="Article">
            <li>
                <strong><tpl:data source="Article/Title" /></strong>
                <em><tpl:data source="Article/Author" /></em>

                <p> <tpl:data source="Article/Body" action="ClassName::methodName('arg2', 'arg3')" /> </p>

                <p> <tpl:data action="Solo" /> </p>
            </li>
        </tpl:loop>
    </body>
</html>

turned into something like this:

<?php
include ('config.php');
include ('actions.php');
$link = mysql_connect(DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD);
mysql_select_db(DATABASE_NAME);
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:tpl="http://malatestapunk-stuff.blogspot.com" xml:lang="en" lang="en">
    <?php $a_variable = 'a value';  $another_variable = FunctionName('arg1', 'arg2');  if ($a_variable == $another_variable) {  echo $YetAnotherVarFromIncludedFile;  } ?>

    <head>
        <title><?php 
$_db_result = mysql_query("SELECT Title FROM " . TABLE_PREFIX . "TableArticles WHERE id=1");
$Title = mysql_fetch_array($_db_result, MYSQL_NUM);
echo $Title [0];
?></title>

    </head>

    <body>
        <?php 
$_db_result = mysql_query("SELECT * FROM " . TABLE_PREFIX . "TableArticles WHERE author='anyone'");
while ($Article = mysql_fetch_array($_db_result, MYSQL_ASSOC)) {
?>
            <li>
                <strong><?php echo $Article['Title']; ?></strong>, by
                <em><?php echo $Article['Author']; ?></em> said:
                <p><?php echo ClassName::methodName($Article['Body'], 'arg2', 'arg3'); ?></p>
                <p><?php echo Solo(); ?></p>
            </li>
        <?php } ?>
    </body>
</html>

before deploying it on the server.

Reasoning

While this approach may not be good enough for any serious stuff, if all you need is to get some data from the database and massage it into some XHTML (which is very often the case), it can be a fast and easy way out. By altering only templates, you keep your code/logic separate from your presentation, which is, of course, very important. However, when compiled to a script (which could easily be done either locally - say with make or a similar tool - or server-side) and deployed to a server, it most likely will outperform a more robust solution because of it's specific scope and thus, a smaller footprint.

Another advantage would be simplified RDBMS switching by extending the main parser class and rebuilding the page scripts, without the added overhead of introducing a database abstraction layer to the script itself. And, compared to working with a full-blown framework, this approach allows for a much shorter development cycle, which is kinda cool for rapid prototyping.

2007-02-17

Use the Web: use Tidy

Once you start web programming, it's only a matter of time before you face the task of fetching and parsing the existing contents - be it a web page, a feed, or whatever. Since that content is most likely to be a markup language of some sort, it would be great if you could use a generic parser to weed through it. In fact, since you know your input is some XML dialect (say, XHTML, RSS or Atom), it would be great if you could just use some ready-made XML parser to reach the portions you need through XPath expressions or DOM functions. But that is where the grief begins.

Guess what? It's not gonna work.

So the page you're fetching is boasting to be XHTML strict, but your parser keeps croaking on you. Why is that happening? Have you done something in your previous life to annoy a deity of some sort? Well actually, most existing XML parsers are quite picky - and they should be, since there is only handful of rules they expect to be fulfilled. However, for one reason or another - and this especially goes for XHTML, since people tend to take feed validity more seriously - they seldom are.

So, what do you do?

Meet you new best friend: HTML Tidy. It will do all the nasty cleanup and repair stuff for you, and leave you with an usable document. Originally a Dave Ragget utility program, it is now maintained by a group of dedicated volunteers on SourceForge. One of their goals was to make a a library form of Tidy, to make it easier to incorporate Tidy into other software.

And that they did - for an example, Tidy is an integral part of many current applications, it is available as a PECL extension for PHP 4.3.x and PHP 5 from http://pecl.php.net/package/tidy, and there are bindings for many other languages as well.

Using Tidy PHP extension

There are two flavors of Tidy for PHP: Tidy 1.0 is just for PHP 4.3.x, while Tidy 2.0 is just for PHP 5. This is how you'd use Tidy 1.0 with 4.3:

// ...
// Let's assume you already obtained the page you want to clean up in string $html
// ...
$config = array (
    'ncr' => true,                // allow numeric entities
    'numeric-entities' => true,   // output numeric instead of named entities
    'quote-nbsp' => true,         // quote non-breaking space character
    'fix-uri' => true,            // fix ampersands and such in URIs
    'output-xml' => true,         // output XML; could be XHTML as well, I think
    'char-encoding' => 'utf8'     // use UTF-8 encoding 
);
tidy_parse_string($html);
foreach ($config as $key=>$value) {
    tidy_setopt($key, $value);
}
tidy_clean_repair();
$html = tidy_get_output();
// ...
// Now $html contains cleaned up original page, ready for XML parser
// ...

the most important part being the $config array. This is where we set up Tidy to make the input string $html parser-friendly. There are a lot of other parameters for Tidy, but these are the basic ones that should correct almost any page. For a full reference on Tidy parameters, check out http://tidy.sourceforge.net/docs/quickref.html.

Using Tidy executable

On the down side, it's possible you don't have Tidy extension around in your environment. If that's the case, you might be able to use the Tidy standalone. To do that, you first need to make a Tidy configuration file. This is an example file, with the same options as in the above example:

ncr: 1                # allow numeric entities
numeric-entities: 1   # output numeric instead of named entities
quote-nbsp: 1         # quote non-breaking space character
fix-uri: 1            # fix amperstands and such in URIs
output-xml: 1         # output XML; could be XHTML as well, I think
char-encoding: utf8   # use UTF-8 encoding

Save that file as tidy.conf in the same directory where your script is. Next, in your script, do something along these lines:

// ...
// Let's assume you already obtained the page you want to clean up in string $html
// ...
define ('PATH_TO_YOUR_CONFIG_FILE', 'tidy.conf', true);
$filename =  tempnam("", "OUT");

$fp = fopen($filename, 'w');
fwrite($fp, $html);
fclose($fp);

$cmd = 'tidy -q -config "' . realpath(PATH_TO_YOUR_CONFIG_FILE) . '" '.$filename;
$html = shell_exec (escapeshellcmd ($cmd));
unlink ($filename);
// ...
// Now $html contains cleaned up original page, ready for XML parser
// ...

And that should be it. Once again, note that Tidy accepts a lot of (well documented) configuration parameters. For a full list, check out http://tidy.sourceforge.net/docs/quickref.html.

2007-01-13

PHP microformats parser

Microformats parser is a PHP package for extracting the microformats data embedded into HTML. The gathered data is stored as an xArray of objects - one for each microformat type container found.

Requirements

Microformats parser requires PHP 4.3, with DOM XML extension. Since DOM XML extenstion doesn't ship with PHP5 anymore, there was a problem that was solved thanks to Alexandre Alapetite and Ludwig. Now it is possible to make it work with PHP5 by following this article.

Microformats parser requires xArray package that's not included by default. So, in order to use this package, you need to download the xArray package (you can do it here) and extract the xArray.php file into the lib/ directory of your parser.

Supported microformats

The parser supports most of the hCard (missing SOUND), hCalendar, hReview (missing item info; spec really needs some clarification) and rel elements, according to their respective specification on microformats Wiki.

Usage

The simplest usage example:

$filename = "http://microformats.org/about/people/";
$html = file_get_contents($filename);
$mfParser = new MicroFormatParser();
$mf = $mfParser->parseSource($html);
if ($mf) $mf->each('
   echo "<h1>".get_class($value)."</h1>";
   var_export($value);
   echo "<hr />";
');

As you can see, the parser expects HTML string input. That is because there is a lot of different ways you can fetch a page, so you're free to use whichever one works for you. Another reason is that DOM XML expects valid XML - in our case, an XHTML document. Since many pages out there are near valid but not really, really valid, you can use PHP Tidy functions (if available on your machine) to prevent parser choking to death.

The parser returns false on failure, or an xArray object with all of the microformats it finds otherwise. Therefore, it is good practice to always check the result for false before anything else.

A note on xArray object

xArray is created after Prototype Enumerable object, in order to facilitate array manipulation. It takes some time getting used to, but allows quite clever stuff. I tried my best to keep the source clean and well-commented, and there are some pre-built docs for it in the docs/ folder. You can re-run phpDocumentor over the sources to generate the output that suits you best. However, if you don't like the way it works, you can always use its toArray() method to get the good ol' PHP array out of it. Here is an example of this:

$filename = "http://microformats.org/about/people/";
$html = file_get_contents($filename);
$mfParser = new MicroFormatParser();
$mf = $mfParser->parseSource($html);
if ($mf) var_export($mf->toArray());
A bit more advanced usage example

Before you call the parseSource() method, you can calibrate the parser to extract just the microformats you're after. You do that by passing a hash of options to the parserSetup method, like this:

$mfParser->parserSetup (array (
   'hcard' => true,
   'hreview' => true,
   'hcalendar' => true,
   'reltag' => true,
));

The parser will fetch all the microformats it finds by default, so the previous code just augments the default behavior. However, doing something like this will seriously limit your search (and memory usage ;)):

$mfParser->parserSetup (array (
   'hcard' => true,
   'hreview' => false,
   'hcalendar' => false,
   'reltag' => false,

));

Please note that you have to do this before you call parseSource() method. So the full example source would be:

$filename = "http://microformats.org/about/people/";
$html = file_get_contents($filename);
$mfParser = new MicroFormatParser();
$mfParser->parserSetup (array (
   'hcard' => true,
   'hreview' => false,
   'hcalendar' => false,
   'reltag' => false,

));
$mf = $mfParser->parseSource($html);
if ($mf) $mf->each('
   echo "<h1>".get_class($value)."</h1>";
   var_export($value);
   echo "<hr />";
');