Map of XSQL pages to implement discussion forum To create the drill-down ForumTopics.xsql page, we throw together the XSQL page describing its breadcrumbs, actions, data, and paging, a
Trang 1top of the stylesheet before any other XSLT actions, we've changed UtilData.xsl to be imported
instead of included and we've moved both of the <xsl:import> statements to the top of the list,
in the appropriate order:
<! ForumPageStructure.xsl: Transform the Abstract Structure of a <page> >
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="UtilPaging.xsl"/>
Figure 17.33 illustrates all the pages we will build One down, seven to go
Trang 2Figure 17.33 Map of XSQL pages to implement discussion
forum
To create the drill-down ForumTopics.xsql page, we throw together the XSQL page describing its
breadcrumbs, actions, data, and paging, and we're done Example 17.31 shows these four structural parts in a single XSQL data page template
Example 17.31 XSQL Page to Display List of Topics for a Forum
<?xml version="1.0"?>
<! ForumTopics.xsql: Show list of topics for a forum >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<! Include request params so Actions can check for forumuser cookie >
<xsql:include-request-params/>
<xsql:action handler="Paging" rows-per-page="5" url-params="id">
SELECT COUNT(t.id) AS "total"
Trang 3<xsql:query max-rows="{@paging-max}" skip-rows="{@paging-skip}">
SELECT t.id AS h_id,
Note a few interesting things about this page:
• It uses <xsql:include-request-params> because one of its actions requires a login Recall that the actions template needs to check for the presence of a cookie in the request to detect if the user is logged in
• The count query inside the <xsql:action> handler that creates the paging section uses the {@id} to count just the topics in the current forum whose id is passed in the URL
• The forumname and forumid for the breadcrumbs are queried from the database since only the forumid is passed into the page
• Like every page on our site, it refers to the standard ForumStyle.xsl stylesheet
Now if we click on the "XML" forum name from the Forums.xsql page to drill-down to a list of its topics, we see our new ForumTopics.xsql page in action as shown in Figure 17.34
Trang 4Figure 17.34 Drill-down display of topics for a selected forum
As expected, the breadcrumbs properly reflect where we are in the site; the action bar indicates the actions we can perform; the data is formatted in a consistent tabular format; and the paging display is functional The column override template for ROW/Topic that we created earlier is already doing its job of formatting the Topic as a hyperlink to the next-level drill-down page:
TopicPostings.xsql Note that since we haven't set our forumuser cookie yet, the action bar displays the "Enter a New Topic" link as "Login to Enter a New Topic" with a hyperlink to the
Login.xsql page as expected
The TopicPostings.xsql page appears in Example 17.32
Example 17.32 XSQL Page to Display List of Postings for a Topic
<?xml version="1.0"?>
<! TopicPostings.xsql: Show list of postings for a given topic >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<! Include request params so Actions can check for forumuser cookie >
<xsql:include-request-params/>
<breadcrumbs>
<xsql:query>
SELECT t.forumid as "forumid", f.name AS "forumname",
t.id as "topicid", t.title AS "topicname"
FROM forum_topic t, forum f
WHERE t.id = {@id}
AND f.id = t.forumid
</xsql:query>
</breadcrumbs>
<actions>
Trang 5<link page="EnterForumTopicReply.xsql" label="Post a New Reply" login="yes"> <xsql:include-param name="id"/>
topicid passed into the page
17.3.5 Using Recursive Named Templates
Clicking from the ForumTopics.xsql page on a particular topic name's hyperlink now brings us to
a list of all the postings for that topic as illustrated in Figure 17.35
Figure 17.35 Display of all postings for a particular topic
This looks exactly like what we want; however, the text of the posting is a little jumbled for the Java code example I posted It would be nice to format the text in a monospaced font and preserve the spacing and carriage returns so that a posted code example could be understood by others reading the postings It's not as easy as simply formatting the text as an HTML <pre> tag,
Trang 6since preformatted text and table cells don't get along so nicely in HTML The solution involves replacing carriage returns in the text of the posting with an HTML <br> tag so that a line break is reflected without using the <pre> mode Preserving indentation is a little tricky too since, in general, whitespace in HTML is generally not treated as relevant, except in the <pre> sections that we cannot use here So, we create the following named template subroutines:
br-replace
Replaces carriage return with <br>
sp-replace
Replaces consecutive spaces with non-breaking spaces
The br-replace and sp-replace named templates in Example 17.33 illustrate a general
technique that will likely come in handy for future applications: recursive named templates We need to use this more elaborate technique for generalized text substitution since the XPath
translate( ) function we saw in UtilData.xsl only allows one character x to be substituted by another character y If we study the br-replace template, we see that it accepts a parameter named text This will contain the text passed in by the invoking template for which carriage returns need to be substituted by <br> tags
Example 17.33 Library Stylesheet of Text-Formatting Routines
<! UtilTest.xsl: Common text formatting routines >
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <! Replace new lines with html <br> tags >
| Then invoke this same br-replace template again, passing the
| substring *after* the carriage return as the new "$text" to
| consider for replacement
+ >
Trang 7<xsl:when test="contains($text,$cr)">
which uses the XPath contains( ) function to see if the value of the $text parameter contains the value of the $cr parameter, that is, if $text contains a carriage return If it contains a carriage return, we select the value of the substring of $text located before the carriage return using substring-before($text,$cr) We then create a literal <br/> element, followed by a recursive invocation of the br-replace template, passing substring-after($text,$cr) to see
if the remainder of the string has any further carriage returns In the template's
<xsl:otherwise> clause, which will be executed when $text contains no carriage returns, we simply return the value of $text If br-replace has been called recursively, this ends the recursion The sp-replace template is identical to br-replace except that it replaces the occurrence of two consecutive space characters with two consecutive non-breaking space characters represented by
Trang 8We apply these by adding a third column override template now for the ROW/Posting pattern in
our ColumnOverrides.xsl stylesheet, like this:
<! Anywhere a Posting appears in a <ROW>, Format it as Code >
posting will now show up with its carriage returns and spaces replaced by HTML-savvy
equivalents, allowing our posting to be displayed in a monospaced font without having to use a
<pre> element Now we touch the timestamp on our main ForumStyle.xsl stylesheet to cause the
XSQL page processor to reload it along with all the stylesheets it includes or imports We'll
immediately see the difference in the display of our posting when refreshing the TopicPosting.xsql
page as shown in Figure 17.36
Trang 9Figure 17.36 User text in postings with nicely wrapped,
monospaced font
That looks a lot better! Next, we can knock off the TodaysActiveTopics.xsql page by coming up
with the right couple of queries in the underlying XSQL page The page for
TodaysActiveTopics.xsql is shown in Example 17.34
Example 17.34 XSQL Page to Display Discussion Topics Active Today
<?xml version="1.0"?>
<! TodaysActiveTopics.xsql: Listing of topics active today >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<xsql:action handler="Paging" rows-per-page="5">
SELECT count(t.id) AS "total"
FROM forum_topic t,
forum f
WHERE t.forumid = f.id
AND t.lastpost BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE+1)
<xsql:query skip-rows="{@paging-skip}" max-rows="{@paging-max}">
SELECT t.id AS h_id,
Trang 10t.title AS "Topic",
f.name AS "Forum"
FROM forum_topic t,
forum f
WHERE t.forumid = f.id
AND t.lastpost BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE+1)
ORDER BY t.lastpost desc
</xsql:query>
</data>
</page>
Clicking on the Active Topics Today link from the Forums.xsql home page now shows us the list of
all topics across all forums that have been updated today as illustrated in Figure 17.37
Figure 17.37 List of all active discussion topics across all
forums
17.3.6 Reusing Templates to Generate Forms
We need to build a few HTML forms based on the tasks for:
• Logging into the forum by providing a username
• Entering a new topic in a forum
• Posting a new reply to an existing forum topic
• Searching the forums
Since we designed the UtilDataForm.xsl stylesheet earlier in this chapter with templates to format
HTML forms based on a structural description, we can reuse it here The abstract structure of our page can be enhanced to allow an optional <dataform> section By including the UtilDataForm.xsl stylesheet in our ForumPageStructure.xsl and adding one additional xsl:apply-templates with
a select="dataform", like this:
Trang 11<! ForumPageStructure.xsl: Transform the Abstract Structure of a <page> >
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="UtilData.xsl"/>
all of the HTML forms will be rendered automatically across the whole site
The Login.xsql form requires a very simple form structure, so its XSQL page looks like this:
<dataform target="Forums.xsql" submit="Login">
<item type="text" name="userid" label="Your Email"/>
</dataform>
</page>
With this in place, clicking on a link in the action bar like "Login to Enter a New Topic" brings us
to a login page like the one shown in Figure 17.38
Trang 12Figure 17.38 Simple login form
Notice that the target attribute indicates that the HTML form submission will be handled by our
home page, Forums.xsql We need to add one additional XSQL action element to the top of the Forums.xsql page to receive the userid parameter and set the forumuser cookie with its value:
<! Added to the top of the Forums.xsql page to set forumuser cookie >
<xsql:set-cookie name="forumuser" value="{@userid}"
ignore-empty-value="yes" only-if-unset="yes"/>
Now submitting the login page will set the cookie and show the user the list of discussion forums again At that point they will now see action links like "Enter a New Topic" and "Post a New Reply" instead of the links asking them to log in
Example 17.35 shows the EnterForumTopic.xsql page to create the form used for entering a new
topic in a forum
Example 17.35 XSQL Page to Enter a New Topic in a Forum
<?xml version="1.0"?>
<! EnterForumTopic.xsql: Form to enter a new topic in a forum >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<breadcrumbs>
<xsql:query>
SELECT name as "forumname", id AS "forumid",
'Enter a New Topic' AS "topicname"
FROM forum
WHERE id = {@id}
</xsql:query>
</breadcrumbs>
<dataform target="ForumTopics.xsql" submit="Create Your New Topic">
<item type="hidden" name="id">
<xsql:include-param name="id"/>
</item>
<item type="text" name="userid" label="Your Email">
Trang 13<xsql:include-param name="forumuser"/>
</item>
<item type="text" name="title" size="60" label="Topic Subject"/>
<item type="textarea" name="posting" size="60" label="Your Message"/>
</dataform>
</page>
This defines a dataform with a hidden item, a text item, and a textarea We indicate that the
HTML form should be posted to the ForumTopics.xsql page, and add the following extra XSQL action element to the top of ForumTopics.xsql to handle the posted HTML form and insert it into
the forum_new_topic view
<! Added to the top of the ForumTopics.xsql page to handle new posted topic >
<xsql:insert-request table="forum_new_topic" transform="InsertPostedTopic.xsl"/>
When users are just browsing the ForumTopics.xsql page, this action element is a no-op
We need an insert transformation to transform the XML document representing the posted HTML form into the appropriate ROWSET structure for the forum_new_topic table We can generate a
skeleton for this automatically by again using our GenerateInsertTransform.xsql page with the
<FORUMID>, <TITLE>, <USERID>, and <POSTING> elements, as shown in Example 17.36
Example 17.36 Insert Transform for the forum_new_topic Table
<! Created by GenerateInsertTransform.xsl >
<! InsertPostedTopic.xsl: Insert transform for forum_new_topic table >
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <! Transform /request/parameters into ROWSET for forum_new_topic >
Trang 14Now we can click on "Enter a New Topic" and we'll see our EnterForumTopic.xsql HTML form
appear It appears in Figure 17.39
Figure 17.39 Form to enter a new discussion topic
The current userid from the forumuser cookie was selected in the XSQL data page as the content
of the dataform/item element for "Your Email," so this value defaults automatically Submitting the form now inserts the new posting and shows it at the top of the list of postings for the forum The database trigger we created on the forum_new_topic view silently does its job of inserting a row into the forum_topic table and a row into the forum_topic_posting table The database trigger on forum_topic_posting automatically adjusts the number of postings and the lastpost
date for the forum
The form to enter a reply to a posting should display a form, as well as the history of current replies on the topic so the user formulating a reply can consult the trail of previous users' thoughts If we include both a <dataform> section and a <data> section in the XSQL page for
Trang 15EnterForumTopicReply.xsql, our overarching ForumPageStructure.xsl stylesheet with its pagetemplate will cause all the ingredients to fall perfectly into place The EnterForumTopicReply.xsql
page appears in Example 17.37
Example 17.37 XSQL Page to Post a Reply to a Forum Topic
<?xml version="1.0"?>
<! EnterForumTopicReply.xsql: Form to post a reply on a topic >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<breadcrumbs>
<xsql:include-xsql href="ForumTopicLookup.xsql"/>
</breadcrumbs>
<dataform target="TopicPostings.xsql" submit="Post Your Reply">
<item type="hidden" name="id">
We need to make changes to TopicPostings.xsql similar to what we did for the previous example
to handle the posted HTML form and insert it into the forum_topic_posting table So we add an
<xsql:insert-request> to the top of TopicPostings.xsql:
<! Added to the top of the TopicPostings.xsql page
to handle new posted topic >
<xsql:insert-request table="forum_topic_posting"
transform="InsertPostedReply.xsl"/>
Again employing our handy utility to create the skeleton insert transformation for the
forum_topic_posting table, we execute the command:
xsql GenerateInsertTransform.xsql InsertPostedReply.xsl table=forum_topic_posting
and edit the skeleton stylesheet to produce the InsertPostedReply.xsl transformation in Example 17.38
Trang 16Example 17.38 Insert Transform for the forum_topic_posting Table
<! Created by GenerateInsertTransform.xsl >
<! InsertPostedReply.xsl: Insert transform for forum_topic_posting table >
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <! Transform /request/parameters into ROWSET for forum_topic_posting > <xsl:template match="/">
Note that since our table's BEFORE INSERT trigger will automatically assign the ID and POSTED
columns, we can comment them out of the insert transformation template or remove these sections completely
We're ready now to try replying to a posting Clicking on the "Post a New Reply" link from the
TopicPostings.xsql page, we'll see our new form, as shown in Figure 17.40
Trang 17Figure 17.40 Form to let user post a reply on a discussion
topic
Notice that our dataform structure from the underlying XSQL page was transformed into the HTML form as desired, and the data section containing the query to produce the list of current postings in date order is formatted by the data template Since the query produces a
ROW/Posting element, the ROW/Posting template we created in our ColumnOverrides.xsl
stylesheet formats the Posting as HTML-friendly code listings, just as it did on the
TopicPostings.xsql page
We're nearly done The last feature to build is sitewide searching
17.3.7 Dynamic Queries Using Ref Cursors
We create a Search.xsql page in Example 17.39 to display the search form It uses several list-style items instead of just text and textarea like the earlier forms
Example 17.39 XSQL Page to Drive the Forum Search Criteria Form
<?xml version="1.0"?>
<! Search.xsql: Forum search criteria form >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<breadcrumbs>
<forumname>Search</forumname>
</breadcrumbs>
Trang 18<dataform target="ForumSearchResults.xsql" submit="Search">
<xsql:set-page-param name="default" value="{@id}"/>
<item type="text" name="searchFor" size="30" label="Search For"/>
<item type="list" name="searchIn" label="In">
<ROW><VALUE>-7</VALUE><DISPLAY>In Past Week</DISPLAY></ROW>
<ROW><VALUE>-14</VALUE><DISPLAY>In Past Two Weeks</DISPLAY></ROW>
<ROW><VALUE>-21</VALUE><DISPLAY>In Past Three Weeks</DISPLAY></ROW>
as a parameter since new forums might be added at any time In case no item ID is passed into
the Search.xsql page, the query also includes a UNION ALL with the statement:
SELECT -1, 'All Forums' FROM dual
to include that as a default choice in the list
When clicking the search action from within a forum, the forum ID is passed as a parameter to the
search.xsql page, and the default behavior is to search for text only within the current forum If
Trang 19the user clicks on "SEARCH from the Forum list," no ID is passed, so the "All Forums" entry we
added becomes the default instead
Our Search.xsql form then will look like Figure 17.41
Figure 17.41 Form allowing user to search the discussion
forum
The Search.xsql page targets the ForumSearchResults.xsql page so that the search form
parameters can be used to query the database and present the search results What query do we
include in the ForumSearchResults.xsql page? The query will need to look different depending on
what combination of search form parameters the user has set As we learned in Chapter 15,
<xsql:ref-cursor-function> handles all the dynamic aspects inside a stored package function that programmatically determines the query statement, executes that dynamic query, and returns a cursor to the XSQL page to render the results as XML
Since we want to be able to page through query results, we need to pass the various parameters that affect the query to the stored package function, and have it return us both the actual rows retrieved by the dynamic query and a count we can use as the count query of our Paging action handler Example 17.40 shows the ForumSearch package that gets the job done
Example 17.40 Package to Handle Dynamic Discussion Forum Site Search
CREATE OR REPLACE PACKAGE ForumSearch IS
TYPE ref_cursor IS REF CURSOR;
FUNCTION Find(forumid NUMBER := -1,
daysAgo NUMBER := 0,
searchIn VARCHAR2 := 'S',
searchFor VARCHAR2 := NULL) RETURN ref_cursor;
FUNCTION Hits(forumid NUMBER := -1,
daysAgo NUMBER := 0,
Trang 20searchIn VARCHAR2 := 'S',
searchFor VARCHAR2 := NULL) RETURN NUMBER;
END;
CREATE OR REPLACE PACKAGE BODY ForumSearch IS
FUNCTION Query(forumid NUMBER := -1,
daysAgo NUMBER := 0,
searchIn VARCHAR2 := 'S',
searchFor VARCHAR2 := NULL) RETURN VARCHAR2 IS
query VARCHAR2(2000);
noCriteriaSupplied BOOLEAN := LTRIM(RTRIM(searchFor)) IS NULL;
searchEntireMessage BOOLEAN := UPPER(searchIn)='E';
restrictToForum BOOLEAN := forumid > 0;
restrictByDate BOOLEAN := daysAgo < 0;
WHERE t.id = p.topicid
AND t.forumid = f.id';
IF searchEntireMessage THEN
query := query ||' AND ( CONTAINS(p.posting, '''||searchFor||''')>0 '|| ' OR UPPER(t.title) LIKE UPPER(''%'||searchFor||'%''))'; ELSE
query := query || ' AND UPPER(t.title) LIKE UPPER(''%'||searchFor||'%'')'; END IF;
Trang 21FUNCTION Hits(forumid NUMBER := -1,
The package-private Query( ) function figures out the SQL statement based on all the
parameters passed in Notice that if the user has indicated a search not just in the topic title, but
in the message body as well, the Query( ) function appends a WHERE clause using an interMedia CONTAINS query to do a text search through the postings
The public Find( ) function returns the ref cursor of query results returned by the dynamically determined query, while the public Hits( ) function returns the count of the number of rows retrieved by the dynamically determined query
With all the heavy lifting being done behind the scenes by this database package, the XSQL page
in Example 17.41 looks pretty simple
Trang 22Example 17.41 XSQL Page to Drive Discussion Forum Search Results
<?xml version="1.0"?>
<! ForumSearchResults.xsql: Paging display of search 'hits' >
<?xml-stylesheet type="text/xsl" href="ForumStyle.xsl"?>
<page connection="xmlbook" xmlns:xsql="urn:oracle-xsql">
<xsql:action handler="Paging" rows-per-page="5"
url-params="forum daysAgo searchIn searchFor">
in which no parameter value is passed in, we're using a combination of adding zero to number arguments and the NVL( ) function for string arguments
We create the functional index to allow us to quickly search the topic headings in a
case-insensitive way:
CREATE INDEX forum_topic_title_idx ON forum_topic(UPPER(title));
and create an interMedia index using the autosectioner we learned about in Chapter 13, to enable searching on text or XML elements that might be contained in the posting document body:
Trang 23CREATE INDEX forum_topic_posting_idx ON forum_topic_posting(posting)
INDEXTYPE IS ctxsys.contextPARAMETERS ('section group ctxsys.auto_section_group');
With that, we're done We can now click on the Search action link on any page where it appears,
enter search criteria using the Search.xsql form, and then browse the results of the dynamically
determined query in a way that's totally consistent with every other page in the site Figure 17.42
illustrates browsing the search results
Figure 17.42 Browsing results from a cross-forum search
We've come a long way in a short time We've directly experienced several examples now of separating data from presentation and pragmatically implementing it using a combination of Oracle XSQL Pages to handle the assembly of the data page and XSLT stylesheets to handle the modular presentation for anything from one page to an entire web site
17.3.8 Unifying Our News Portal and Discussion Forum
As a final fun project, let's tie together the news portal and the discussion forum we've built with
an XSLT-driven tab-page system Just as we did earlier for the abstract structure of pages, we use
a Site.xsql page, shown in Example 17.42, to reflect the structure of our site:
Example 17.42 XSQL Page to Drive Tab Set
<?xml version="1.0"?>
<! Site.xsql: Provide tabset structure for home page >
<?xml-stylesheet type="text/xsl" href="tab.xsl"?>
<site elt="query" xmlns:xsql="urn:oracle-xsql">
<!
| Pass the values of current "tab" parameter (if passed in explicitly),
| the "lasttab" cookie (if set), and the name of this page to the
Trang 24| XSLT stylesheet that renders the tab bar
+ >
<xsql:set-stylesheet-param name="thispage" value="site.xsql"/>
<xsql:set-stylesheet-param name="tab" value="{@tab}" ignore-empty-value="yes"/> <xsql:set-stylesheet-param name="lasttab" value="{@lasttab}"
ignore-empty-value="yes"/>
<! Remember the last tab clicked in an HTTP Cookie >
<xsql:set-cookie name="lasttab" value="{@tab}" ignore-empty-value="yes"/> <! This is static XML that provides the tab structure >
<tabs>
<tab id="News" name="News"/>
<tab id="Forums" name="Forums"/>
</tabs>
</site>
We're using some <xsql:set-stylesheet-param> elements to pass a few tab-related
parameters to the Tab.xsl stylesheet in Example 17.43 so the stylesheet can know the currently
selected tab The Site.xsql page also uses <xsql:set-cookie> to keep track of the latest tab
chosen by the user Tab.xsl contains templates to match the site element as well as named
templates that match the selected and unselected tabs Although our site will only have the
"News" and "Forums" tabs above, you can easily add more tabs by changing the structural information in the site/tabs section of your data page
Example 17.43 Stylesheet to Render a Set of HTML Tabs for a Frameset
<xsl:when test="$tab"><xsl:value-of select="$tab"/></xsl:when>
<xsl:when test="$lasttab"><xsl:value-of select="$lasttab"/></xsl:when> <xsl:otherwise>
Trang 25<a href=" /index.html" target="_parent">
<img src="{$i}banner.gif" width="342" height="69" border="0"/>
<xsl:template match="tab"> <! Match a regular tab >
<td bgcolor="#B6B687" width="1%" align="LEFT" valign="TOP"><img
src="{$i}tab_open.gif" height="21" width="13" border="0" /></td>
<td width="1%" bgcolor="#B6B687">
Trang 26<a target="contents" href="{@id}.xsql"
<td bgcolor="#B6B687" width="1%" align="RIGHT" valign="TOP">
<img src="{$i}tab_close.gif" height="21" width="10" border="0"/>
<td bgcolor="#336699" width="1%" align="RIGHT" valign="TOP">
<img src="{$i}ctab_close.gif" height="21" width="12" border="0"/>
</td>
</xsl:template>
</xsl:stylesheet>
And finally, a little index.html page ties the tabs frame and defaults the News.xsql page to be the
default content in the main section of the page:
<html>
<head>
<title>XML Book XSQL Sample Site</title>
</head>
<frameset border="no" rows="90,*">
<frame frameborder=no noresize name="tabs" src="site.xsql">
<frame frameborder=no name="contents" src="News.xsql">
</frameset>
</html>
Figure 17.43 illustrates what the user now sees when logged into the site, with the additional tabs
in place The news portal page is the initial tab displayed
Trang 27Figure 17.43 Final news portal and discussion application
with tabs
Clicking on the Forums tab causes Forums.xsql to be requested in the main content frame,
producing our discussion forum home page on that tab
And with this final touch of grace, our journey is complete
In conclusion, we've demonstrated:
• The speed, functionality, and reliability of the Oracle database
• The power of XML as a universal standard for data exchange
• The flexibility of XSLT to transform data into any format required
These factors give us a real leg up in the race to build the next great web application Throughout
this book, we've learned how the powerful features of XML, XSLT, and Oracle8i—in combination
with Oracle XML technologies like the XML Parser, XSLT processor, XML SQL Utility, and XSQL Pages—allow us to:
• Extract data from a database in XML format
• Search and transform XML data using XSLT and XPath
• Send and receive XML datagrams over the Web
• Store datagrams of any shape and size in the database
• Search efficiently through millions of XML documents
• Assemble XML information and transform it for delivery in any needed format
Now, excited by the opportunities that XML and the Oracle XML technologies enable, all that's left
to do is to "go forth and transform!"
Trang 28Part IV: Appendixes
This part of the book contains the following reference appendixes:
Appendix A, explains the source code for the PL/SQL helper packages we built in Chapter 3: xml,
xmldoc, xpath, xslt, and http
Appendix B, describes how to install the XSQL Servlet that you can use with any servlet engine (Apache JServ, JRun, etc.)
Appendix C, graphically summarizes the relationships between key XML concepts and the family
of XML-related standards that support them
Appendix D, provides cheat sheets on XML, XSLT, and XPath syntax
Trang 29Appendix A XML Helper Packages
This appendix includes the PL/SQL package implementations for the XML helper packages we used back in Chapter 5:
A.1 Installing the XML Helper Packages
To install all of the XML helper packages we used in Chapter 5, follow these steps:
1 Change directory to the /examples/appa directory, which contains the files for this
appendix
2 Connect as SYS using SQL*Plus and run the install_sys_helper_packages.sql:
sqlplus sys/change_on_install @install_sys_helper_packages.sql
3 Compile PropertyHelper.java and use loadjava to load it into the database as SYS:
4 javac PropertyHelper.java
loadjava -verbose -resolve -user sys/change_on_install PropertyHelper.class
5 Connect as XMLBOOK using SQL*Plus and run the install_helper_packages.sql script:
sqlplus xmlbook/xmlbook @install_helper_packages.sql:
A.2 Source Code for the XML Helper Packages
The xml, xmldoc, xpath, and xslt packages depend on having the Oracle XML Parser for PL/SQL installed (See Chapter 5 for installation details) In addition, since the Oracle XML Parser for PL/SQL wraps the Oracle XML Parser for Java, one of the following must be true before you can successfully compile and run these packages:
• You must have already:
o Installed the Oracle XML Parser for Java in your schema
o Installed the Oracle XML Parser for PL/SQL packages in your schema, and
o Been granted the JAVAUSERPRIV privilege to execute Java code inside Oracle8i
Trang 30• Alternatively, you must have been granted EXECUTE permission on the Oracle XML Parser for PL/SQL packages installed in another schema
A.2.1 The xml Package
The xml package provides key functionality for parsing XML documents from VARCHAR2, CLOB, BFILE, and URL sources It uses the Oracle XML Parser for PL/SQL's xmlparser package To use these routines, perform these steps:
1 Call one of the xml.parse functions to parse your XML document
2 Work with the returned xmldom.DOMDocument
3 Call xml.freeDocument(yourDoc) to free the memory used by the parsed representation
of your XML document
It is safe to cache the instances of xmldom.DOMDocument in PL/SQL package-level variables for parsed XML documents you need to use over and over during the same database session, but remember that the memory they occupy is not freed until you explicitly call xml.freeDocument Note that if you need to parse an XML document from a URL outside a firewall, or parse any XML document with external references (external entities or DTD, for example) based on URLs outside
a firewall, you must call:
xml.setHttpProxy('proxyServerName');
once in your current database session before using the parse functions The various parsefunctions handle both creating and freeing the xmlparser.parser object used to create the in-memory DOM tree representation of your parsed XML file
Here is the source code for the xml package:
CREATE OR REPLACE PACKAGE xml AS
Set HTTP proxy server in case you reference documents
or DTDs outside a corporate firewall
PROCEDURE setHttpProxy(machinename VARCHAR2,
port VARCHAR2 := '80');
Parse and return an XML document
FUNCTION parse(xml VARCHAR2) RETURN xmldom.DOMDocument;
FUNCTION parse(xml CLOB) RETURN xmldom.DOMDocument;
FUNCTION parse(xml BFILE) RETURN xmldom.DOMDocument;
Trang 31Parse and return an XML Document by URL
FUNCTION parseURL(url VARCHAR2) RETURN xmldom.DOMDocument;
Free the memory used by an XML document
PROCEDURE freeDocument(doc xmldom.DOMDocument);
the in-memory DOM Document representation of the parsed XML
Call freeDocument( ) when you're done using the document returned by the function
- FUNCTION parse(xml VARCHAR2) RETURN xmldom.DOMDocument IS
Trang 33Free the Java objects associated with an in-memory DOM tree
PROCEDURE freeDocument(doc xmldom.DOMDocument) IS
BEGIN
xmldom.freeDocument(doc);
END;
END;
A.2.2 The xmldoc Package
The xmldoc package provides three key functions for saving well-formed XML documents to, and retrieving them from, an xml_documents table A database trigger on the xml_documents table manages a timestamp column indicating the last time a new XML document was saved in the table Each XML document is saved as a CLOB and is accessed by a document name that serves
as the primary key for the document For convenience, you can retrieve an existing document in the table by using:
Trang 34Retrieves a CLOB
The xmldoc.save method uses the xml helper package described in the previous section to
xml.parse the document being saved, checking it for XML well-formedness in the process Any well-formedness errors prevent the document from being saved If the document is well-formed, the code uses the xmldom.writeToClob method to save the XML document in the
xml_documents table's xmldoc CLOB column
If the documents you are saving contain external references to URLs outside your corporate firewall, you'll need to call xml.setHttpProxy once in your current database session before using the xmldoc routines
Here is the source code for the xmldoc package:
CREATE OR REPLACE PACKAGE xmldoc AS
Save an XML document (parsing it first if necessary) into the
xml_documents table with a given document name
PROCEDURE save(name VARCHAR2,
xmldoc VARCHAR2,
docommit BOOLEAN := TRUE);
PROCEDURE save(name VARCHAR2,
xmldoc CLOB,
docommit BOOLEAN := TRUE);
PROCEDURE save(name VARCHAR2,
xmldoc BFILE,
docommit BOOLEAN := TRUE);
PROCEDURE save(name VARCHAR2,
xmldoc xmldom.DOMDocument,
docommit BOOLEAN:=TRUE);
Get an XML document by name from the xml_documents table
FUNCTION get(name VARCHAR2) RETURN xmldom.DOMDocument;
Get an XML document as a CLOB by name from the xml_documents table
FUNCTION getAsCLOB(name VARCHAR2) RETURN CLOB;
Get an XML document as a VARCHAR2 by name from the xml_documents table FUNCTION getAsText(name VARCHAR2) RETURN VARCHAR2;
Remove an XML document by name from the xml_documents table
Trang 35PROCEDURE remove(name VARCHAR2, docommit BOOLEAN := TRUE); Test if a named document exists in the xml_documents table
FUNCTION docExists(name VARCHAR2) RETURN BOOLEAN;
END;
CREATE OR REPLACE PACKAGE BODY xmldoc AS
FUNCTION get(name VARCHAR2, createNew BOOLEAN) RETURN CLOB IS
Trang 36FUNCTION get(name VARCHAR2) RETURN xmldom.DOMDocument IS
BEGIN
RETURN xml.parse(getAsCLOB(name));
END;
PROCEDURE save(name VARCHAR2,xmldoc VARCHAR2,docommit BOOLEAN:=TRUE) IS
c CLOB := get(name, createNew=>TRUE);
PROCEDURE save(name VARCHAR2,xmldoc CLOB,docommit BOOLEAN:=TRUE) IS
c CLOB := get(name, createNew=>TRUE);
PROCEDURE save(name VARCHAR2,xmldoc BFILE,docommit BOOLEAN:=TRUE) IS
c CLOB := get(name, createNew=>TRUE);
PROCEDURE save(name VARCHAR2,xmldoc xmldom.DOMDocument,docommit BOOLEAN:=TRUE) IS
c CLOB := get(name, createNew=>TRUE);
BEGIN
Trang 37xmldom.writeToClob(xmldoc,c);
IF docommit THEN commit; END IF;
END;
PROCEDURE remove(name VARCHAR2,docommit BOOLEAN := TRUE) IS
c CLOB := get(name, createNew=>FALSE);
BEGIN
DELETE FROM xml_documents WHERE docname = name;
IF docommit THEN commit; END IF;
A.2.3 The xpath Package
The xpath package provides four key functions used to exploit XPath expressions on in-memory XML documents:
Selects a list of nodes matching an XPath expression
Here is the source code for the xpath package:
CREATE OR REPLACE PACKAGE xpath AS
Return the value of an XPath expression, optionally normalizing whitespace FUNCTION valueOf(doc xmldom.DOMDocument,
Trang 38xpath VARCHAR2,
normalize BOOLEAN:=FALSE) RETURN VARCHAR2;
FUNCTION valueOf(node xmldom.DOMNode,
xpath VARCHAR2,
normalize BOOLEAN:=FALSE) RETURN VARCHAR2;
FUNCTION valueOf(doc VARCHAR2,
xpath VARCHAR2,
normalize BOOLEAN := FALSE) RETURN VARCHAR2;
FUNCTION valueOf(doc CLOB,
xpath VARCHAR2,
normalize BOOLEAN := FALSE) RETURN VARCHAR2;
Test whether an XPath predicate is true
FUNCTION test(doc xmldom.DOMDocument,xpath VARCHAR2) RETURN BOOLEAN; FUNCTION test(node xmldom.DOMNode, xpath VARCHAR2) RETURN BOOLEAN; FUNCTION test(doc VARCHAR2, xpath VARCHAR2) RETURN BOOLEAN; FUNCTION test(doc CLOB, xpath VARCHAR2) RETURN BOOLEAN;
Extract an XML fragment for set of nodes matching an XPath pattern optionally normalizing whitespace (default is to normalize it)
FUNCTION extract(doc xmldom.DOMDocument,
xpath VARCHAR2:='/',
normalize BOOLEAN:=TRUE) RETURN VARCHAR2;
FUNCTION extract(doc VARCHAR2,
xpath VARCHAR2 := '/',
normalize BOOLEAN := TRUE) RETURN VARCHAR2;
FUNCTION extract(doc CLOB,
xpath VARCHAR2 := '/',
normalize BOOLEAN := TRUE) RETURN VARCHAR2;
Select a list of nodes matching an XPath pattern
Note: DOMNodeList returned has a zero-based index
FUNCTION selectNodes(doc xmldom.DOMDocument,
xpath VARCHAR2) RETURN xmldom.DOMNodeList;
FUNCTION selectNodes(node xmldom.DOMNode,
xpath VARCHAR2) RETURN xmldom.DOMNodeList;
Trang 39FUNCTION selectNodes(doc VARCHAR2,
xpath VARCHAR2) RETURN xmldom.DOMNodeList;
FUNCTION selectNodes(doc CLOB,
xpath VARCHAR2) RETURN xmldom.DOMNodeList;
END;
CREATE OR REPLACE PACKAGE BODY xpath AS
"Casts" a DOMDocument as a DOMNode
FUNCTION toNode(doc xmldom.DOMDocument) RETURN xmldom.DOMNode IS
BEGIN RETURN xmldom.makeNode(doc); END;
- Removes extraneous whitespace from a string
Translates CR, LF, and TAB to spaces, then leaves only a single space between characters
- FUNCTION normalizeWS( v VARCHAR2 )
RETURN VARCHAR2 IS
result VARCHAR2(32767);
BEGIN
result := RTRIM(LTRIM(TRANSLATE(v,CHR(13)||CHR(8)||CHR(10),' '))); WHILE (INSTR(result,' ') > 0) LOOP
document order Passing an XPath of '/' matches the single root node and so the entire document is serialized to XML markup If the
normalizeWhitespace is set to TRUE (the default) then the string is stripped of extraneous whitespace before being returned
- FUNCTION selectAndPrint(doc xmldom.DOMDocument,xpath VARCHAR2,
normalizeWhitespace BOOLEAN := TRUE)
RETURN VARCHAR2 IS
retval VARCHAR2(32767);
result VARCHAR2(32767);
curNode xmldom.DOMNode;
Trang 40The "extract" functions parse the inbound document if necessary
and use selectAndPrint to extract the XML markup for the nodes
matching the XPath expression passed in If the normalize parameter
is TRUE, extraneous whitespace is removed