使用 microformats 分离数据与格式

发表于:2007-05-24来源:作者:点击数: 标签:microformats分离格式使用数据
微格式(Microformat)是在标准 XHTML 代码中嵌入结构化数据的一种新方法。阅读本文,了解如何读写 Web 这种全新的微格式。 每当我偶尔看到有人灵光一闪产生的某个理念,我就会自言自语:我要是能想到这个该多好,真是天才! 微格式正是这样一种理念。您知道
微格式(Microformat)是在标准 XHTML 代码中嵌入结构化数据的一种新方法。阅读本文,了解如何读写 Web 这种全新的微格式。

每当我偶尔看到有人灵光一闪产生的某个理念,我就会自言自语:“我要是能想到这个该多好,真是天才!” 微格式正是这样一种理念。您知道,人们一直在尝试从非结构化的 Web 中提取结构化数据。在人们讨论 “语义 Web”(数据与格式分离的一种 Web)时,您会听到一些与此相关的内容。而无论如何,语义 Web 尚未消失,所有在非结构化世界中寻找结构化数据的问题依然存在。

迄今为止,微格式是向着导出 Web 上的结构化数据这一方向迈进的一小步。理念非常简单。获得一个包含某些事件信息的页面:开始时间、结束时间、位置、主题、Web 页面,等等。过去的方法是将这些信息放置在页面的超文本标记语言(HTML)中,微格式摒弃了这种做法,而是添加一些标准化 HTML 标记和层叠样式表(CSS)类名。页面依然可以保留您选择的任何外观,但对于寻找这些格式化的(或者应该说,是微格式化的)HTML 片段的浏览器来说,变化无处不在。

或许查看一段微格式化的 HTML 能帮助您理解这种概念。清单 1 展示了格式化为 hCalendar 微格式标准的一个事件。


清单 1. 一个简单的事件页面
<html>
            <body>
            <div class="vevent">
            <a class="url" href="http://myevent.com">
            <abbr class="dtstart" title="20060501">May 1</abbr> -
            <abbr class="dtend" title="20060502">02, 2006</abbr>
            <span class="summary">My Conference opening</span> - at
            <span class="location">Hollywood, CA</span>
            </a>
            <div class="description">The opening days of the conference</div>
            </div>
            </body>
            </html>
            

单独的一个页面上可以有多个事件,各事件均包含于一个用 vevent 类进行标记的 <div> 标记中。在这个 <div> 标记中是使用不同的标记进行标记的事件数据,有些人用特定的类来标记事件数据。url 类是转向 Web 页面的定位点。dtstartdtend 类位于 title 元素中编码了开始时间和结束时间的标记内。summarylocationdescription 类均属于包含相应内容的标记。

现在,这些条目所在的页面即可使用 CSS 任选方式来编码这些条目了。因此,卡片的外观对于站点来说依然是惟一的。类的命名规范才是最重要的。

这就是微格式要处理的全部。微格式的 Web 站点就像是一个维基百科,您可为想展示的任何数据添加您自己的格式。在我上一次查看该站点时,那里有用于事件、联系信息、评论及其他许多方面的格式。站点中甚至还提供了一个方便的格式创建程序,如 图 1 所示。


图 1. hCalendar 创建程序页面
hCalendar 创建程序页面

在这篇文章中,我使用 PHP 将给定页面中的事件记录提取为 XML 格式。然后创建另外一个 PHP 页面来接受这种 XML,并通过以 hCalendar 微格式编码的事件构建一个 HTML 页面。在此之前,先简要介绍一下微格式的优点和缺点。

优点和缺点

坦白地说,任何经验丰富的工程师在看过上面的 hCalendar 示例后都会问 “这种微格式 中的格式在哪里?” 的确,微格式算不上一种独立的格式,倒不如说是已有格式(HTML)之上的一层。不得不承认,这是微格式的缺点之一。观察以下对比代码,您会更清楚地理解我的意思:

BEGIN:vCalendar
            VERSION:5.0
            PRODID:-//Microsoft Corporation//Works 2000//EN
            BEGIN:vEvent
            DTSTART: 20060501T000000
            DTEND: 20060502T000000
            SUMMARY;ENCODING=QUOTED-PRINTABLE: My Conference opening
            PRIORITY: 3
            END:vEvent
            END:vCalendar
            

