Difference between revisions of "XmlWiki"

From Organic Design wiki
m
 
(26 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<?php
+
{{legacy}}__NOTOC__
# xmlWiki - MediaWiki XML Hack
+
The [[Wikipedia:Wiki|Wiki]] concept has been invaluable to [[About The Project|the project]] by increasing the ability to collaborate and organise information effectively but without separating the developers from those working on more general content.
# Nad - 2005-05-18
 
  
# INIT
+
The [[Wikipedia:Mediawiki|Mediawiki]] software has proven to be an excellent environment for developing these new directions and making them readily accessible to anyone. These extentions come in the form of a main script called [[xmlwiki.php]] and a number of other transforms. All these are articles in the wiki which the project developers (members of the [[Group:Dev|dev]] group) can discuss and develop together like any of the other articles and documents.
# SECURITY
 
# INPUT HOOK
 
# OUTPUT HOOK
 
# PARSER HOOK
 
# SKIN HOOK
 
# ARTICLE FUNCTIONS
 
# UTILITY FUNCTIONS
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
All this development effort is slowly but surely moving in the direction of [[peerd]] which is a new [[Wikipedia:P2P|peer-to-peer]] Wiki system which integrates with and supports the current Wiki paradigm while also extending it into our new distributed space based on the [[Nodal Model]].
  
# TODO:
+
== Technical Details ==
# - Undomificate: validation, xslt
+
Articles in xmlWiki work just like the normal MediaWiki articles except that they can have an associated "properties" article named ''xml:Article'', the contents of which is xml. This ''properties'' article defines rendering transforms, permissions, publishing etc for its "parent" article. XML articles are represented as a DOM object at runtime, and the content will be validated against any referenced DTD or xml-schema. If any XSLT's are referenced, xmlWiki will attempt to apply them to the article. If the XSLT-output-method is "html" then it will be reduced to a string.
# - Transform: check perms on php-execution
 
# Publish:
 
# - xml:article - holds article publish-list
 
# - mirror/wiki-sync
 
# Transforms:
 
# - xml:article - now transforms are in here (main article is now like <body>)
 
# - so we don't do the "its one of ours" thing anymore
 
  
# LATER:
+
The names of posted form data, or query-string data can contain XPath queries to direct the information into the artiles' properties-DOM before rendering (but after permissions!)
# - allow XIncludes to build large docs from others
 
# - allow docBook and DSSSL content
 
# - Maybe only use XML transforms and use PHP via PI's
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
== How does it tie in with MediaWiki? ==
# INIT
+
The main script [[xmlwiki.php]], and its many supporting transforms are all articles in the wiki itself, so development and collaboration on XmlWiki is made very simple. There is also a number of files in the /wiki/xmlwiki directory containing some images and libraries required by XmlWiki and some of its components.
 +
Since XmlWiki requires some hooks which are not available in the standard imstallation, an admin script is also supplied in the xmlwiki directory to apply or remove the hooks. Most of the hooks are into the MediaWiki ''index.php'' file and one of them into ''parser.php''. Note that if XmlWiki is disabled then the security layer it provides is also disabled, so the xmlwiki directory should be accessible only by admin.
  
# Exit if not included from index.php
+
Two pseudo-namespaces have been added "xml" and "sys". These are both XML articles, and their contents affects the article they're associated with. Any sys:article is readable and writable only by users in the "admin" group. Pseudo namespaces have been used rather than using the official namespace paradigm because they have to apply to articles regardless of their namespace.
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
+
*The sys:user-article holds security information: <groups>a,b,c</groups>
 +
*The xml:article (''properties'') contains information pertaining to the content like transforms, publishing settings, data-sources and queries, and permissions.
 +
*sys:user articles are of docType ''xmlwiki:system'', and xml:articles are ''xmlwiki:properties''.  
  
# Allows Geshi to be installed by PARSER-HOOK if not installed th usual way already
+
== The XmlWiki Environment ==
if (!@is_object($wgGeshiSyntaxSettings)) $wgGeshiSyntaxSettings = false;
+
XmlWiki uses these features to create a more flexible wiki environment offering content, template and transform management... The following tranform articles form the XmlWIki environment:
  
# Otherwise set up xmlWiki global environment ready for input and output processing
+
*[[XmlWiki Transforms|XmlWiki Transforms]]
$xwMessages = array();
+
*[[The Properties Object]]
$xwEdit = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'edit');
+
*[[The PHP Transform Environment]]
$xwView = (!isset($_REQUEST['action']) || (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'view')));
 
$xwRaw = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'raw');
 
$xwPreview = isset($_REQUEST['wpPreview']);
 
$xwSave = isset($_REQUEST['wpSave']);
 
$xwUserName = $wgUser->mName;
 
$xwUserGroups = array(strtolower($xwUserName), 'anyone', 'everyone', 'nobody', 'world');
 
$xwArticleReadableBy = array('anyone');
 
$xwArticleWritableBy = array('anyone');
 
$xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false);
 
$xwArticleTitle = $wgTitle->getPrefixedURL();
 
$xwArticleCache = array();
 
$xwArticleLinks = array();
 
$xwUserLinks = array();
 
$xwMetaArticle = false;
 
$xwMsgToken = '<!--xwMsg-->';
 
$xwTemplate = null;
 
$xwParserHookCalled = false;
 
