Non-obtrusive HTML Replacing (non-ob)
The typical approach
A typical way that HTML pages are generated dynamically is to have HTML be create first be returned from the code:
A code example of this in XQuery might look like this:
let $article := doc(...)/article
let $title := $article/title/text()
let $body := $article/article-body
return
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{$title}</title>
</head>
<body>
<div id="article-title">{$title}</div>
<div id="article-body">{$body}</div>
</body>
</html>
In this approach, the xqy file takes an xml file for input, and outputs the html with data from the xml file:

JSP pages work in a similar way, although JSF, Tapestry, and Wicket have made it so objects output the HTML, but in the end you still have the notion of a “page” being something that is constructed by the code in real-time. But the concern of these approaches is focused on trying to find a elegant way to construct HTML from some other data model, whether it be objects or just “loose” data from the database. There is an impedance mismatch between objects and HTML and also between relational data and HTML. The frameworks and approaches and best practices in the OO world are meant to minimize the difficulty inherent in those mismatches.
This makes it difficulty to move from HTML design and markup to a dynamic page. Usually a front-end coder creates the markup which then a developer uses really as a guide to create a dynamic page whose resulting HTML matches the HTML of the front-end coder. And if the front-end coder makes changes to the HTML, he needs to notify the developer who then tries to adapt the code to produce the new markup. This process of going from design to implementation can be very costly and somewhat risky, and it doesn’t fit very well in the agile approach of using short and frequent cycles in developing a site.

The non-ob approach
An approach we took for one of ours sites is something I call “Non-obtrusive HTML replacing” or “non-ob” for short. We thought: since HTML is technically an XML file, why don’t we treat it like a piece of content itself, just like all the other content in the database, and modify the HTML in memory as needed? Rather than having the “page” be XQuery code that builds HTML, why don’t we let the front-end coder build the HTML and our code will just make the dynamic changes. So HTML file is an input to the “page” which just has rules and instructions about how to modify the markup. This is “non-obtrusive” because we are leaving the HTML file from the front-end coder intact. And we are just replacing text, attributes, or elements of the HTML in memory, hence the “HTML Replacing” part of the name.

This makes it so that the front-end coder can create HTML, Javascript, and CSS so that it works correctly in a static setting, and then he can check these html, css, and js files into our source control system. The developers just need to be able to confidentially select the items to change, and then the front-end coder is free to change anything else on the page or about the page that he wants. He can move elements around, or remove them or insert new ones. He can add CSS classes, change Javascript on the page, whatever. And he can check the new versions into the source control system and not have to have the changes go through the development team. His changes will automatically be part of the site. As long as his changes don’t break the HTML replacing, then he pretty much can change the markup however, whenever he wants.