这段代码强大、易于阅读,毫无疑问,它也非常容易解析。这段代码看上去似乎超出了 Fortran 77 或早期的 UNIX® 惯例。您可以毫不困难地设想出一种 XML 替代格式,能够对上述格式略加改进。但要想从中获取这种信息格式,您必须编写专用的客户机。这是新标准乃至 XML 标准的一个很大的缺点,却是微格式的显著优点。

使用微格式,您能利用两种极为成功的技术:HTTP(Hypertext Transfer Protocol)和 HTML。这两种标准集成到了世界上的所有桌面中,工程师们一直在努力拓展其用途,使您能够利用它们做更多的事情。

以 Greasemonkey 为例。Greasemonkey 是 Mozilla Firefox 浏览器的一个绝妙扩展,允许您在载入一个页面之后为页面应用 JavaScript 代码。有些人使用此扩展来移除标题广告。有些人用它在不受自己控制的站点上添加额外的信息。另外还有些人编写 Greasemonkey 脚本来查找微格式化的数据。最妙的就是脚本是用 JavaScript 代码编写的,因此您不必担心平台问题。这里完全不需要 Win32 或 Cocoa:只要使用可移植 JavaScript 代码编写您的扩展即可。但对于非 HTML 页面,Greasemonkey 是不起作用的,此时就是微格式大显身手的时机了。

在介绍微格式时,我想采用先抑后扬的方法,我将回顾不久前我在华盛顿西雅图的一次技术研讨会上的经历。当时我在与 Technorati(一个博客搜索引擎)的创建者之一交谈,我认为继 HTML 之后,最成功的基于标记的数据格式应该是 RSS。但在谈话中,那个人指出大多数 RSS 要么形式不佳,要么比较过时,所以 Technorati 不依靠 RSS。他们直接使用 HTML,并在代码中寻求博客风格的模式。

这并不表示 RSS 不好:我热爱 RSS。但我认为在现实中使人们接受 HTML 以外的其他格式非常艰难。所以在 HTML 之上设置格式更有意义。

现在,停止我的长篇大论,让我们来看看代码。先来读取一个页面中的微格式。





回页首


读取微格式

要读取一个使用微格式数据编码的页面,您必须获得一个其上具有事件的 Web 页面。我从一个 .html 文件入手,如 清单 2 所示。


清单 2. Hcalendar.html
<html>
            <head>
            <style>
            body { font-family: arial, verdana, sans-serif; }
            </style>
            </head>
            <body>
            <div style="width:600px;">
            <div class="vevent" id="one">
            <a class="url" href="http://myevent.com">
            <abbr class="dtstart" title="20060501">May 1</abbr> -
            <abbr class="dtend" title="20060502">02, 2006</abbr>
            <span class="summary">My Conference opening</span> - at
            <span class="location">Hollywood, CA</span>
            </a>
            <div class="description">The opening days of the conference</div>
            </div>
            <div class="vevent" id="two">
            <a class="url" href="http://myevent.com">
            <abbr class="dtstart" title="20060503">May 3</abbr> -
            <abbr class="dtend" title="20060504">04, 2006</abbr>
            <span class="summary">My Conference closing</span> - at
            <span class="location">Hollywood, CA</span>
            </a>
            <div class="description">The closing days of the conference</div>
            </div>
            </div>
            </body>
            </html>
            

在 Web 浏览器中查看此文件时,显示效果如 图 2 所示。


图 2. hcalendar.html 页面
hcalendar.html 页面

这看上去并不美观,但我只是想让这个示例尽可能地简单。关键在于您可以使用希望的任何 HTML 样式,使卡片显示为您喜欢的任何外观。只要使用了正确的 CSS 类名,它仍然会被识别为一个微格式化的 hCalendar 条目。

既然已经有了页面,我需要一些 PHP 代码来读取此页面。清单 3 显示了此代码,这是读取脚本的基础。


清单 3. get_page 函数
<?php
            require_once 'HTTP/Client.php';
            function get_page( $url )
            {
            $client = new HTTP_Client();
            $client->get( $url );
            $resp = $client->currentResponse();
            return $resp['body'];
            }
            

此代码使用 HTTP Client PEAR 模块从给定 URL 读取内容。如果您还没有安装该模块,可以使用 PEAR 命令行来安装:

% pear install HTTP_Client
            

