Web-based XML Editing with W3C XML Schema and XSLT, Part 2
Pages: 1, 2, 3
The first thing we need to do is to extend our MetaXSLGUI which was presented in the previous article, so that the generated XSLGUI knows where and when to generate plus signs. The following is what our XSLGUI would look like after the extension:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
...
<xsl:template match="course">
<xsl:param name="path"/>
<xsl:variable name="index">
<xsl:number count="course"/>
</xsl:variable>
<xsl:variable name="maxOccurs" select="3"/>
<xsl:variable name="nodeCount"
select="count(../course)"/>
<b>course </b>
<xsl:if test="$nodeCount < $maxOccurs">
<a>
<xsl:attribute name="href">
<xsl:value-of
select="concat('javascript:submitForm(',
"'",$path,
'/course[', $index,']',
"'", ',',
"'",'insert',
"'",');')"/>
</xsl:attribute>
<xsl:attribute name="onClick">
<xsl:value-of
select="concat('submitForm(',"'",
$path,'/course[', $index,
']', "'", ',',
"'", 'insert',
"'", ');
return false;')"/>
</xsl:attribute>
+
</a>
</xsl:if>
<xsl:apply-templates select="*">
<xsl:with-param name="path"
select="concat($path,'/course[', $index,']')"/>
</xsl:apply-templates>
</xsl:template>
...
<xsl:template match="phone">
<xsl:param name="path"/>
<xsl:variable name="index">
<xsl:number count="phone"/>
</xsl:variable>
<xsl:variable name="maxOccurs" select="3"/>
<xsl:variable name="nodeCount"
select="count(../phone)"/>
<b>phone: </b>
<!-- The output of the following will look like
this: <input name="/person/phone[2]"
value="0630458920"/>
-->
<input>
<xsl:attribute name="name">
<xsl:value-of
select="concat($path,'/phone[',
$index,']')"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="text()"/>
</xsl:attribute>
</input>
<xsl:if test="$nodeCount < $maxOccurs">
<!-- The output of the following will look
like:
<a href="javascript:submitForm(
'/person/phone[2]',
'insert');"
onClick="submitForm('
/person/phone[2]',
'insert');
return false;">
+</a>
-->
<a>
<xsl:attribute name="href">
<xsl:value-of
select="concat('javascript:submitForm(',
"'",$path,
'/phone[', $index, ']',
"'", ',',
"'", 'insert',
"'", ');')"/>
</xsl:attribute>
<xsl:attribute name="onClick">
<xsl:value-of
select="concat('submitForm(',"'",
$path,'/phone[', $index,']',
"'", ',',
"'", 'insert',
"'",');
return false;')"/>
</xsl:attribute>
+
</a>
</xsl:if>
</xsl:template>
...
</xsl:stylesheet>
Here is the extended MetaXSLGUI.
The value of the maxOccurs attribute in the XSD is passed
to the template of the element. On every match the number of elements in
that position in the document is counted and set as
nodeCount. Then a check is done to see if
nodeCount is smaller than the maxOccurs of the
element. If that is the case the element will get a plus sign. The plus
sign is then a link which submits our form with the appropriate
parameters. (We could use the same trick to add minus signs to remove
elements from the instance document.)
When the user clicks on the plus sign next to the second course, a
request will be sent to the server with the XPath position of the element
for which an insert is requested. It is possible to insert the element
before or after the element with the plus sign. In this example we insert
the element before the element with the plus sign. Using the XPath
notation we know that the new course element has to be inserted before the
second course in the person element. The server
makes an XUpdate document with an insert-before command:
<?xml version="1.0" encoding="UTF-8"?>
<xu:modifications
xmlns:xu="http://www.xmldb.org/xupdate">
<xu:insert-before select="/person/course[2]">
<insert-course/>
</xu:insert-before>
</xu:modifications>
We don't insert the course element itself but, rather, an
insert-course element instead. The reason for that is because
at the server side we do not have any knowledge of the structure of the
element that has to be inserted. All we know from the incoming request is
that the user requests to insert a course element before the
second course element (/person/phone[2]).
This XUpdate document will be executed on a temporary copy of the original (Marc.xml) instance document. Now we have an XML document (labeled as XML+) with an additional tag which is not defined in our XSD but is merely meant as a temporary help tag:
<?xml version="1.0" encoding="UTF-8"?>
<person
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Person.xsd">
<name>Marc</name>
<phone>0153816546</phone>
<phone>0630458920</phone>
<date_of_birth>1978-01-21</date_of_birth>
<course>
<course_name>TCP/IP</course_name>
<course_code>T465</course_code>
</course>
<insert-course/>
<course>
<course_name>Java Programming</course_name>
<course_code>J867</course_code>
</course>
</person>
See the following figure.

Figure 2. Inserting elements
To be able to transform this XML+ to our XML document, we have to have
an XSLT which knows how to substitute an
<insert-elementName/> tag with the element tag it
should really be. This XSLT (called XSL+) is created using the XSD. In
this XSLT for every element of the XSD the corresponding match is created
with the element(s) which has to substitute the insert-tag in XML+. The
XSL+ has the function of substituting the insert tag while keeping the
original structure and values of the elements intact
The following shows parts of the XSL+:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="person">
<xsl:element name="person">
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
...
<xsl:template match="insert-phone">
<xsl:element name="phone"/>
</xsl:template>
<xsl:template match="insert-course">
<xsl:element name="course">
<xsl:element name="course_name"/>
</xsl:element>
</xsl:template>
<xsl:template match="insert-person">
<xsl:element name="person">
<xsl:element name="name"/>
<xsl:element name="date_of_birth"/>
<xsl:element name="phone"/>
</xsl:element>
</xsl:template>
...
</xsl:stylesheet>
The XML+ with the insert-course tag will be transformed to
a valid instance document which is the same as the original XML document
plus a course element. Marc.xml would now look like:
<?xml version="1.0" encoding="UTF-8"?>
<person
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Person.xsd">
<name>Marc</name>
<phone>0153816546</phone>
<phone>0630458920</phone>
<date_of_birth>1978-01-21</date_of_birth>
<course>
<course_name>TCP/IP</course_name>
<course_code>T465</course_code>
</course>
<course>
<course_name/>
</course>
<course>
<course_name>
Java Programming
</course_name>
<course_code>
J867
</course_code>
</course>
</person>
Only the course_name is added into the course
element. This is because course_name has a minOccurs of 1 (by
default) and course_code has a minOccurs of 0, which means
that it is not a mandatory element. After validation this XML document can be transformed
into a GUI using our XSLGUI, just like any other instance
document.