$xwSkinHookCalled = false;
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
== XmlWiki Hooks ==
# SECURITY
+
XmlWiki consists of five hooks which attach into the MediaWiki environment through ''/wiki/index.php'' and ''/wiki/includes/parser.php'', so those two files need to be writable by the admin script (''/wiki/xmlwiki/index.php''). The hooks now work in accord with the official [http://meta.wikimedia.org/wiki/MediaWiki_Hooks_and_their_Parameters MediaWiki Hooks] paradigm, except for the SKIN-HOOK which uses an extra ''dummy'' skin called ''xwSkin'' in the /wiki/skins directory. Following is a brief description of each hook and a list of the processes executed in each.
  
# Not currently used: R/W by user, for xmlWiki prefs etc
+
=== INIT: ===
if ($xwUserXML = xwArticleContent(strtolower("xml:$xwUserName")))
+
This hooks into ''index.php'' after the environment has been established by ''DefaultSettings.php'' and ''LocalSettings.php'', but before any article or input processing occurs. This hook is actually the initial retreival and execution of the ''xmlwiki.php'' article.
xwDomificateArticle($xwUserXML, "xml:$xwUserName");
+
*Modify MediaWiki environment defined by ''DefaultSettings.php'' and ''LocalSettings.php''
 +
*Define XmlWiki system-globals
 +
*Define XmlWiki user-globals
 +
*Define XmlWiki article-globals
 +
*Retreive ''sys:user'' and user-prefs
 +
*Retreive article-properties and [[default-properties.xml|default-properties]] and merge
 +
*Security (Process permissions for this article and deny access if not readable)
  
# R/W by admin only, holds users groups and other system info like stats
+
=== INPUT: ===
# - no sys:user means "anyone" in group "everyone" as far as perms goes
+
This hooks into ''index.php'' before input processing so it can direct any XPath inputs into the article-DOM and transform them into a standard input for xmlWiki. This also handles write-permissions. This could probably be changed to the official ''ArticleSaveComplete'' Hook.
if ($xwUserSYS = xwArticleContent(strtolower("sys:$xwUserName")))
+
*Security (If not writable, change ''action'' to "view")
xwDomificateArticle($xwUserSYS, "sys:$xwUserName");
+
*Apply any ''POST''ed ''XPath''-queries to article-properties
 +
*If saving and content is XML, apply XML-validation and change ''action'' to "edit" if invalid
  
# If sys:user exists, get users' group-list
+
=== OUTPUT: ===
if (is_object($xwUserSYS)) {
+
This replaces the output rendering in ''index.php''. It handles read permissions and transforms the xwskin-generated DOM-output with ''default-skin.xslt''.
$xwRoot = $xwUserSYS->document_element();
+
*Activate the ''xwSkin'' and request wiki-output
if (count($xwList = $xwRoot->get_elements_by_tagname('groups')))
+
*If article has just been saved, apply ''onChange'' transforms
$xwUserGroups = array_merge($xwUserGroups, split(',', strtolower($xwList[0]->get_content())));
+
*If editing a new ''sys:user'' or ''xml:article'', fill text-input with default content
}
+
*Insert any pending messages into output
 +
*Return final result to browser
  
# - Security info is: owner, read-groups, write-groups (default values are "world")
+
=== PARSER: ===
# - Users and groups are interchangeable
+
This hooks into the parse-function in ''includes/Parser.php''. If the article is  XML, it is validated and domificated. If it includes XSLT references, or has a transform-list in its ''xml:article'' then those transforms are also applied here. The article will be back in string form again after this.
# - Default user is "anyone", default group is "everyone"
+
*Check if content being parsed is the article, and is readable
if (eregi('^xml:(.+)$', $xwArticleTitle)) {
+
*Apply ''data-transforms'' (article is a string after these are done)
# This is an xml-pseudo-namespace
+
*Returns boolean determining if normal parsing should still occur or not.
# - its perms apply to its 'parent' and to itself aswell
 
$xwXML = $xwArticle;
 
xwDomificateArticle($xwXML, $xwArticleTitle);
 
if (is_object($xwXML)) {
 
$xwRoot = $xwXML->document_element();
 
if (count($xwList = $xwRoot->get_elements_by_tagname('read')))
 
$xwArticleReadableBy = split(',', strtolower($xwList[0]->get_content()));
 
if (count($xwList = $xwRoot->get_elements_by_tagname('write')))
 
$xwArticleWritableBy = split(',', strtolower($xwList[0]->get_content()));
 
}
 
}
 
elseif (eregi('^sys:(.+)$', $xwArticleTitle)) {
 
# This is a sys-pseudo-namespace
 
# - only admin can read/write sys:
 
$xwArticleReadableBy = $xwArticleWritableBy = array('admin');
 
}
 
else {
 
# This is a normal article, get xml:article
 
if ($xwArticleXML = xwArticleContent(strtolower("xml:$xwArticleTitle")))
 
xwDomificateArticle($xwArticleXML, "xml:$xwArticleTitle");
 
if (is_object($xwArticleXML)) {
 
$xwRoot = $xwArticleXML->document_element();
 
if (count($xwList = $xwRoot->get_elements_by_tagname('read')))
 
$xwArticleReadableBy = split(',', $xwList = strtolower($xwList[0]->get_content()));
 
if (count($xwList = $xwRoot->get_elements_by_tagname('write')))
 
$xwArticleWritableBy = split(',', $xwList = strtolower($xwList[0]->get_content()));
 
}
 
}
 
  
# Set perms for this request
+
=== SKIN: ===
$xwReadable = 0 < count(array_intersect($xwArticleReadableBy, $xwUserGroups));
+
This is a new compulsory skin (''skins/xwskin.php'') which renders the page output as a DOM-object so xmlWiki can apply design transforms to it rather than PHP-based templates. If ''raw'' content is requested, then only the articles own transforms are applied, because skins and page-layout are part of the xmlWiki environment, not the article.
$xwWritable = 0 < count(array_intersect($xwArticleWritableBy, $xwUserGroups));
+
*Apply ''view-transforms''
if ($xwDebug) {
 
xwMessage('Groups: '.join(', ', $xwUserGroups));
 
xwMessage("Readable ($xwReadable): ".join(', ', $xwArticleReadableBy));
 
xwMessage("Writable ($xwWritable): ".join(', ', $xwArticleWritableBy));
 
}
 
  
# Divert to access-denied article if not readable
+
=== Required Articles: ===
if (!$xwReadable) {
+
''Before installing xmlWiki, the following articles should be present on the target MediaWiki. Later the admin script will install these if not present.''
$action = 'view';
+
*[[xmlwiki.php]] and [[xml:xmlwiki.php]]
$xwSave = $xwEdit = false;
+
*[[default-properties.xml]]
die('Sorry, the requested article was not readable.');
+
*[[document.php]] and [[xml:Document.php]]
}
+
*[[geshi.php]] and [[xml:Geshi.php]]
 +
*[[export.php]] and [[xml:Export.php]]
 +
*[[default-skin.php]] and [[xml:Default-skin.php]]
 +
*[[default-skin.css]]
 +
*[[default-skin.xslt]]
  
 +
=== Other Required Scripts: ===
 +
''A number of other support files ae required which should all be in '''/wiki/xmlwiki/'''''
 +
*[[xwadmin.php|xmlWiki admin script]] (installs and uninstalls the hooks)
 +
*[[xwskin.php|xmlWiki skin-hook]]
 +
#Article Properties
 +
'''<read>'''''User|group'',''User|group'',''User|group...'''''</read>'''
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
'''<write>'''''User|group'',''User|group'',''User|group...'''''</write>'''
# INPUT HOOK
 
  
# Parse and process input from forms
+
Read and write elements contain a comma-separated list defining access permissions for the article. Note that users have upper-case and groups are lower-case. Assigning group membership for users is set by admin in the users' ''sys:User'' page.
function xwInputHook() {
 
  
global $xwArticle, $xwArticleXML, $xwArticleTitle, $xwWritable, $_REQUEST;
+
== Transform Stacks: ==
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle;
+
''(data, view, edit, save)''
if ($xwDebug) xwMessage('xwInputHook()','green');
+
There can be any number of each of these four kinds of element. Each one contains a single transform ''(*.php, *.xslt or *.css)''. Each time one is applied, its element is removed from the ''properties'' object until there are none left. It's done this way so that transforms can push more transforms onto the stack while they execute.
 +
*If a transform is PHP it is executed, but for security reasons it will only execute if it is writable only by ''dev'' or ''admin''.
 +
*If the transform is XML and is a valid ''xmlwiki:properties'' document, then it will be merged with the current ''properties'' object.
 +
*If the transform is a CSS it is output as a stylesheet link in the document head with any other stylesheets such as [[default-skin.css]].
 +
*If it is an XSLT, then it will be applied immediately if the article is XML, or otherwise will be included as a reference in the output like a CSS.
  
# If not writable, change action to view
+
Following is some more details on each of the four transform stacks.
if (!$xwWritable && ($action != 'view')) {
 
xwMessage('Sorry, article not writable, action changed to "view".', 'red');
 
$action = 'view';
 
$xwSave = $xwEdit = false;
 
}
 
  
# Parse POST for XPath inputs
+
=== &lt;data> ===
# - Form-input-name is "xw:[[@]node]:XPathQuery" which directs the value into xwArticle (if DOM)
+
The transforms are applied in the order they appear in the properties object. The ''data'' transforms are applied before any wiki-parsing occurs, so they can generate wiki-markup if they need to. The ''data'' transforms are intended to apply to queries, filters and data sources of the article content.
# - if "node" exists, then a new element (or att if "@node") is created for the value
 
# - hidden-inputs can direct input-handlers into the transform list
 
foreach ($_REQUEST as $k => $v) {
 
if (is_object($xwArticleXML) && preg_match("/^xw:(@?)(\\w*):(.+)$/", $k, $m)) {
 
list(, $att, $node, $xpath) = $m;
 
foreach (xwXPathQuery($xwArticleXML, $xpath) as $result)
 
if ($node) {
 
# create new element/attribute in result-node
 
if ($att) $node = $xwArticleXML->create_attribute($node, $v);
 
else {
 
$node = $xwArticleXML->create_element($node);
 
$node->set_content($v);
 
}
 
$result->append_child($node);
 
}
 
else $result->set_content($v); # node not set, just replace current-result-value
 
}
 
}
 
  
# if it's a pseudo-namespace...
+
Commonly used data-transforms will be for database-querying which makes the article content into the result of the query. This resultant list then gets parsed and converted to html, then is processed by any ''view'' transforms.
if (preg_match('/^(xml)|(sys):/i', $xwArticleTitle)) {
 
 
# wpSave should be valid XML to be saved
 
if ($xwSave) {
 
xwDomificate($wpSave);
 
if (!is_object($wpSave)) {
 
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
 
$action='edit';
 
}
 
}
 
 
}
 
  
}
+
=== &lt;view> ===
 +
This is a transform stack which works in exactly the same way as the ''data'' transforms, except that they're all applied after the wiki parser has applied its markup rules and converted the article to html. These are intended to be dedicated to content presentation such as layout, language and style.
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
=== &lt;edit> ===
# OUTPUT HOOK
+
The transforms in the stack formed by the ''edit'' elements of an articles ''properties'' object are very similar to the ''view'' transforms in that they are applied after the wiki parser and apply to layout, language and style. The difference is simply that the ''edit'' transforms apply instead of the view transforms if the page is being edited. These transforms are intended to allow specific kinds of articles to customise their input forms.
  
# Generate output to send back to client
+
=== &lt;save> ===
# - if article is not XML, then wiki's parsed content is used
+
The last of the transform stacks; these are applied after the article has just been saved. One of these transforms which will be commonly used is the [[publish.php]] transform.
function xwOutputHook() {
 
  
global $wgOut, $wgUser, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug;
+
=== &lt;publish> ===
global $xwArticle, $xwArticleXML, $xwArticleTitle, $xwReadable, $xwWritable;
+
<publish username="..." password="...">'''''destination URL'''''</publish>'''
global $xwView, $xwPreview, $xwSave, $xwRaw, $xwUserLinks, $xwArticleLinks;
 
if ($xwDebug) xwMessage('xwOutputHook()','green');
 
  
# Generate output page
+
A transform can use its name as the root-element(s) of its data in the properties object. There can be any number of ''publish'' elements each specifying a remote destination for the article to be published to whenever it changes.
$wgUser->setOption('skin', 'xwskin');
 
$wgOut->output();
 
  
# NOTE - Why not move this into INPUT-HOOK???????? - or maybe not....?
+
The ''publish'' elements are only checked if the publish(.php) transform is included in the ''save'' list.
# If saving, check the publishing list
 
if ($xwSave) {
 
 
# Check publish-list and process if any
 
if (false) {
 
xwMessage('Publishing...');
 
# Publish each item
 
foreach ($xwArticlePublish as $url) {
 
if ($FH = fopen($url, 'a')) {
 
# ftp example: ftp://user:password@example.com/file.txt
 
fwrite($FH, $xwArticle);
 
fclose($FH);
 
xwMessage("Article published to $url");
 
}
 
else xwMessage("Failed to publish to $url", 'red');
 
}
 
}
 
 
# Check mirror-list and process if any
 
# - allows articles in separate wiki's to be syncronised
 
# - does rudimentary security by sending a salt and response being a hash of the password generated from the salt
 
# - this could be done for login too using ECMA-MD5 on browser
 
if (false) {
 
xwMessage('Mirroring...');
 
}
 
}
 
  
 +
== XPath queries ==
 +
The names of posted form data, or query-string data can contain XPath queries to direct the information into the articles' properties-DOM.
  
# Insert messages into final html and output
+
The syntax of these names is:
if ($xwDebug) {
+
<pre>xpath:''query'':[[@]''new-node''][+]=''value''</pre>
if (!$xwParserHookCalled) xwMessage('PARSER-HOOK was not called','green');
+
where ''new-node'' is the name of a new element (or attribute if @name used) to create in the query result nodes. If no node name is specified the value overwrites (or appends if "+" included) the current result-node contents.
if (!$xwSkinHookCalled) xwMessage('SKIN-HOOK was not called','green');
 
if (is_object($xwArticleXML)) xwMessage($xwArticleXML->dump_mem(true),'purple');
 
}
 
xwAddMessages();
 
print $xwArticle;
 
}
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
XPath queries allow the rendered articles to include forms which can direct information and requests back into the articles' properties to direct further processing.
# PARSER HOOK
 
 
 
function xwParserHook(&$text) {
 
 
 
global $xwRaw, $xwParserHookCalled, $xwDebug, $xwArticleTitle, $xwArticle;
 
if ($xwDebug) xwMessage('xwParserHook(TEXT)','green');
 
$xwParserHookCalled = true;
 
$transformed = false;
 
 
 
# If this text-fragment is our article, apply transforms
 
if ((strncmp($text, $xwArticle, 100) == 0) || (strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0)) {
 
if ($xwDebug) xwMessage("PARSER-HOOK: \"$xwArticleTitle\" text-fragment recognised and intercepted.");
 
$xwArticle = $text; # needed because xwTransform works on global article
 
# If attempted xml encountered, try to domificate and transform
 
if (preg_match("/^<\\?xml/i", $xwArticle)) {
 
$transformed = true;
 
xwDomificateArticle($xwArticle, 'ParserHook/text');
 
xwTransformArticle();
 
}
 
# If not a 'raw' request, try some Geshi on
 
if (!$xwRaw) {
 
global $wgGeshiSyntaxSettings;
 
if (!is_object($wgGeshiSyntaxSettings)) require_once('extensions/geshi/geshi.php');
 
if ($lang = xwArticleType($xwArticleTitle, $xwArticle)) {
 
if ($xwDebug) xwMessage("Geshi: This is \"$lang\".");
 
# Replace tabs with spaces (better than geshi)
 
$xwArticle = preg_replace_callback("/^(.*?)(\\t+)/m", 'xwReplaceTabs', $xwArticle);
 
# Set up a geshi-parser
 
$geshi = new GeSHi($xwArticle, $lang == 'xslt' ? 'xml' : $lang, 'extensions/geshi/geshi');
 
$geshi->set_header_type(GESHI_HEADER_DIV);
 
#$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
 
#@$geshi->set_line_style('color:#aaaaaa');
 
#@$geshi->set_code_style('color:black');
 
@$geshi->set_keyword_group_style(1, 'color:blue', true);
 
@$geshi->set_keyword_group_style(2, 'color:blue', true);
 
@$geshi->set_keyword_group_style(3, 'color:blue', true);
 
@$geshi->set_comments_style(1,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(2,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(3,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_escape_characters_style('color:red',true);
 
@$geshi->set_brackets_style('color:black',true);
 
@$geshi->set_strings_style('color:#008080',true);
 
@$geshi->set_numbers_style('color:black',true);
 
@$geshi->set_methods_style('color:black',true);
 
@$geshi->set_symbols_style('color:black',true);
 
@$geshi->set_regexps_style('color:green',true);
 
# Do the parse
 
$xwArticle = preg_replace("/^<div.*?>/", '<div class="xwcode">', $geshi->parse_code());
 
$transformed = true;
 
}
 
elseif ($xwDebug) xwMessage('Geshi: Not my problem.');
 
}
 
$text = $xwArticle;
 
}
 
elseif ($xwDebug) xwMessage('Parsing other content');
 
 
# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours
 
return $transformed;
 
}
 
 
# Converts tabs to spaces better than geshi
 
function xwReplaceTabs($m) { return $m[1].str_repeat(' ',strlen($m[2])*4-strlen($m[1])%4); }
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# SKIN HOOK
 
 
 
# If attempted xml encountered, try to domificate and transform
 
# - Raw requests don't get here
 
function xwSkinHook(&$tmpl) {
 
 
 
global $xwArticle, $xwTemplate, $xwDebug, $xwSkinHookCalled;
 
if ($xwDebug) xwMessage('xwSkinHook(TEMPLATE)','green');
 
$xwSkinHookCalled = true;
 
$xwTemplate = $tmpl;
 
$xwArticle = $xwTemplate->data['bodytext'];
 
#$xslt = xwArticleContent('xmlwiki.xslt');
 
 
# Do xmlWiki page-layout transform
 
 
 
xwTransformPageLayout(); # should be: xwApplyXSLT($xwArticle, $xslt, 'xmlwiki.xslt');
 
 
 
}
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ARTICLE FUNCTIONS
 
 
 
# Retreive wiki-article as raw text
 
function xwArticleContent($articleTitle) {
 
# Return with results if already cached
 
global $xwArticleCache, $xwDebug;
 
if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue');
 
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
 
# Get wiki article content (or use global if no article passed)
 
# - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads
 
if ($article = new Article(Title::newFromText($articleTitle)))
 
$article = $article->getContentWithoutUsingSoManyDamnGlobals();
 
if ($article == '(There is currently no text in this page)') return false;
 
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
 
return $article;
 
}
 
 
 
# Decide kind of article from content and title
 
function xwArticleType($title, $article) {
 
# Name based matches
 
if (ereg('\\.php$', $title)) return 'php';
 
if (ereg('\\.css$', $title)) return 'css';
 
if (ereg('\\.as$', $title)) return 'actionscript';
 
if (ereg('\\.py$', $title)) return 'python';
 
if (ereg('\\.java$', $title)) return 'java';
 
if (ereg('\\.((cpp)|(h))$', $title)) return 'cpp';
 
if (ereg('\\.js$', $title)) return 'javascript';
 
if (ereg('\\.css$', $title)) return 'css';
 
# Content based matches
 
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
 
if (ereg('^<\\?xml', $article)) return 'xml';
 
if (ereg('^<\\?html', $article)) return 'html4strict';
 
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
 
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
 
}
 
 
 
# Convert passed article to a DOM object
 
# - Article is unchanged if not valid XML
 
function xwDomificateArticle(&$article, $id = '') {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwDomificateArticle(XML,$id)",'blue');
 
ob_start();
 
if ($dom = domxml_open_mem($article)) $article = $dom;
 
else {
 
# Could not convert, extract error messages from output
 
xwMessage("Failed :-(", 'red');
 
xwExtractMessages();
 
}
 
ob_end_clean();
 
}
 
 
 
# Reduce article to a string if it's a DOM object
 
# - ie if all xslt's had xml-output-method
 
function xwUndomificateArticle(&$article, $id = '') {
 
if (!is_object($article)) return false;
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwUndomificate(DOM,$id)",'blue');
 
# TODO:
 
# Validate, report errors
 
# xwApplyXSLT($article, $xslt) if xml referrs one
 
# Convert DOM back to XML if still an object
 
if (is_object($article)) $article = $article->dump_mem(true);
 
return true;
 
}
 
 
 
# Transform an article object
 
# - Article is undomificated on exit
 
# - Article remains unchanged if not DOM
 
function xwTransformArticle() {
 
global $xwDebug, $xwArticle, $xwArticleXML, $xwArticleTitle;
 
# xml:article must be an object...
 
if (is_object($xwArticleXML)) {
 
$docType = $xwArticleXML->doctype();
 
# ...and must be xmlwiki:* doctype
 
if (ereg('^xmlwiki:', $docType->name)) {
 
$root = $xwArticle->document_element();
 
if ($xwDebug) xwMessage("xwTransformArticle()",'blue');
 
# Loop thru transforms applying each
 
foreach ($root->get_elements_by_tagname('transform') as $tElement) {
 
$tName = $tElement->get_content();
 
# Transform is not local, get transform-article and apply to current-article-object
 
if ($tText = xwArticleContent($tName)) {
 
# Get transform type
 
$tType = xwArticleType($tName, $tText);
 
if ($tType == 'xslt') xwApplyXSLT($xwArticle, $tText, $tName);
 
elseif ($tType == 'php') {
 
# TODO: php, check perms and execute (trapping errors for reporting - unless fatal)
 
if ($perms_check_out_ok = true) {
 
ob_start();
 
eval("?>$tText<?");
 
xwExtractMessages();
 
ob_end_clean();
 
}
 
}
 
elseif ($tType == 'css') xwMessage("CSS's from transform-list aren't applied yet :-(", 'red');
 
}
 
else xwMessage("No such transform-article \"$tName\"!", 'red');
 
}
 
}
 
else xwMessage("Could not transform: xml:article must be doctype:xmlwiki!",'red');
 
}
 
else xwMessage("xwTransformArticle(NON-OBJECT)",'red');
 
# If article is still a DOM object, stringify it once and for all
 
if (is_object($xwArticle)) xwUndomificateArticle($xwArticle, $xwArticleTitle);
 
return true;
 
}
 
 
 
# Apply XSLT to article-dom
 
# - if output-method is html, article will get stringified
 
function xwApplyXSLT(&$article, &$xslt, $id = '') {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue');
 
ob_start();
 
if ($tObject = domxml_xslt_stylesheet($xslt)) {
 
$tResult = $tObject->process($article);
 
# If output-method is html, use XSLT's dump_mem to create an html-string
 
if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt))
 
$tResult = $tObject->result_dump_mem($tResult);
 
}
 
xwExtractMessages();
 
ob_end_clean();
 
if (isset($tResult)) $article = $tResult; else return false;
 
return true;
 
}
 
 
 
# Return result set of an XPath query on passed DOM-object
 
function xwXPathQuery(&$dom, $query) {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwXPathQuery(DOM,\"$query\")",'blue');
 
ob_start();
 
$context = $dom->xpath_new_context();
 
if ($xpath = $context->xpath_eval($query)) $result = $xpath->nodeset;
 
else {
 
xwMessage('Query Failed! :-(', 'red');
 
$result = array();
 
}
 
xwExtractMessages();
 
ob_end_clean();
 
return $result;
 
}
 
 
 
# Surrounds the article with HTML structural page-layout containing a ref to xmlwiki.css
 
# - TEMP: see skin-hook
 
function xwTransformPageLayout() {
 
 
 
global $xwMsgToken, $xwArticle, $xwTemplate, $xwDebug, $xwWritable;
 
if ($xwDebug) xwMessage("xwTransformPageLayout()",'blue');
 
$tmpl = &$xwTemplate;
 
$data = &$tmpl->data;
 
$doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ';
 
$doctype .= '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
 
$html = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="';
 
$html .= htmlspecialchars($data['lang']).'" lang="'.htmlspecialchars($data['lang']).'" dir="';
 
$html .= htmlspecialchars($data['dir']).'">';
 
 
 
# Head
 
$head = '<head><meta http-equiv="Content-Type" content="'.htmlspecialchars($data['mimetype']);
 
$head .= '; charset='.htmlspecialchars($data['charset']).'" />'.$data['headlinks'];
 
$head .= '<link rel="stylesheet" type="text/css" href="/wiki/index.php?title=xmlwiki.css&action=raw&gen=css" />';
 
 
 
$head .= '<title>'.htmlspecialchars($data['pagetitle']).'</title>';
 
$head .= '</head>';
 
 
 
# Body opening tag
 
$body = '<body ';
 
if ($data['body_ondblclick']) $body .= 'ondblclick="'.htmlspecialchars($data['body_ondblclick']).'" ';
 
if ($data['nsclass']) $body .= 'class="'.htmlspecialchars($data['nsclass']).'"';
 
$body .= '>';
 
 
 
# Content
 
$content = '<div id="column-content">';
 
$content .= '<div id="content"><a name="top" id="contentTop"></a>';
 
if ($data['sitenotice']) $content .= '<div id="siteNotice">'.$data['sitenotice'].'</div>';
 
$content .= '<h1 class="firstHeading">'.htmlspecialchars($data['title']).'</h1>';
 
$content .= '<div id="bodyContent">';
 
$content .= '<h3 id="siteSub">'.htmlspecialchars($tmpl->translator->translate('tagline')).'</h3>';
 
$content .= '<div id="contentSub">'.$data['subtitle'].'</div>';
 
if ($data['undelete']) $content .= '<div id="contentSub">'.$data['undelete'].'</div>';
 
if ($data['newtalk']) $content .= '<div class="usermessage">'.$data['newtalk'].'</div>';
 
$content .= "<!-- start content -->$xwArticle"; # <---- Actual article content inserted here
 
if ($data['catlinks']) $content .= '<div id="catlinks">'.$data['catlinks'].'</div>';
 
$content .= '<!-- end content --><div class="visualClear"></div>';
 
$content .= '</div></div></div>';
 
 
 
# Action-links (remove edit link if not writable)
 
$actions = '<div id="p-cactions" class="portlet"><h5>Views</h5><ul>';
 
foreach ($data['content_actions'] as $key => $action) {
 
#if ($key == '
 
#if (!(ereg('action=edit$', $action['href'])&&!$xwWritable)) {
 
$actions .= '<li id="ca-'.htmlspecialchars($key).'"';
 
if ($action['class']) $actions .= ' class="'.htmlspecialchars($action['class']).'"';
 
$href = '<a href="'.htmlspecialchars($action['href']).'">'.htmlspecialchars($action['text']).'</a>';
 
$actions .= ">$href</li>";
 
# }
 
}
 
$actions .= '</ul></div>';
 
 
 
# Personal-links
 
$personal = '<div class="portlet" id="p-personal">';
 
$personal .= '<h5>'.htmlspecialchars($tmpl->translator->translate('personaltools')).'</h5>';
 
$personal .= '<div class="pBody"><ul>';
 
foreach ($data['personal_urls'] as $key => $item) {
 
$personal .= '<li id="pt-'.htmlspecialchars($key).'">';
 
$href = '<a href="'.htmlspecialchars($item['href']).'"';
 
if (!empty($item['class'])) $href .= 'class="'.htmlspecialchars($item['class']).'"';
 
$href .= '>'.htmlspecialchars($item['text']).'</a>';
 
$personal .= "$href</li>";
 
}
 
$personal .= "</ul></div></div>";
 
 
 
# Logo
 
$logo = '<div class="portlet" id="p-logo">';
 
$logo .= '<a style="background-image: url('.htmlspecialchars($data['logopath']).');" ';
 
$logo .= 'href="'.htmlspecialchars($data['nav_urls']['mainpage']['href']).'" ';
 
$logo .= 'title="'.htmlspecialchars($tmpl->translator->translate('mainpage')).'"></a></div>';
 
$logo .= '<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>';
 
 
 
# Navigation
 
$nav = '<div class="portlet" id="p-nav">';
 
$nav .= '<h5>'.htmlspecialchars($tmpl->translator->translate('navigation')).'</h5>';
 
$nav .= '<div class="pBody"><ul>';
 
foreach ($data['navigation_urls'] as $navlink) {
 
$nav .= "<li id=\"".htmlspecialchars($navlink['id'])."\">";
 
$nav .= '<a href="'.htmlspecialchars($navlink['href']).'">'.htmlspecialchars($navlink['text']).'</a>';
 
$nav .= '</li>';
 
}
 
$nav .= '</ul></div></div>';
 
 
# Search
 
$search = '<div id="p-search" class="portlet">';
 
$search .= '<h5><label for="searchInput">'.htmlspecialchars($tmpl->translator->translate('search')).'</label></h5>';
 
$search .= '<div class="pBody">';
 
$search .= '<form name="searchform" action="'.htmlspecialchars($data['searchaction']).'" id="searchform">';
 
$search .= '<input id="searchInput" name="search" type="text"';
 
if ($tmpl->haveMsg('accesskey-search')) $search .= ' accesskey="';
 
$search .= htmlspecialchars($tmpl->translator->translate('accesskey-search')).'"';
 
if (isset($data['search'])) $search .= ' value="'.htmlspecialchars($data['search']).'"';
 
$search .= ' />';
 
$search .= '<input type="submit" name="go" class="searchButton" id="searchGoButton" value="';
 
$search .= htmlspecialchars($tmpl->translator->translate('go')).'" />';
 
$search .= '&nbsp;<input type="submit" name="fulltext" class="searchButton" value="';
 
$search .= htmlspecialchars($tmpl->translator->translate('search')).'" />';
 
$search .= '</form></div></div>';
 
 
# Toolbox
 
$toolbox = '<div class="portlet" id="p-tb">';
 
$toolbox .= '<h5>'.htmlspecialchars($tmpl->translator->translate('toolbox')).'</h5>';
 
$toolbox .= '<div class="pBody"><ul>';
 
if ($data['notspecialpage']) {
 
foreach (array('whatlinkshere', 'recentchangeslinked') as $special ) {
 
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
 
$toolbox .= "<li id=\"t-'$special'\">$href</li>";
 
}
 
}
 
if ($data['feeds']) {
 
$toolbox .= '<li id="feedlinks">';
 
foreach ($data['feeds'] as $key => $feed) {
 
$toolbox .= '<span id="feed-'.htmlspecialchars($key).'">';
 
$toolbox .= '<a href="'.htmlspecialchars($feed['href']).'">'.htmlspecialchars($feed['text']).'</a>';
 
$toolbox .= '&nbsp;</span>';
 
}
 
$toolbox .= '</li>';
 
}
 
foreach (array('contributions', 'emailuser', 'upload', 'specialpages') as $special ) {
 
if ($data['nav_urls'][$special]) {
 
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
 
$toolbox .= "<li id=\"t-$special\">$href</li>";
 
}
 
}
 
$toolbox .= '</ul></div></div>';
 
 
 
# Language
 
$language = '';
 
if ($data['language_urls']) {
 
$language .= '<div id="p-lang" class="portlet">';
 
$language .= '<h5>'.htmlspecialchars($tmpl->translator->translate('otherlanguages')).'</h5>';
 
$language .= '<div class="pBody"><ul>';
 
foreach ($data['language_urls'] as $langlink) {
 
$href = '<a href="'.htmlspecialchars($langlink['href']).'">'.$langlink['text'].'</a>';
 
$language .= "<li>$href</li>";
 
}
 
$language .= '</ul></div></div>';
 
}
 
 
 
# Footer
 
$footer = '<div id="footer"><ul id="f-list">';
 
if ($data['lastmod']) $footer .= '<li id="f-lastmod">'.$data['lastmod'].'</li>';
 
if ($data['viewcount']) $footer .= '<li id="f-viewcount">'.$data['viewcount'].'</li>';
 
if ($data['credits']) $footer .= '<li id="f-credits">'.$data['credits'].'</li>';
 
if ($data['copyright']) $footer .= '<li id="f-copyright">'.$data['copyright'].'</li>';
 
if ($data['about']) $footer .= '<li id="f-about">'.$data['about'].'</li>';
 
if ($data['disclaimer']) $footer .= '<li id="f-disclaimer">'.$data['disclaimer'].'</li>';
 
$footer .= '</ul></div>';
 
 
 
# Return assembled parts
 
$xwArticle = " $doctype
 
$html
 
$head
 
$body
 
<table border=\"0\" cellpadding=\"10\" cellspacing=\"1\">
 
<tr>
 
<td valign=\"top\" bgcolor=\"#cccccc\">
 
<img src=\"/wiki/xmlwiki.jpg\" />
 
$search
 
$nav
 
$toolbox
 
</td>
 
<td width=\"100%\" valign=\"top\" bgcolor=\"#cccccc\">
 
$personal$actions$xwMsgToken$content
 
</td>
 
</tr>
 
<td colspan=\"2\" bgcolor=\"#cccccc\">$footer</td>
 
<tr>
 
</table>
 
</body>
 
</html>
 
";
 
}
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# UTILITY FUNCTIONS
 
 
 
# Append messages to content
 
function xwAddMessages() {
 
global $xwArticle, $xwMessages, $xwMsgToken;
 
if (count($xwMessages)) {
 
$msg = '<div class="portlet" id="p-xwmessages"><h5>There are Messages</h5></div>';
 
$msg .= '<div class="xwmessage"><ul><li>'.join("</li><li>", $xwMessages).'</li></ul></div>';
 
$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle);
 
}
 
}
 
 
 
# Extract error-messages from captured output and put in proper message-queue
 
function xwExtractMessages() {
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
 
$err = preg_replace('/ in .+? on line.+?[0-9]+/', '', $err);
 
$err = preg_replace('/Warning.+?\\(\\): /', '', $err);
 
foreach (split("\n", $err) as $msg) if ($msg) xwMessage("$msg", 'red');
 
}
 
 
# Add message to queue
 
function xwMessage($msg, $col = '#000080') {
 
global $xwMessages;
 
$msg = htmlentities($msg);
 
return $xwMessages[] = "<font color=\"$col\">$msg</font>";
 
}
 
 
 
# Print list of passed object's methods and properties
 
function xwCheckoutObject($obj) {
 
if (is_object($obj)) {
 
print strtoupper("<br>$obj properties:<br>");
 
foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."<br>";
 
print strtoupper("<br>$obj methods:<br>");
 
foreach (get_class_methods($obj) as $k=>$v) print "$v<br>";
 
}
 
if (is_array($obj)) {
 
print strtoupper("<br>Array content:<br>");
 
foreach ($obj as $k=>$v) print "$k => $v<br>";
 
}
 
die;
 
}
 
 
?>
 

Latest revision as of 05:16, 23 July 2011

Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, now this page is for historic record only.

The Wiki concept has been invaluable to the project by increasing the ability to collaborate and organise information effectively but without separating the developers from those working on more general content.

The Mediawiki software has proven to be an excellent environment for developing these new directions and making them readily accessible to anyone. These extentions come in the form of a main script called xmlwiki.php and a number of other transforms. All these are articles in the wiki which the project developers (members of the dev group) can discuss and develop together like any of the other articles and documents.

All this development effort is slowly but surely moving in the direction of peerd which is a new peer-to-peer Wiki system which integrates with and supports the current Wiki paradigm while also extending it into our new distributed space based on the Nodal Model.

Technical Details

Articles in xmlWiki work just like the normal MediaWiki articles except that they can have an associated "properties" article named xml:Article, the contents of which is xml. This properties article defines rendering transforms, permissions, publishing etc for its "parent" article. XML articles are represented as a DOM object at runtime, and the content will be validated against any referenced DTD or xml-schema. If any XSLT's are referenced, xmlWiki will attempt to apply them to the article. If the XSLT-output-method is "html" then it will be reduced to a string.

The names of posted form data, or query-string data can contain XPath queries to direct the information into the artiles' properties-DOM before rendering (but after permissions!)

How does it tie in with MediaWiki?

The main script xmlwiki.php, and its many supporting transforms are all articles in the wiki itself, so development and collaboration on XmlWiki is made very simple. There is also a number of files in the /wiki/xmlwiki directory containing some images and libraries required by XmlWiki and some of its components. Since XmlWiki requires some hooks which are not available in the standard imstallation, an admin script is also supplied in the xmlwiki directory to apply or remove the hooks. Most of the hooks are into the MediaWiki index.php file and one of them into parser.php. Note that if XmlWiki is disabled then the security layer it provides is also disabled, so the xmlwiki directory should be accessible only by admin.

Two pseudo-namespaces have been added "xml" and "sys". These are both XML articles, and their contents affects the article they're associated with. Any sys:article is readable and writable only by users in the "admin" group. Pseudo namespaces have been used rather than using the official namespace paradigm because they have to apply to articles regardless of their namespace.

  • The sys:user-article holds security information: <groups>a,b,c</groups>
  • The xml:article (properties) contains information pertaining to the content like transforms, publishing settings, data-sources and queries, and permissions.
  • sys:user articles are of docType xmlwiki:system, and xml:articles are xmlwiki:properties.

The XmlWiki Environment

XmlWiki uses these features to create a more flexible wiki environment offering content, template and transform management... The following tranform articles form the XmlWIki environment:

XmlWiki Hooks

XmlWiki consists of five hooks which attach into the MediaWiki environment through /wiki/index.php and /wiki/includes/parser.php, so those two files need to be writable by the admin script (/wiki/xmlwiki/index.php). The hooks now work in accord with the official MediaWiki Hooks paradigm, except for the SKIN-HOOK which uses an extra dummy skin called xwSkin in the /wiki/skins directory. Following is a brief description of each hook and a list of the processes executed in each.

INIT:

This hooks into index.php after the environment has been established by DefaultSettings.php and LocalSettings.php, but before any article or input processing occurs. This hook is actually the initial retreival and execution of the xmlwiki.php article.

  • Modify MediaWiki environment defined by DefaultSettings.php and LocalSettings.php
  • Define XmlWiki system-globals
  • Define XmlWiki user-globals
  • Define XmlWiki article-globals
  • Retreive sys:user and user-prefs
  • Retreive article-properties and default-properties and merge
  • Security (Process permissions for this article and deny access if not readable)

INPUT:

This hooks into index.php before input processing so it can direct any XPath inputs into the article-DOM and transform them into a standard input for xmlWiki. This also handles write-permissions. This could probably be changed to the official ArticleSaveComplete Hook.

  • Security (If not writable, change action to "view")
  • Apply any POSTed XPath-queries to article-properties
  • If saving and content is XML, apply XML-validation and change action to "edit" if invalid

OUTPUT:

This replaces the output rendering in index.php. It handles read permissions and transforms the xwskin-generated DOM-output with default-skin.xslt.

  • Activate the xwSkin and request wiki-output
  • If article has just been saved, apply onChange transforms
  • If editing a new sys:user or xml:article, fill text-input with default content
  • Insert any pending messages into output
  • Return final result to browser

PARSER:

This hooks into the parse-function in includes/Parser.php. If the article is XML, it is validated and domificated. If it includes XSLT references, or has a transform-list in its xml:article then those transforms are also applied here. The article will be back in string form again after this.

  • Check if content being parsed is the article, and is readable
  • Apply data-transforms (article is a string after these are done)
  • Returns boolean determining if normal parsing should still occur or not.

SKIN:

This is a new compulsory skin (skins/xwskin.php) which renders the page output as a DOM-object so xmlWiki can apply design transforms to it rather than PHP-based templates. If raw content is requested, then only the articles own transforms are applied, because skins and page-layout are part of the xmlWiki environment, not the article.

  • Apply view-transforms

Required Articles:

Before installing xmlWiki, the following articles should be present on the target MediaWiki. Later the admin script will install these if not present.

Other Required Scripts:

A number of other support files ae required which should all be in /wiki/xmlwiki/

  1. Article Properties

<read>User|group,User|group,User|group...</read>

<write>User|group,User|group,User|group...</write>

Read and write elements contain a comma-separated list defining access permissions for the article. Note that users have upper-case and groups are lower-case. Assigning group membership for users is set by admin in the users' sys:User page.

Transform Stacks:

(data, view, edit, save) There can be any number of each of these four kinds of element. Each one contains a single transform (*.php, *.xslt or *.css). Each time one is applied, its element is removed from the properties object until there are none left. It's done this way so that transforms can push more transforms onto the stack while they execute.

  • If a transform is PHP it is executed, but for security reasons it will only execute if it is writable only by dev or admin.
  • If the transform is XML and is a valid xmlwiki:properties document, then it will be merged with the current properties object.
  • If the transform is a CSS it is output as a stylesheet link in the document head with any other stylesheets such as default-skin.css.
  • If it is an XSLT, then it will be applied immediately if the article is XML, or otherwise will be included as a reference in the output like a CSS.

Following is some more details on each of the four transform stacks.

<data>

The transforms are applied in the order they appear in the properties object. The data transforms are applied before any wiki-parsing occurs, so they can generate wiki-markup if they need to. The data transforms are intended to apply to queries, filters and data sources of the article content.

Commonly used data-transforms will be for database-querying which makes the article content into the result of the query. This resultant list then gets parsed and converted to html, then is processed by any view transforms.

<view>

This is a transform stack which works in exactly the same way as the data transforms, except that they're all applied after the wiki parser has applied its markup rules and converted the article to html. These are intended to be dedicated to content presentation such as layout, language and style.

<edit>

The transforms in the stack formed by the edit elements of an articles properties object are very similar to the view transforms in that they are applied after the wiki parser and apply to layout, language and style. The difference is simply that the edit transforms apply instead of the view transforms if the page is being edited. These transforms are intended to allow specific kinds of articles to customise their input forms.

<save>

The last of the transform stacks; these are applied after the article has just been saved. One of these transforms which will be commonly used is the publish.php transform.

<publish>

<publish username="..." password="...">destination URL</publish>

A transform can use its name as the root-element(s) of its data in the properties object. There can be any number of publish elements each specifying a remote destination for the article to be published to whenever it changes.

The publish elements are only checked if the publish(.php) transform is included in the save list.

XPath queries

The names of posted form data, or query-string data can contain XPath queries to direct the information into the articles' properties-DOM.

The syntax of these names is:

xpath:''query'':[[@]''new-node''][+]=''value''

where new-node is the name of a new element (or attribute if @name used) to create in the query result nodes. If no node name is specified the value overwrites (or appends if "+" included) the current result-node contents.

XPath queries allow the rendered articles to include forms which can direct information and requests back into the articles' properties to direct further processing.