接下来,我将站点返回的 HTML 转换成一个 XML Document Object Model(DOM)。谢天谢地,站点返回的 HTML 是 Extensible HTML(XHTML),因此使一个 XML 阅读器指向它非常简单。我利用 清单 4 中的 get_events() 函数完成这个任务。


清单 4. get_events 函数
function get_events( $page )
            {
            $body = get_page( $page );
            $dom = new DomDocument();
            $dom->loadXML( $body );
            $xpath = new DOMXPath( $dom );
            $events = $xpath->query("//div[@class='vevent']");
            $parsed_events = array();
            foreach( $events as $event )
            {
            $e = parse_event( $dom, $event );
            $parsed_events []= $e;
            }
            return $parsed_events;
            }
            

此函数首先调用 get_page() 函数来检索页面内容。随后创建并加载 DomDocument() 函数。得到了 DOM 版本后,我使用 XPath 查询来获得出现 vevent 类的页面上的所有 <div> 标记,并将那些节点传递给 parse_event

如果您不熟悉 XPath,我将为您略加分析。表达式:

//div
            

将匹配任何级别的 <div> 标记。添加如下约束:

//div[@class='vevent']
            

这样就仅匹配具有一个名为 class 的属性、且其本身具有与 vevent 相匹配的值的 <div> 标记。如果您使用 XML 或一种基于 XML 的语言(例如 XHTML 或 RSS),您应该对 XPath 比较熟悉。XPath 是迄今为止导航 XML 树并获得您寻找的信息的最简单的方法。

为了获得各事件 <div> 标记中的数据,parse_event 类使用了更多的 XPath 查询来提取这些数据。如 清单 5 所示。


清单 5. parse_event() 函数
function parse_event( $dom, $event )
            {
            $data = array();
            $xpath = new DOMXPath( $dom );
            $url = $xpath->query( ".//*[contains(@class,'url')]/@href", $event );
            $data['url'] = $url->length > 0 ? $url->item(0)->nodeValue : ';
            $dtstart = $xpath->query( ".//*[contains(@class,'dtstart')]/@title", $event );
            $data['dtstart'] = $dtstart->length > 0 ? $dtstart->item(0)->nodeValue : ';
            $dtend = $xpath->query( ".//*[contains(@class,'dtend')]/@title", $event );
            $data['dtend'] = $dtend->length > 0 ? $dtend->item(0)->nodeValue : ';
            $summary = $xpath->query( ".//*[contains(@class,'summary')]", $event );
            $data['summary'] = $summary->length > 0 ? $summary->item(0)->nodeValue : ';
            $location = $xpath->query( ".//*[contains(@class,'location')]", $event );
            $data['location'] = $location->length > 0 ? $location->item(0)->nodeValue : ';
            $desc = $xpath->query( ".//*[contains(@class,'description')]", $event );
            $data['desc'] = $desc->length > 0 ? $desc->item(0)->nodeValue : ';
            return $data;
            }
            

代码看上去有点复杂,但实际上这只是一组 XPath 查询,在 XML DOM 树中查找具有特定类名的特定标记。而这种 XPath 编码更为复杂,首先,其中有一个符号:

.//*
            

该符号表示 “从这里 开始往下的所有标记”,其中的这里 也就是代码当前看到的 <event> 标记。请注意,$xpath->query 语句现指定了一个附加的参数 $event,这是搜索的根。典型情况下,XPath 查询从文档根处开始,但您可指定其他根。为此,我使用了 $event 条目。

现在,我并不想获得所有标记。我想要的只是一个具有某个属性的标记,该属性名为 class,并包含一个特定的值(例如 url)。因此,我添加了如下语法:

.//*[contains(@class,'url')]
            

这样它就会匹配类名中包含 url 的标记。但我真正想获得的是该标记中的 href 属性,所以我进一步精炼了此查询,方法如下:

.//*[contains(@class,'url')]/@href
            

精炼过的这个查询将获取任何匹配标记的 href 属性。

事件完成并作为来自 get_events() 函数的数组返回后,我还需要另外一个函数,将这个事件数组导出为 XML。为此,我使用了 dump_events() 函数,如 清单 6 所示。