The first day we tried this, the front end coder wanted to make changes several times to the markup soon after informing us that the markup was “done.” We just told him to go ahead and make the changes, that it wouldn’t affect us. So he did, and continued to do so, all while we were making the page dynamic and even after we declared the code to be “done.”
Under this approach, the code of the article.xqy page might look like:
import module namespace non-ob= "http://non-ob" at "non-ob.xqy";
declare namespace html = "http://www.w3.org/1999/xhtml";
let $article := doc(…)/article
let $title := $article/title/text()
let $body := $article/article-body
let $xhtml := doc("/article.xml")
let $replacements :=
<replacements>
<replacement>
<path>{non-ob:get-path($xhtml//html:div[@id="article-title"])}</path>
<text>{$title}</text>
</replacement>
<replacement>
<path>{non-ob:get-path($xhtml//html:div[@id="article-body"])}</path>
<element>{$body}</element>
</replacement>
</replacements>
let $xhtml := non-ob:replace($xhtml, $replacements)
return $xhtml
Notice that there is little or no HTML in the xqy file. This achieves a truer separation between the presentation code and business logic. All that the XQuery code for the page is doing is transforming HTML data using XML data from the database. The visual presentation is totally driven by markup.
This also makes it so that the front end coder does not need to know any XQuery at all. All he has to do is write valid XHTML and make sure the page works in a static context. We (the developers) will take care of making the page dynamic. This made it possible for us to get a front-end coder started right away on our project, and he could use any HTML-authoring tools he wanted, and he use any CSS and Javascript he wanted and be confident that nothing (or very little) would be lost in translation from static to dynamic pages.
Because the short cycles and freedom that the front-end coder now has, we are able to spend our time and effort making the experience rich, rather than making sure the page works at all. The effort has moved more from the server programming to the front-end programming, not completely but more than it would be otherwise if we were using a Java stack or something similar. And with the speed of MarkLogic (database lookups and not having to marshall data from relational to object to XML), AJAX requests happen so quickly, that it starts to push the boundary that all the data in the database starts to act as if it were all in memory of the browser.
Implementation of the non-ob replace() function
At first I tried using in-mem-update but the problem was that a new copy of the entire HTML tree was created with each replacement. So I wrote a function that took an XML doc of declared replacements and then the replace() function would just make one copy of the HTML with all changes in one pass. On my laptop, with about 20 replacements, the time to do the replacements takes around 50 milliseconds. Three things can be replaced: entire elements, text of elements, or values of attributes:
module namespace non-ob= "http://non-ob";
declare function replace($xhtml, $replacements) {
dispatch($xhtml, $replacements)
};
declare function passthru( $x as node(), $replacements) as node()* {
for $z in $x/node() return dispatch( $z, $replacements )
};
declare function dispatch( $node as node(), $replacements) as node()* {
typeswitch ( $node )
case text() return $node
case comment() return $node
case element( * ) return (
let $current-path := xdmp:path($node)
let $match := $replacements/replacement/path[text() eq $current-path][1]
return (
if ($match)
then (
let $replacement := $replacements/replacement[path/text() eq $match]
return (
if ($replacement/text)
then (
element { fn:QName ("http://www.w3.org/1999/xhtml",fn:local-name( $node ) ) } {
$node/@*,
$node/*,
$replacement/text/text()
}
)
else if ($replacement/attr)
then (
element {fn:QName ("http://www.w3.org/1999/xhtml",fn:local-name( $node ) )} {
(
for $attr in $node/@*
return (
if (fn:local-name($attr) eq $replacement/attr/@name)
then (attribute{fn:name($attr)} {$replacement/attr/text()} )
else ($attr)
)
),
$node/*,
$node/text()
}
)
else ($replacement/element/*)
)
)
else (element { fn:QName ("http://www.w3.org/1999/xhtml",fn:local-name( $node ) )} { ( $node/@*, passthru( $node, $replacements) ) })
)
)
default return element { fn:QName ("http://www.w3.org/1999/xhtml",fn:local-name( $node ) )} { ( $node/@*, passthru( $node, $replacements) ) }
};
declare function get-path($node) {
try {
xdmp:path($node[1])
}
catch ($e) {
""
}
};
Try it out
Here’s an example of dynamically changing the title, some HTML elements, and the href of an anchor.
1. Save the non-ob.xqy code (above) to somewhere in your MarkLogic installation.
2. The following HTML is real markup from this blog. Note the complexity of the the markup, javascript, and CSS. Save this HTML (or just save the source from http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/) in a file somewhere on your local filesystem.:
<!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" dir="ltr" lang="en">
<head profile="http://gmpg.org/xfn/11">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Commandline interface for XQuery « XQuery Web Application Development</title>
<style type="text/css" media="screen">
@import url( http://s1.wp.com/wp-content/themes/pub/rubric/style.css?m=1272404110g );
</style>
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="http://xquerywebappdev.wordpress.com/feed/" />
<link rel="pingback" href="http://xquerywebappdev.wordpress.com/xmlrpc.php" />
<link rel="alternate" type="application/rss+xml" title="XQuery Web Application Development » Commandline interface for XQuery Comments Feed" href="http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/feed/" />
<script type="text/javascript">
/* <![CDATA[ */
function addLoadEvent(func){var oldonload=window.onload;if(typeof window.onload!='function'){window.onload=func;}else{window.onload=function(){oldonload();func();}}}
/* ]]> */
</script>
<link rel="stylesheet" href="http://s0.wp.com/wp-content/themes/h4/global.css?m=1256671583g" type="text/css" />
<link rel='stylesheet' id='snap-css' href='http://s0.wp.com/wp-content/plugins/mshots/cluetip.css?m=1248212374g&ver=2.3' type='text/css' media='all' />
<script type='text/javascript' src='http://s0.wp.com/wp-includes/js/comment-reply.js?m=1233870502g&ver=20090102'></script>
<script type='text/javascript' src='http://s1.wp.com/wp-includes/js/jquery/jquery.js?m=1267815531g&ver=1.4.2'></script>
<script type='text/javascript' src='http://s1.wp.com/wp-content/plugins/mshots/jquery-ui-personalized-1.6rc2.packed.js?m=1233870503g&ver=MU'></script>
<script type='text/javascript' src='http://s0.wp.com/wp-content/plugins/mshots/cluetip.js?m=1253160243g&ver=2.5.1'></script>
<script type='text/javascript' src='http://s1.wp.com/wp-content/plugins/mshots/mshots.js?m=1253160243g&ver=4.4'></script>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://xquerywebappdev.wordpress.com/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://xquerywebappdev.wordpress.com/wp-includes/wlwmanifest.xml" />
<link rel='index' title='XQuery Web Application Development' href='http://xquerywebappdev.wordpress.com/' />
<link rel='prev' title='‘eq’, ‘=’ and ‘is’' href='http://xquerywebappdev.wordpress.com/2010/04/29/eq-and-is/' />
<link rel='next' title='Rules of thumb when you can’t see the data you expect' href='http://xquerywebappdev.wordpress.com/2010/04/29/rules-of-thumb-when-you-cant-see-the-data-you-expect/' />
<meta name="generator" content="WordPress.com" />
<link rel='canonical' href='http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/' />
<link rel='shortlink' href='http://wp.me/pSWB2-2c' />
<style type="text/css">
.quicklinks ul {list-style:none;margin:0;padding:0;text-align:left}
.quicklinks ul li {float:left;margin:0}
#wpcombar .avatar {border:1px solid #999 !important;padding:0 !important;margin:-3px 5px 0 0 !important;vertical-align:middle;float:none;display:inline !important;}
#wpcombar {direction:ltr; background:#666 url('http://s0.wp.com/i/sprite.png?m=1259787853g') 0 -222px repeat-x;color:#ddd;font:12px Arial, Helvetica, sans-serif;height:28px;left:0;margin:0;position:absolute;top:0;width:100%}
#wpcombar .menupop ul li a {color:#555 !important;text-shadow:none;font-weight:normal;white-space:nowrap;}
#wpcombar .menupop ul li a:hover {color:#fff !important; background: #888 url('http://s0.wp.com/i/sprite.png?m=1259787853g') 0 -222px repeat-x !important;text-shadow: #666 0px -1px 0px;}
#wpcombar .menupop a span {background:url('http://s0.wp.com/i/sprite.png?m=1259787853g') right bottom no-repeat;padding-right:.8em;line-height: 28px;}
#wpcombar .menupop ul {-moz-box-shadow:0 4px 8px rgba(0,0,0,0.1);-webkit-box-shadow:0 4px 8px rgba(0,0,0,0.1);background:#fff;display:none;position:absolute;border:1px solid #dfdfdf;border-top:none !important;float:none}
html>body #wpcombar .menupop ul {background:rgba(255,255,255,0.97);border-color:rgba(0,0,0,0.1);}
#wpcombar .menupop.myaccount ul, #wpcombar .menupop.mydashboards ul, #wpcombar .menupop.newpost ul {min-width:140px}
#wpcombar .menupop li {float:none;margin:0;padding:0;background-image:none;}
#wpcombar .quicklinks a {border:none;color:#ddd !important;text-shadow:#555 0px -1px 0px;display:block;font:13px Arial, Helvetica, sans-serif;font-weight:normal;letter-spacing:normal;padding:0 0.75em;line-height:28px;text-decoration:none !important;}
#wpcombar .quicklinks a:hover {text-shadow:#333 0px -1px 0px;}
.quicklinks a:hover,#wpcombar .menupop:hover {background: #555 url('http://s0.wp.com/i/sprite.png?m=1259787853g') 0 -282px repeat-x;}
#adminbarlogin {float:left;display:inline;}
#adminbarsearch {float:right;}
#adminbarsearch {height: 18px;padding: 3px;}
#adminbarsearch * {color: #555;font-size:12px;}
#adminbarsearch label, #adminbarsearch a { height: 28px; color: #ccc; display:block;float:left;padding:3px 4px;text-shadow:0px -1px 0px #444;}
#adminbarsearch a {text-decoration:underline;}
#adminbarsearch a:hover {color:#fff;}
.wp-admin #adminbarsearch #q {
height: 19px !important;
line-height: normal !important;
width: 208px !important;
margin-top: 0px !important;
}
.adminbar-input {
display: block !important;
float:left !important;
font:12px Arial, Helvetica, sans-serif !important;
border: 1px solid #626262 !important;
padding: 2px 3px !important;
margin-right: 3px !important;
background:#ddd url('http://s0.wp.com/i/sprite.png?m=1259787853g') top left no-repeat !important;
-webkit-border-radius: 0 !important;
-khtml-border-radius: 0 !important;
-moz-border-radius: 0 !important;
border-radius: 0 !important;
outline: none;
text-shadow: 0 1px 0 #fff;
}
#adminbarsearch #q {width: 200px;}
button.adminbar-button {
position: relative;
border: 0;
cursor: pointer;
overflow: visible;
margin: 0 !important;
float:left;
background: url('http://s0.wp.com/i/sprite.png?m=1259787853g') right -107px no-repeat;
padding: 0 14px 0 0;
text-align: center;
}
button.adminbar-button span {
position: relative;
display: block;
white-space: nowrap;
height:19px;
background: url('http://s0.wp.com/i/sprite.png?m=1259787853g') left -69px no-repeat;
padding: 3px 0 0 14px;
font:12px Arial, Helvetica, sans-serif !important;
font-weight:bold !important;
color: #444 !important;
text-shadow: 0px 1px 0px #eee !important;
}
button.adminbar-button:active {
background-position: right -184px !important;
text-shadow: 0px 1px 0px #eee !important;
}
button.adminbar-button:hover span {
color: #000 !important;
}
button.adminbar-button:active span {
background-position: left -146px !important;
}
button.adminbar-button::-moz-focus-inner {
border: none;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
button.adminbar-button span {
margin-top: -1px;
}
}
body {padding-top: 28px !important;}
</style>
<!--[if IE 6]>
<style type="text/css">
#wpcombar, #wpcombar .menupop a span, #wpcombar .menupop ul li a:hover, #wpcombar .myaccount a, .quicklinks a:hover,#wpcombar .menupop:hover {
background-image: none !important;
}
#wpcombar .myaccount a {
margin-left:0 !important;
padding-left:12px !important;
}
</style>
<![endif]-->
<style type="text/css" media="print">
#wpcombar { display:none; }
</style>
<script type="text/javascript">
function showNav(el) { el.getElementsByTagName('UL')[0].style.display='block'; }
function hideNav(el) { el.getElementsByTagName('UL')[0].style.display='none'; }
function pressthis(step) {if (step == 1) {if(navigator.userAgent.indexOf('Safari') >= 0) {Q=getSelection();}else {if(window.getSelection)Q=window.getSelection().toString();else if(document.selection)Q=document.selection.createRange().text;else Q=document.getSelection().toString();}} else {location.href='http://xquerywebappdev.wordpress.com/wp-admin/post-new.php?text='+encodeURIComponent(Q.toString())+'&popupurl='+encodeURIComponent(location.href)+'&popuptitle='+encodeURIComponent(document.title);}}
</script>
<link rel="shortcut icon" type="image/x-icon" href="http://www.gravatar.com/blavatar/b5cb2c931a325551ceaa474db2ad7b31?s=16&d=http://s2.wp.com/i/favicon.ico" />
<link rel="icon" type="image/x-icon" href="http://www.gravatar.com/blavatar/b5cb2c931a325551ceaa474db2ad7b31?s=16&d=http://s2.wp.com/i/favicon.ico" />
<link rel="apple-touch-icon" href="http://www.gravatar.com/blavatar/159d72b5eea356b5c2999e1e377c0438?s=158&d=http://s0.wp.com/wp-content/themes/h4/i/webclip.png" />
<link rel='openid.server' href='http://xquerywebappdev.wordpress.com/?openidserver=1' />
<link rel='openid.delegate' href='http://xquerywebappdev.wordpress.com/' />
<link rel="search" type="application/opensearchdescription+xml" href="http://wordpress.com/opensearch.xml" title="WordPress.com" />
<link rel="search" type="application/opensearchdescription+xml" href="http://xquerywebappdev.wordpress.com/osd.xml" title="XQuery Web Application Development" />
<style type="text/css">
#header{
background: url(http://s1.wp.com/wp-content/themes/pub/rubric/images/rubric/pen-sm.jpg) no-repeat top right;
}
#header a {
color:#B54141;
}
</style>
</head>
<body>
<div id="rap">
<h1 id="header"><a href="http://xquerywebappdev.wordpress.com/">XQuery Web Application Development</a></h1>
<div id="content">
<!-- end header -->
<h2>April 29, 2010</h2>
<div class="post-136 post type-post hentry category-newbie-track" id="post-136">
<h3 class="storytitle"><a href="http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/" rel="bookmark">Commandline interface for XQuery</a></h3>
<div class="meta">Filed under: <a href="http://en.wordpress.com/tag/newbie-track/" title="View all posts in newbie track" rel="category tag">newbie track</a> — Ryan Semerau @ 6:27 am <a class="post-edit-link" href="http://xquerywebappdev.wordpress.com/wp-admin/post.php?post=136&action=edit" title="Edit Post">Edit This</a><br /></div>
<div class="storycontent">
<div class='snap_preview'><div class="pd-rating" id="pd_rating_holder_1802738_post_136"></div>
<p><script type="text/javascript" charset="utf-8">
PDRTJS_settings_1802738_post_136 = {
"id" : "1802738",
"unique_id" : "wp-post-136",
"title" : "Commandline+interface+for+XQuery",
"item_id" : "_post_136",
"permalink" : "http%3A%2F%2Fxquerywebappdev.wordpress.com%2F2010%2F04%2F29%2Fcommandline-interface-for-xquery%2F"
}
</script><br />
MarkLogic doesn’t provide an actual commandline interface, but there are some web-based tools the behave like one. This makes it easier to tinker with XQuery code since you don’t have to create an xqy file. This is the easiest way to enter ad hoc code.</p>
<p><strong>CQ</strong></p>
<p>You probably already CQ in the Samples directory of your MarkLogic installation. If not, you can download it from <a href="http://developer.marklogic.com/code/">here</a>. You can just copy the “cq” directory to the Docs directory and then access it at <a href="http://localhost:8000/cq">http://localhost:8000/cq</a>. The four buttons under the textarea execute the code and display the results in the various formats (text, xml, html). The Profile button will return detailed profiling information about the code execution.</p>
<p><strong>DQ</strong></p>
<p>DQ is an enhancement to CQ that include syntax highlighting, tabs, and line numbers. You can download DQ <a href="http://www.xqueryhacker.com/2010/01/early-version-of-dq-an-alternative-interface-for-mark-logics-cq-xquery-editor/">here</a>. Unzip the contents into the “cq” directory (see above) and then you can access it at <a href="http://localhost:8000/cq/dq">http://localhost:8000/cq/dq</a></p>
</div> </div>
<div class="feedback">
<a href="http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/#respond" title="Comment on Commandline interface for XQuery">Leave a Comment</a> </div>
</div>
<h2 id="comments">No Comments Yet
<a href="#postcomment" title="Leave a comment">»</a>
</h2>
<p>No comments yet.</p>
<p><a href='http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/feed/'><abbr title="Really Simple Syndication">RSS</abbr> feed for comments on this post.</a>
<a href="http://xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/trackback/" rel="trackback">TrackBack <abbr title="Uniform Resource Identifier">URI</abbr></a>
</p>
<div id="respond">
<h2 id="postcomment">Leave a comment</h2>
<div id="cancel-comment-reply"><small><a rel="nofollow" id="cancel-comment-reply-link" href="/2010/04/29/commandline-interface-for-xquery/#respond" style="display:none;">Click here to cancel reply.</a></small></div>
<form action="http://xquerywebappdev.wordpress.com/wp-comments-post.php" method="post" id="commentform">
<p>Logged in as <a href="http://xquerywebappdev.wordpress.com/wp-admin/profile.php">Ryan Semerau</a>. <a href="http://xquerywebappdev.wordpress.com/wp-login.php?action=logout" title="Log out of this account">Logout »</a></p>
<!--<p><small><strong>XHTML:</strong> You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> </small></p>-->
<p><textarea name="comment" id="comment" cols="90%" rows="10" tabindex="4"></textarea></p>
<p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
<input type='hidden' name='comment_post_ID' value='136' id='comment_post_ID' />
<input type='hidden' name='comment_parent' id='comment_parent' value='0' />
</p>
<input type="hidden" name="genseq" value="1273042484" />
<p>You are the author of this post.</p><p><input type="checkbox" name="subscribe_blog" id="subscribe_blog" value="subscribe" style="width: auto;" tabindex="7"/> <label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog">Notify me of new posts via email.</label></p><input type="hidden" name="sub-type" value="comment-form-text0" />
</form>
</div>
<!-- begin footer -->
</div>
<!-- begin sidebar -->
<div id="menu">
<ul>
<li id="search-3" class="widget widget_search"><form role="search" method="get" id="searchform" action="http://xquerywebappdev.wordpress.com/" >
<div><label class="screen-reader-text" for="s">Search for:</label>
<input type="text" value="" name="s" id="s" />
<input type="submit" id="searchsubmit" value="Search" />
</div>
</form></li>
<li id="pages-4" class="widget widget_pages"><h2 class="widgettitle">Pages</h2>
<ul>
<li class="page_item page-item-15"><a href="http://xquerywebappdev.wordpress.com/hello-world-with-xquery-on-marklogic/" title="Hello, world! with XQuery on MarkLogic">Hello, world! with XQuery on MarkLogic</a></li>
<li class="page_item page-item-3"><a href="http://xquerywebappdev.wordpress.com/object-oriented-vs-functional-programming/" title="Object Oriented vs. Functional Programming">Object Oriented vs. Functional Programming</a></li>
</ul>
</li>
<li id="archives-4" class="widget widget_archive"><h2 class="widgettitle">Archives</h2>
<ul>
<li><a href='http://xquerywebappdev.wordpress.com/2010/05/' title='May 2010'>May 2010</a></li>
<li><a href='http://xquerywebappdev.wordpress.com/2010/04/' title='April 2010'>April 2010</a></li>
</ul>
</li>
<li id="categories-2" class="widget widget_categories"><h2 class="widgettitle">Categories</h2>
<ul>
<li class="cat-item cat-item-271"><a href="http://xquerywebappdev.wordpress.com/category/commentary/" title="View all posts filed under commentary">commentary</a>
</li>
<li class="cat-item cat-item-34867394"><a href="http://xquerywebappdev.wordpress.com/category/newbie-track/" title="View all posts filed under newbie track">newbie track</a>
</li>
<li class="cat-item cat-item-87423"><a href="http://xquerywebappdev.wordpress.com/category/tips-n-tricks/" title="View all posts filed under Tips n' Tricks">Tips n' Tricks</a>
</li>
<li class="cat-item cat-item-1"><a href="http://xquerywebappdev.wordpress.com/category/uncategorized/" title="View all posts filed under Uncategorized">Uncategorized</a>
</li>
</ul>
</li>
<li id="linkcat-13552" class="widget snap_preview widget_links"><h2 class="widgettitle">Favorite Blogs</h2>
<ul class='snap_preview xoxo blogroll'>
<li><a href="http://blog.msbbc.co.uk/">Alex Bleasdale</a></li>
<li><a href="http://www.kellblog.com/">MarkLogic CEO's Blog</a></li>
<li><a href="http://developer.marklogic.com/blog">MarkLogic Developer Blog</a></li>
<li><a href="http://www.xqueryhacker.com/">XQueryHacker</a></li>
</ul>
</li>
<li id="linkcat-1035" class="widget snap_preview widget_links"><h2 class="widgettitle">Other</h2>
<ul class='snap_preview xoxo blogroll'>
<li><a href="http://www.28msec.com/download/enterprise_webapps.pdf">Developing an Enterprise Web Application in XQuery (pdf)</a></li>
</ul>
</li>
<li id="linkcat-12716" class="widget snap_preview widget_links"><h2 class="widgettitle">References</h2>
<ul class='snap_preview xoxo blogroll'>
<li><a href="http://www.xqueryfunctions.com/xq/">Functx functions</a></li>
<li><a href="http://developer.marklogic.com/docs" title="official docs from MarkLogic">MarkLogic Developer Docs</a></li>
<li><a href="http://www.w3.org/TR/xquery/" title="Official spec from W3C">XQuery 1.0 Specification</a></li>
</ul>
</li>
</ul>
</div>
<p class="credit"><cite><a href='http://wordpress.com/' rel='generator'>Blog at WordPress.com</a>.</cite></p>
</div>
<script type="text/javascript">_qacct='p-18-mFEk4J448M';_qoptions={labels:'adt.0,language.en,wp.loggedin'};</script>
<script type="text/javascript" src="//secure.quantserve.com/quant.js"></script>
<noscript><p><img class="robots-nocontent" src="//secure.quantserve.com/pixel/p-18-mFEk4J448M.gif?labels=adt.0%2Clanguage.en%2Cwp.loggedin" style="display:none" height="1" width="1" alt="" /></p></noscript>
<script type='text/javascript' src='http://i.polldaddy.com/ratings/rating.js?ver=MU'></script>
<script src="http://s.stats.wordpress.com/w.js?19" type="text/javascript"></script>
<script type="text/javascript">
st_go({'blog':'13094960','v':'wpcom','user':'1','user_id':'13488119','post':'136','subd':'xquerywebappdev'});
ex_go({'crypt':'RDZ8LFkxbXEsMHZxWURlOStxTl10X2F1Q1VVakpwRGdob2NQW0RGMEFfcklsa3FMTFF+NS49UnJnaDE1RmYrWkpILWZQQUYxMm1KVWRDJlhyTXBFWnMxN2ZuWyVCYjJncm5dLVImdnJ6Ylc5YTlkNGh3NF9SWXJUTGZCam5EMXBmLDctWHlGP1RFdjQxZ2NhenArVTVxeHMsVS09NzM5fmxLeDIxQixlQiV8UjdhdWNUaUkvWm1KRDVjanBWW0pRd1FDLGJPYU0uRmp5TXV2K3FrPU1iMjg1K3I='});
</script>
<script type="text/javascript" src="http://wordpress.com/js/admin-bar-13488119-1272043535-847den.php"></script>
<script type="text/javascript">
/* <![CDATA[ */
adminbar=adminbar.replace(/%%BLOGID%%/g, '13094960');
adminbar=adminbar.replace(/%%POSTID%%/g, '136');
adminbar=adminbar.replace(/%%FAVORITELINK%%/, '');
adminbar=adminbar.replace(/%%SEARCHACTION%%/, 'http://en.search.wordpress.com/');
adminbar=adminbar.replace(/%%BLOGHOST%%/g, 'xquerywebappdev.wordpress.com');
adminbar=adminbar.replace(/%%BLOGHOME%%/g, 'http://xquerywebappdev.wordpress.com');
adminbar=adminbar.replace(/%%BLOGTITLE%%/g, 'XQuery Web Application Development');
adminbar=adminbar.replace(/%%EDITLINK%%/, '<li><a class="post-edit-link" href="http://xquerywebappdev.wordpress.com/wp-admin/post.php?post=136&action=edit" title="Edit Post">Edit Post</a></li>');
adminbar=adminbar.replace(/%%URL%%/, 'xquerywebappdev.wordpress.com/2010/04/29/commandline-interface-for-xquery/', 'mg');
document.write(adminbar);
if ( document.createElement ) {
var q = document.getElementById('wpcombar');
var r = document.createElement('div');
r.innerHTML = q.innerHTML;
q.parentNode.removeChild(q);
r.setAttribute('id', 'wpcombar');
r.setAttribute('class', 'snap_nopreview');
r.style.zIndex = '1001';
document.body.insertBefore(r, document.body.childNodes[0]);
}
/* ]]> */
</script>
</body>
</html>
3. Put the absolute path to the HTML file in the “$html-filepath” variable below and run this through CQ (use the XML button to see the HTML output):
xquery version '1.0-ml';
import module namespace non-ob= "http://non-ob" at "non-ob.xqy";
declare namespace html = "http://www.w3.org/1999/xhtml";
let $html-filepath := "C:/running-cq.html"
let $xhtml := xdmp:tidy(xdmp:document-get($html-filepath))[2]/html:html
let $post-title := "Ryan wuz here"
let $image-el := <html:img src="http://1.gravatar.com/avatar/94a1fe92ae42a403272231e6a67a9d49?s=48&d=identicon&r=G" />
let $replacements :=
<replacements>
<replacement>
<path>{non-ob:get-path($xhtml//html:h1[@id="header"]/html:a)}</path>
<text>{$post-title}</text>
</replacement>
<replacement>
<path>{non-ob:get-path($xhtml//html:div[@class="meta"])}</path>
<element>{$image-el}</element>
</replacement>
<replacement>
<path>{non-ob:get-path($xhtml//html:h3[@class="storytitle"]/html:a)}</path>
<attr name="href">http://marklogic.com</attr>
</replacement>
</replacements>
let $xhtml := non-ob:replace($xhtml, $replacements)
return $xhtml
Some Notes
In this example the HTML is pretty convoluted, but I just wanted to show that this approach is even more advantageous the more complicated the HTML is. The HTML code can be changed on the filesystem in pretty much any way without the xqy code needing to change. We also could have put the HTML in the database rather than the filesystem. We also could have retrieved the HTML in real-time from a live HTML server rather than saving the HTML locally. The XQuery code stays pretty lean, only containing the replacements.
The non-ob approach can not be done in Java in a performant way. The memory requirement processing XML would just be prohibited of doing a real-time HTML replacement. XSLT may be a better way to do the replacement instead of XQuery. When MarkLogic 4.2 comes out I’ll try it out.
Hi Ryan,
Didn’t get a chance to see you during the conference, but heard the talk went well. Thanks for a great post!
JD
Thanks JD. I attended your session at the very end along with several members of my team and it has been frequently discussed. Defining requirements with the customer is a big area improvement for us. Thanks for the info.
Hi Ryan,
Have you had a chance to try this with XSLT yet?
Kelly
Not yet. Been heads down on a project trying to hit a deadline. In a week or so things should ease off and hopefully I can do some more tinkering.
o9HmIA http://gdjI3b7VaWpU1m0dGpvjRrcu9Fk.com
Ryan,
Great approach! Does this approach also handle cases where you need some control flow, such as displaying a list of items based on a sequence?
Damon
What’s the site where you used this technique?
I came here to work http://rosydodyjy.de.tl zuzka model I love how deeply he sniffs that last chicks ass, but what I love even more is how she WANTS him to do it! I wish a girl would tell me to sniff her ass before I eat it
I think that is one of the such a lot significant info for me. And i’m satisfied reading your article. But should commentary on few general issues, The web site style is wonderful, the articles is actually excellent : D. Good task, cheers