Avid Web surfers will probably already know about (and have maybe even used) the Google Notebook service: it allows users to save to-do items, Web clippings, text scraps, and personal notes in an online journal that they can access through a Web browser. It's also good for collaborative tasks, because you can share a single notebook between multiple users or publish it as a Web page.
What's most interesting about Google Notebook, from a developer perspective, is its Data API; this allows developers the ability to read the contents of public notebooks through a REST-based API, and integrate this data into their own XML-aware client application. Because the API returns data in XML format, it's easy to parse it using popular languages like Perl, PHP or Java™. If you use PHP, for example, a tool like SimpleXML has everything you need to parse REST responses to data requests.
This article will demonstrate how this works, showing you how to integrate content from Google Notebook into your PHP application using SimpleXML. It includes examples of retrieving a list of a user's public notebooks; retrieving all the notes from a notebook; and retrieving notebook content by section.
Before diving into the PHP code, a few words about the Google Notebook Data API are in order. As with all REST-based services, the API works over HTTP, decoding an HTTP query string that contains one or more input parameters and returns an Atom- or RSS-formatted response, suitable for parsing in any XML-aware client.
Currently, the Google Notebook Data API is read-only; it includes methods to retrieve the contents in one or more of a user's public notebooks, but cannot be used to add or modify notebook data or retrieve the contents of private notebooks. That said, it still includes more than enough information to build a reasonably-complete and useful application. Consider Listing 1, which illustrates the result of an API call to retrieve the list of a user's public notebooks:
Listing 1: A REST response from the Google Notebook Data API
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
<id>http://www.google.com/notebook/feeds/00000000000000</id>
<updated>2008-05-12T11:43:32.830Z</updated>
<title type='text'>Anonymous's Notebooks</title>
<link rel='alternate' type='text/html'
href='http://www.google.com/notebook/user/00000000000000'
title='Anonymous's Notebooks'/>
<link rel='http://schemas.google.com/g/2005#feed'
type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000000000'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000000000'/>
<author>
<name>Anonymous</name>
</author>
<generator version='1.0'
uri='http://www.google.com/notebook'>Google Notebook</generator>
<openSearch:totalResults>2</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>10</openSearch:itemsPerPage>
<entry>
<id>http://www.google.com/notebook/feeds/00000000000000/AAAAAAAAAA</id>
<published>2008-05-11T18:05:04.234Z</published>
<updated>2008-05-12T11:43:32.830Z</updated>
<title type='text'>My First Notebook</title>
<summary type='text'>My First Notebook</summary>
<content type='application/atom+xml'
src='http://www.google.com/notebook/feeds/00000000000000/notebooks/AAAAAAAAAA'/>
<link rel='alternate' type='text/html'
href='http://www.google.com/notebook/public/00000000000000/AAAAAAAAAA'
title='My First notebook'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000000000/AAAAAAAAAA'/>
<author>
<name>Anonymous</name>
</author>
</entry>
<entry>
<id>http://www.google.com/notebook/feeds/00000000000000
/BDQ7QIgoQp6TK350j</id>
<published>2008-05-12T07:05:17.607Z</published>
<updated>2008-05-12T11:42:46.103Z</updated>
<title type='text'>My Second Notebook</title>
<summary type='text'>My Second Notebook</summary>
<content type='application/atom+xml'
src='http://www.google.com/notebook/feeds/00000000000000/notebooks/BDQ7QIgoQp6TK350j'
/><link rel='alternate' type='text/html'
href='http://www.google.com/notebook/public/00000000000000/BDQ7QIgoQp6TK350j'
title='My Second Notebook'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000000000/BDQ7QIgoQp6TK350j'/>
<author>
<name>Anonymous</name>
</author>
</entry>
</feed>
|
Take a quick glance through this output to familiarize yourself with its main elements:
- The Google Notebook Data API responds to a REST request with a feed that contains the requested data. This is represented in the XML response by a <feed> element as the root element. The <feed> element contains <link> elements, which contain URLs for the notebook list in both XML and HTML format, and <openSearch:> elements, which contain summary statistics.
- The outermost <feed> element encloses one or more <entry> elements, each representing a notebook. Each <entry> contains further detail, including the title, description, publication date, last update date, and author of each notebook. These are represented by <title>, <summary>, <published>, <updated>, and <author> elements respectively.
In a similar vein, it's possible to obtain a feed with the details of what each notebook contains. The format is similar (although it has more elements). You can see an example of it in Listing 3.
Let's now proceed to an example of processing a Google Notebook Data API feed using PHP. Listing 2 takes the feed from Listing 1 and uses SimpleXML to extract relevant fragments of data from it and format it into a Web page:
Listing 2: Listing a user's public notebooks
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Listing a user's public notebooks</title>
<style>
body {
font-family: Verdana;
}
li {
border-bottom: solid black 1px;
margin: 10px;
padding: 2px;
width: auto;
padding-bottom: 20px;
}
h2 {
color: red;
text-decoration: none;
}
span.attr {
font-weight: bolder;
}
</style>
</head>
<body>
<?php
// set user ID
// replace with actual value
$userid = '00000000000000';
// build feed URL
$feedURL = 'http://www.google.com/notebook/feeds/' . $userid;
// read feed into SimpleXML object
$sxml = simplexml_load_file($feedURL);
// get number of notebooks
$counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
$total = $counts->totalResults;
?>
<h1><?php echo $sxml->title; ?></h1>
<?php echo $total; ?> item(s) found.
<p/>
<ol>
<?php
// iterate over entries in category
// print each entry's details
foreach ($sxml->entry as $entry) {
$title = $entry->title;
$summary = $entry->summary;
$published = $entry->published;
$updated = $entry->updated;
echo "<li>\n";
echo "<h2>$title</h2>\n";
echo "<span class=\"attr\">Summary:</span> $summary <br/>\n";
echo "<span class=\"attr\">Last update on:</span> " .
date('d M Y h:i:s', strtotime($updated));
echo "</li>\n";
}
?>
</ol>
</body>
</html>
|
Figure 1 demonstrates the output you might see:
Figure 1. A Web page displaying a user's public notebooks
Listing 2 begins by using the simplexml_load_file() function to send a request to the feed URL and
convert the response into a SimpleXML object. It then iterates over the
<entry> elements in the response, processing each one using a foreach()
loop and retrieving the information shown in Figure 1. Child
nodes under each <entry> are represented as SimpleXML object
properties—for example, the <title> node is represented by $sxml->title, the <summary> node by $sxml->summary, and so on.
A word here about the feed URL: it's generated by appending a user's unique identifier to the standard feed URL, in the format http://www.google.com/notebook/feeds/USERID. Obtaining the identifier is a manual task: you will need to visit the corresponding Google Notebook page and manually copy the user identifier into your PHP script.
That takes care of listing a user's public notebooks. Now, how about displaying the contents of a notebook?
This isn't particularly difficult either: all you need to do is obtain the
notebook identifier (a manual process similar to the process of obtaining a user
identifier, described in the previous section), generate the feed URL in the format http://www.google.com/notebook/feeds/USERID/notebooks/NOTEBOOKID
and send a REST request to this URL. Listing 3 demonstrates what the output might look like:
Listing 3: A REST response from the Google Notebook Data API
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
<id>http://www.google.com/notebook/feeds/00000000/notebooks/AAAAAAAAA</id>
<updated>2008-05-12T07:47:31.118Z</updated>
<title type='text'>My first notebook</title>
<link rel='alternate' type='text/html'
href='http://www.google.com/notebook/public/00000000/AAAAAAAAA'
title='My first notebook'/>
<link rel='http://schemas.google.com/g/2005#feed'
type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000/notebooks/AAAAAAAAA'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000/notebooks/AAAAAAAAA'/>
<author>
<name>Anonymous</name>
</author>
<generator version='1.0' uri='http://www.google.com/notebook'>
Google Notebook</generator>
<openSearch:totalResults>5</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>10</openSearch:itemsPerPage>
<entry>
<id>http://www.google.com/notebook/feeds/00000000/notebooks
/AAAAAAAAA/NDQ9jIgoQ6qH53p0j</id>
<published>2008-05-12T06:43:10.186Z</published>
<updated>2008-05-12T06:43:46.415Z</updated>
<category scheme='http://schemas.google.com/notebook/gdata/2007/section'
term='SDQqRIwoQm5zj4J0j' label='My Books'/>
<title type='text'>How to do Everything with PHP and MySQL
A beginner's guide to using PHP and M...</title>
<content type='html'><a href="http://www.everythingphpmysql.com/">
How to do Everything with PHP and MySQL</a><br>A beginner's
guide to using PHP and MySQL together <br></content>
<link rel='alternate' type='text/html'
href='http://www.google.com/notebook/public/00000000/AAAAAAAAA#NDQ9jIgoQ6qH53p0j'
title='How to do Everything with PHP and MySQL
A beginner's guide to using PHP and M...'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000/notebooks
/AAAAAAAAA/NDQ9jIgoQ6qH53p0j'/>
<author>
<name>Anonymous</name>
</author>
</entry>
<entry>
<id>http://www.google.com/notebook/feeds/00000000/notebooks/AAAAAAAAA/
NDSGpIgoQ6oX13p0j</id>
<published>2008-05-12T06:42:01.066Z</published>
<updated>2008-05-12T06:42:46.926Z</updated>
<category scheme='http://schemas.google.com/notebook/gdata/2007/section'
term='SDQqRIwoQm5zj4J0j' label='My Books'/>
<title type='text'>MySQL: The Complete Reference An authoritative
reference to MySQL</title>
<content type='html'><a href="http://www.mysql-tcr.com/">
MySQL: The Complete Reference</a><br>An authoritative reference to
MySQL<br></content>
<link rel='alternate' type='text/html'
href='http://www.google.com/notebook/public/00000000/AAAAAAAAA#NDSGpIgoQ6oX13p0j'
title='MySQL: The Complete Reference An authoritative reference to MySQL'/>
<link rel='self' type='application/atom+xml'
href='http://www.google.com/notebook/feeds/00000000/notebooks/
AAAAAAAAA/NDSGpIgoQ6oX13p0j'/><author>
<name>Anonymous</name>
</author>
</entry>
...
</feed>
|
This is not very different from the output shown in Listing 1. It begins with a <feed> root element, followed by <link> elements, which contain URLs for the notebook contents in both XML and HTML format, and <openSearch:> elements, which contain summary statistics for the notebook.
The outermost <feed> element encloses one or more <entry> elements,
each representing a note. Each <entry> contains further detail, including
the category, title, content, publication date, last update date, and author.
These are represented by <category>, <title>, <content>,
<published>, <updated>, and <author> elements respectively. The
<content> element, in particular, holds the actual content of the note. The
type=html attribute indicates that the content is represented in HTML format (and so, can be directly rendered in a browser).
Listing 4 illustrates the PHP code to process this XML and render it as a Web page:
Listing 4: Displaying notebook contents
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Listing a user's notes</title>
<style>
li {
border-bottom: solid black 1px;
margin: 10px;
padding: 2px;
width: auto;
padding-bottom: 20px;
}
a {
color: red;
text-decoration: none;
}
</style>
</head>
<body>
<?php
// set user ID and notebook ID
// replace these with actual values
$userid = '000000000000000000';
$notebookid = 'AAAAAAAAAA';
// build feed URL
$feedURL = 'http://www.google.com/notebook/feeds/' . $userid .
'/notebooks/' . $notebookid;
// read feed into SimpleXML object
$sxml = simplexml_load_file($feedURL);
// get number of notes
$counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
$total = $counts->totalResults;
?>
<h1><?php echo $sxml->title; ?></h1>
<?php echo $total; ?> note(s) found.
<p/>
<ol>
<?php
// iterate over entries in notebook
// print each entry's details
foreach ($sxml->entry as $entry) {
$entry->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$content = $entry->xpath("atom:content[@type='html']");
echo "<li>{$content[0]}</li>\n";
}
?>
</ol>
</body>
</html>
|
Figure 2 illustrates the likely result:
Figure 2. A Web page displaying the contents of a user's public notebook
In Listing 4, the feed URL is dynamically generated from the user and notebook identifier supplied, and the resulting XML is processed into a SimpleXML object using the simplexml_load_file() method. The various <entry> elements are then processed with a foreach loop, and the content of each note is retrieved and displayed. Notice the use of the xpath() method to retrieve only those <content> nodes which have a type=html attribute attached.
You can customize the API output by adding some of the following parameters to your REST query:
- The
start-indexparameter, which specifies the start offset for the notebook entries; - The
max-resultsparameter, which specifies the number of notebook entries to retrieve; - The
updated-min,updated-max,published-min, andpublished-maxparameters, which specifies a date range (updated or published) for the returned entries; - The
orderbyparameter, which specifies how entries should be ordered.
Listing 5 demonstrates how to restrict the output of Listing 4 to only five entries and sort them by their original position in the notebook:
Listing 5: Customizing API output
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Listing a user's notes</title>
<style>
li {
border-bottom: solid black 1px;
margin: 10px;
padding: 2px;
width: auto;
padding-bottom: 20px;
}
a {
color: red;
text-decoration: none;
}
</style>
</head>
<body>
<?php
// set user ID and notebook ID
// replace these with actual values
$userid = '02956669685298890120';
$notebookid = 'BDT3iIgoQ6oKhyZ0j';
// build feed URL
// set max results and sort order
$feedURL = 'http://www.google.com/notebook/feeds/' . $userid .
'/notebooks/' . $notebookid;
$feedURL .= '?max-results=5&orderby=position';
// read feed into SimpleXML object
$sxml = simplexml_load_file($feedURL);
?>
<h1><?php echo $sxml->title; ?></h1>
<p/>
<ol>
<?php
// iterate over entries in notebook
// print each entry's details
foreach ($sxml->entry as $entry) {
$entry->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$content = $entry->xpath("atom:content[@type='html']");
echo "<li>{$content[0]}</li>\n";
}
?>
</ol>
</body>
</html>
|
Listing 5 is just like Listing 4, except
that it adds the max-results and orderby parameters to the feed URL to further customize the data retrieved. Figure 3 illustrates the output:
Figure 3. A Web page displaying the filtered contents of a user's public notebook
Working with notebook sections
Google Notebook allows users to organize notes into sections, and access these sections through its Data API. These sections are represented by <category> elements under each entry—you'll see some examples of these in Listing 3. It's not very difficult to process these and add an extra level of detail to your client application; Listing 6 enhances Listing 4 and shows you how:
Listing 6: Displaying notebook sections
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Listing a user's public notes by section</title>
<style>
li {
border-bottom: solid black 1px;
margin: 10px;
padding: 2px;
width: auto;
padding-bottom: 20px;
}
a {
color: red;
text-decoration: none;
}
</style>
</head>
<body>
<?php
// set user ID and notebook ID
// replace these with actual values
$userid = '00000000000000';
$notebookid = 'AAAAAAAAAAAA';
// build feed URL
$feedURL = 'http://www.google.com/notebook/feeds/' . $userid .
'/notebooks/' . $notebookid;
// read feed into SimpleXML object
$sxml = simplexml_load_file($feedURL);
// get number of notes
$counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
$total = $counts->totalResults;
// get section labels
$sxml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$categories = array_unique($sxml->xpath('//atom:category/@label'));
?>
<h1><?php echo $sxml->title; ?></h1>
<?php echo $total; ?> note(s) found.
<p/>
<?php
// get entries
// iterate over entries and check section labels
// print each entry's details in the appropriate section
$entries = $sxml->xpath("//atom:entry");
foreach ($categories as $c) {
echo "<h2>{$c['label']}</h2>\n";
echo "<ol>\n";
foreach ($entries as $entry) {
$entry->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
if(trim($entry->category['label']) == trim($c['label'])) {
$content = $entry->xpath("atom:content[@type='html']");
echo "<li>{$content[0]}</li>\n";
}
}
echo "</ol>\n";
}
?>
</body>
</html>
|
Figure 4 demonstrates the revised output:
Figure 4. A Web page displaying the sections of a user's public notebook
The addition of sections makes Listing 6 a little more
complicated than Listing 4. First, the script generates and
sends a REST request to the feed URL, converting the resultant XML into a
SimpleXML object. Next, you use the xpath() method to
retrieve all the <category> elements, read their label attributes and generate an array containing the unique labels
(section names). A nested foreach() loop then
iterates over the entries, assigns them to sections, and displays them as a Web page.
However, this isn't the only way to solve this particular issue. The Google
Notebook Data API also allows developers to restrict notebook entries to a
particular category (section), by appending the category name to the URL for the
notebook feed, such as http://www.google.com/notebook/feeds/USERID/notebooks/NOTEBOOKID/-/Section1/.
You can use this fact to develop an alternative way to build a categorized view of notebook entries, as in Listing 7:
Listing 7: Displaying notebook sections
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Listing a user's public notes by section</title>
<style>
li {
border-bottom: solid black 1px;
margin: 10px;
padding: 2px;
width: auto;
padding-bottom: 20px;
}
a {
color: red;
text-decoration: none;
}
</style>
</head>
<body>
<?php
// set user ID and notebook ID
// replace these with actual values
$userid = '00000000000000';
$notebookid = 'AAAAAAAAAAAAAAA';
// build feed URL
$feedURL = 'http://www.google.com/notebook/feeds/' . $userid .
'/notebooks/' . $notebookid;
// read feed into SimpleXML object
$sxml = simplexml_load_file($feedURL);
// get number of notes
$counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
$total = $counts->totalResults;
// get section labels
$sxml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$categories = array_unique($sxml->xpath('//atom:category/@label'));
?>
<h1><?php echo $sxml->title; ?></h1>
<?php echo $total; ?> note(s) found.
<p/>
<?php
// iterate over categories
foreach ($categories as $c) {
echo "<h2>{$c['label']}</h2>\n";
echo "<ol>\n";
// get feed of entries for each category
$categoryfeedURL = $feedURL . '/-/' . urlencode($c['label']);
$sxml2 = simplexml_load_file($categoryfeedURL);
// iterate over entries in category
// print each entry's details
foreach ($sxml2->entry as $entry) {
$entry->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$content = $entry->xpath("atom:content[@type='html']");
echo "<li>{$content[0]}</li>\n";
}
echo "</ol>\n";
}
?>
</body>
</html>
|
Listing 7 takes a different approach. First, it processes the notebook feed and isolates the category names from it using the same approach as Listing 6. Then, for each category found, it generates a new REST request with an additional category parameter, to restrict the output to only the entries matching that category. The output of this subrequest is then further processed using SimpleXML and displayed, to generate the same output as that seen in Figure 4.
As these examples illustrate, the Google Notebook Data API is a mature, convenient, and flexible way for developers to integrate public notebook content into any Web application. It's great to use when you create a mashup, or simply build a customized interface to Google Notebook. Play with it, and see what you think!
Learn
-
XFront tutorial: Understand how
REST works and how to build Web services in the REST style.
-
Enable REST with Web services, Part 1: REST and Web services in WSDL 2.0 (Eran Chinthaka, developerWorks, May 2007): Learn to use REST to enable Web services with REST.
-
Developer's Guide and Reference Guide: Learn more about the Google Notebook Data APIs.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
- The technology
bookstore: Browse for books on these and other technical topics.
-
developerWorks
podcasts: Listen to interesting interviews and discussions for software developers.
Get products and technologies
-
IBM
trial software for product evaluation: Build your next project with trial software available for download directly from developerWorks, including application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
XML zone discussion forums: Participate in any of several XML-related discussions.
-
developerWorks XML zone: Share your thoughts: After you read this article, post your comments and thoughts in this forum. The XML zone editors moderate the forum and welcome your input.
-
developerWorks blogs: Check out these blogs and get involved in the developerWorks community.
Vikram Vaswani is the founder and CEO of Melonfire, a consulting services firm with special expertise in open-source tools and technologies. He is also the author of the books PHP Programming Solutions and How to do Everything with PHP and MySQL.
Comments (Undergoing maintenance)