清单 6. dump_events() 函数
function dump_events( $events )
            {
            $dom = new DomDocument();
            $dom->formatOutput = true;
            $root = $dom->createElement( 'events' );
            $dom->appendChild( $root );
            foreach( $events as $event )
            {
            $elEvent = $dom->createElement( 'event' );
            $root->appendChild( $elEvent );
            $elUrl = $dom->createElement( 'url' );
            $elUrl->appendChild( $dom->createTextNode( $event['url'] ) );
            $elEvent->appendChild( $elUrl );
            $elStart = $dom->createElement( 'start' );
            $elStart->appendChild( $dom->createTextNode( $event['dtstart'] ) );
            $elEvent->appendChild( $elStart );
            $elEnd = $dom->createElement( 'end' );
            $elEnd->appendChild( $dom->createTextNode( $event['dtend'] ) );
            $elEvent->appendChild( $elEnd );
            $elSummary = $dom->createElement( 'summary' );
            $elSummary->appendChild( $dom->createTextNode( $event['summary'] ) );
            $elEvent->appendChild( $elSummary );
            $elLocation = $dom->createElement( 'location' );
            $elLocation->appendChild( $dom->createTextNode( $event['location'] ) );
            $elEvent->appendChild( $elLocation );
            $elDesc = $dom->createElement( 'description' );
            $elDesc->appendChild( $dom->createTextNode( $event['desc'] ) );
            $elEvent->appendChild( $elDesc );
            }
            print( $dom->saveXML() );
            }
            

此函数与其他函数背道而驰。这段代码不是围绕某些 XML 进行查询,而是使用 createElementappendElement 创建一个树,从而创建了一个 DOM。随后,我使用 saveXML 命令将数据导出到标准输出。

在命令行中使用 hcalendar.html 页面的 URL 运行这个 PHP 脚本时,我得到了 清单 7 所示的输出结果。


清单 7. PHP 脚本的输出结果
% php get_calendar.php http://localhost/micro/hcalendar.html
            <?xml version="1.0"?>
            <events>
            <event>
            <url>http://myevent.com</url>
            <start>20060501</start>
            <end>20060502</end>
            <summary>My Conference opening</summary>
            <location>Hollywood, CA</location>
            <description>The opening days of the conference</description>
            </event>
            <event>
            <url>http://myevent.com</url>
            <start>20060503</start>
            <end>20060504</end>
            <summary>My Conference closing</summary>
            <location>Hollywood, CA</location>
            <description>The closing days of the conference</description>
            </event>
            </events>
            %
            

现在我有了一个脚本,可以指向任何 Web 页面,并将任何 hCalendar 格式的条目提取为 XML。





回页首


通过 XML 创建 hCalendar 条目

既然已经有了从 Web 页面中提取出来的 XML,那么就可以创建一个 PHP 页面,将此 XML 格式化为 HTML 内的 hCalendar 条目。清单 8 显示了此页面。


清单 8. Index.php
<?php
            $dom = new DomDocument();
            $dom->load( "calendar.xml" );
            $xpath = new DomXPath($dom);
            $events = $xpath->query( '//event' );
            ?>
            <html>
            <head>
            <title>My Calendar</title>
            <style>
            body { font-family: arial, verdana, sans-serif; }
            td { border-bottom: 1px solid black; border-top: 1px solid black; }
            abbr { border-bottom: none; }
            </style>
            </head>
            <body>
            <table>
            <?php
            foreach( $events as $event )
            {
            $desc = $xpath->query( 'description', $event )->item(0)->nodeValue;
            $start= $xpath->query( 'start', $event )->item(0)->nodeValue;
            $end = $xpath->query( 'end', $event )->item(0)->nodeValue;
            $location = $xpath->query( 'location', $event )->item(0)->nodeValue;
            $summary = $xpath->query( 'summary', $event )->item(0)->nodeValue;
            $url = $xpath->query( 'url', $event )->item(0)->nodeValue;
            ?>
            <tr>
            <td>
            <div class="vevent">
            <a class="url" href="<?php echo( $url ); ?>">
            <span class="summary"><?php echo($summary ); ?></span></a><br/>
            Start: <abbr class="dtstart" title="<?php echo($start ); ?>">
            <?php echo($start ); ?></abbr><br/>
            End: <abbr class="dtend" title="<?php echo($end ); ?>">
            <?php echo($end ); ?></abbr><br/>
            Location: <span class="location"><?php echo($location ); ?></span><br/>
            <div class="description"><?php echo($desc ); ?></div>
            </div>
            </td>
            </tr>
            <?php
            }
            ?>
            </table>
            </body>
            </html>
            

这段代码似乎有些复杂,但实际上非常简单。加载前面使用 get_calendar.php 脚本创建的 calendar.xml 文件来启动页面。页面随后启动 HTML,直至 <table> 标记。在此标记中,我循环遍历了各个 <event> 标记,并将它们导出为 HTML 中的行。这样,就完成了 Web 页面。图 3 显示了最终结果。


图 3. index.php 页面
Index.php 页面

为了查看这段代码是否确实编码了 hCalendar 条目,我将 get_calendar.php 脚本指向它。清单 9 显示了结果。


清单 9. 事件 XML 片段
% php get_calendar.php http://localhost/micro/index.php
            <?xml version="1.0"?>
            <events>
            <event>
            <url>http://myevent.com</url>
            <start>20060501</start>
            <end>20060502</end>
            <summary>My Conference opening</summary>
            <location>Hollywood, CA</location>
            <description>The opening days of the conference</description>
            </event>
            ...
            %
            

这有多好呢?我有了一个脚本,能读取带有日历条目的页面并将这些条目导出为 XML。然后我又有了另外一个页面,能将 XML 转换回日历条目。随后原始脚本可以读取该页面并获得相同的数据。这最终变成了一个无休无止的循环。

好吧,或许这不怎么样。也不怎么美观。如果我想让显示效果更美观一点,会怎么样呢?我是否必须转储微格式?完全不需要。在 清单 10 中,我改进了日历条目的格式。


清单 10. Index2.php
...
            <?php
            foreach( $events as $event )
            {
            $desc = $xpath->query( 'description', $event )->item(0)->nodeValue;
            $start= $xpath->query( 'start', $event )->item(0)->nodeValue;
            $end = $xpath->query( 'end', $event )->item(0)->nodeValue;
            $location = $xpath->query( 'location', $event )->item(0)->nodeValue;
            $summary = $xpath->query( 'summary', $event )->item(0)->nodeValue;
            $url = $xpath->query( 'url', $event )->item(0)->nodeValue;
            ?>
            <tr>
            <td class="event">
            <div class="vevent">
            <table width="100%" cellspacing="0" cellpadding="0">
            <tr>
            <td colspan="2">
            <a class="url" href="<?php echo( $url ); ?>">
            <span class="summary"><?php echo($summary ); ?></span></a>
            </td>
            </tr>
            <tr>
            <td>Start</td>
            <td><abbr class="dtstart" title="<?php echo($start ); ?>">
            <?php echo($start ); ?></abbr></td>
            </tr>
            <tr>
            <td>End</td>
            <td><abbr class="dtend" title="<?php echo($end ); ?>">
            <?php echo($end ); ?></abbr></td>
            </tr>
            <tr>
            <td>Location</td>
            <td><span class="location"><?php echo($location ); ?></span></td>
            </tr>
            <tr>
            <td colspan="2">
            <div class="description"><?php echo($desc ); ?></div>
            </td>
            </tr>
            </table>
            </div>
            </td>
            </tr>
            <?php
            }
            ?>
            ...
            

也许您很难从这些标签中发现有什么改变,但从显示效果中能清楚地看出差别,如 图 4 所示。


图 4. index2.php 页面
Index2.php 页面

现在,startendlocation 列排列得非常美观。但它是否仍然解析为 hCalendar 条目?是的,原因在于 get_calendar.php 脚本中的 XPath 代码极为灵活。

清单 11 展示了我为证明上述问题而运行的测试


清单 11. 对 index2.php 的测试
% php get_calendar.php http://localhost/micro/index2.php
            <?xml version="1.0"?>
            <events>
            <event>
            <url>http://myevent.com</url>
            <start>20060501</start>
            <end>20060502</end>
            ...
            

我真的非常喜欢这两个读取和写入脚本之间的对称。





回页首


结束语

微格式是一种注重实效的方法,解决了 Web 上结构化数据的问题。从架构上来看,它是否纯粹是通过 XSLT 样式表这样的机制与格式相分离的 XML 编码数据?并非如此。但我认为这种方法是一种实际的中间步骤,能够帮助您构建起更为智能化的 Web,更易于使用,并且能够提供更好的搜索和数据集成。

原文转自:http://www.ltesting.net