I have a XML produce with MySQL Query Browser.
I'm trying to apply a XSLT to output the result into Word tables. One table for each record.
Here's a sample of my XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ROOT SYSTEM "Nessus.dtd">
<ROOT>
<row>
<field name="Niveau">Critique</field>
<field name="Name">Apache 2.2 < 2.2.15 Multiple Vulnerabilities</field>
</row>
<row>
<field name="Niveau">Critique</field>
<field name="VulnName">Microsoft Windows 2000 Unsupported Installation Detection</field>
</row>
<row>
<field name="Niveau">Haute</field>
<field name="VulnName">CGI Generic SQL Injection</field>
</row>
</ROOT>
For the XLST I've already found out that I need to do a for-each select
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="ROOT/row">
Niveau : <xsl:value-of select="????"/>
Name : <xsl:value-of select="????"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When I do this loop I see the same number of empty table as there is <row></row> in my file.
But I haven't found the way to make the right "value-of select=". I've try the following without luck.
<xsl:value-of select="#name"/>
<xsl:value-of select="name"/>
<xsl:value-of select="#row/name"/>
<xsl:value-of select="row/#name"/>
<xsl:value-of select="#ROOT/row/name"/>
And a few other that I can't remember. Any idea what I need to craft the request to get the value in my resulting file?
I've just tried with :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="ROOT/row">
Niveau : <xsl:value-of select="field/#Niveau"/>
Name : <xsl:value-of select="field/#name"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And it output this :
NIVEAU :
NAME : name
NIVEAU :
NAME : Niveau
NIVEAU :
NAME : Niveau
I would like this output :
NIVEAU : Critique
NAME : Apache 2.2 < 2.2.15 Multiple Vulnerabilities
NIVEAU : Critique
NAME : Microsoft Windows 2000 Unsupported Installation Detection
NIVEAU : Haute
NAME : CGI Generic SQL Injection
Any help would be appreciated.
Thank you.
UPDATE
Now with this XSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="row">
<xsl:text>NIVEAU : </xsl:text>
<xsl:value-of select="field[#name = 'Niveau']"/>
<xsl:text>
</xsl:text>
<xsl:text>NAME : </xsl:text>
<xsl:value-of select="field[#name = 'Name']"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I get this output :
<?xml version="1.0"?>
NIVEAU :
NAME : Apache 2.2 < 2.2.15 Multiple Vulnerabilities
NIVEAU : Critique
NAME : Microsoft Windows 2000 Unsupported Installation Detection
NIVEAU : Haute
NAME : CGI Generic SQL Injection
As you can see the first field is empty. I could honestly live with that and fill it manually but if you see why this is happenning I'd be very happy :)
UPDATE
Using <xsl:value-of select="field[#name = 'foo']"/> gave me the value I wanted. I kept the for-each as it was easier to use (for me) inside a MS Word template.
for-each is generally code smell in XSLT. You most likely want a template, not a for-each loop:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="field">
<xsl:value-of select="#name"/> : <xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
This template will produce the following output:
<root>
Niveau : Critique
name : Apache 2.2 < 2.2.15 Multiple Vulnerabilities
Niveau : Critique
name : Microsoft Windows 2000 Unsupported Installation Detection
Niveau : Haute
name : CGI Generic SQL Injection
</root>
XSLT was designed for this pattern of use--many xsl:templates matching a small part of the source and applying other templates recursively. The most important and common template in this pattern is the identity template, which copies output. This is a good tutorial on XSLT coding patterns that you should read.
Update
Below is a complete solution that will produce text output (since that is what you seem to want, not XML output). I'm not sure xsl is the best language for this, but it works....
Notice that the only place we use for-each is for sorting to determine the length of the longest name. We use templates and pattern-matching selects to make all other loops implicit.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="newline"><xsl:text>
</xsl:text></xsl:variable>
<!-- determine the maximum length of the "name" field so we can pad with spaces for all shorter items -->
<xsl:variable name="max_name_len">
<xsl:for-each select="ROOT/row/field/#name">
<xsl:sort select="string-length(.)" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="string-length(.)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- for each row, apply templates then add a blank line -->
<xsl:template match="row">
<xsl:apply-templates/>
<xsl:value-of select="$newline"/>
</xsl:template>
<!-- for each field, apply template to name and add value, followed by a newline -->
<xsl:template match="field">
<xsl:apply-templates select="#name"/> : <xsl:value-of select="concat(., $newline)"/>
</xsl:template>
<!-- for each name, uppercase and pad with spaces to the right -->
<xsl:template match="field/#name">
<xsl:call-template name="padright">
<xsl:with-param name="text">
<xsl:call-template name="toupper">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="len" select="$max_name_len"/>
</xsl:call-template>
</xsl:template>
<!-- Utility function: uppercase a string -->
<xsl:template name="toupper">
<xsl:param name="text"/>
<xsl:value-of select="translate($text, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:template>
<!-- Utility function: pad a string to desired len with spaces on the right -->
<!-- uses a recursive solution -->
<xsl:template name="padright">
<xsl:param name="text"/>
<xsl:param name="len"/>
<xsl:choose>
<xsl:when test="string-length($text) < $len">
<xsl:call-template name="padright">
<xsl:with-param name="text" select="concat($text, ' ')"/>
<xsl:with-param name="len" select="$len"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This stylesheet produces the following output:
NIVEAU : Critique
NAME : Apache 2.2 < 2.2.15 Multiple Vulnerabilities
NIVEAU : Critique
NAME : Microsoft Windows 2000 Unsupported Installation Detection
NIVEAU : Haute
NAME : CGI Generic SQL Injection
When you do <xsl:for-each select="ROOT/row">, the current context inside the loop is the rowelement. So in order to access the field name, you need to write, for example, <xsl:value-of select="field/#name"/>.
Since your XML contains several fields, you will still have to extend your XSLT file somewhat to iterate the fields, or (as Francis Avila suggested) write a template. Both methods are ok, though. The technical terms for the two approaches are "pull" and "push", respectively.
And of course, since you ultimately want to generate a word table, you will have to generate w:tr and w:tc elements, etc.
This stylesheet will give you exactly your desired output
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="row">
<xsl:text>NIVEAU : </xsl:text>
<xsl:value-of select="field[#name eq 'Niveau']"/>
<xsl:text>
</xsl:text>
<xsl:text>NAME : </xsl:text>
<xsl:value-of select="field[#name eq 'VulnName']"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
This is very specific to your input xml and output text, for example any <field> children of <row> other than 'Niveau' or 'VulnName' will be dropped from your report. A more generic solution could look like this:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="field">
<xsl:value-of select="concat(upper-case(#name),': ',.)"/>
</xsl:template>
</xsl:stylesheet>
This solution though doesn't exactly match the whitespace formatting in your desired output, but it does capture all possible fields.
Related
I have a large number of HTML (and possibly other xml) documents that I need to redact.
The redactions are typically of the form "John Doe" -> "[Person A]". The text to be redacted may be in headers or paragraphs, but will almost always be in paragraphs.
Simple string substitutions really. Not very complicated things.
However, I do want to preserve document structure, and I would prefer to not reinvent any wheels. String substitution in the document text may do the job, but also may break document structure, so it will be a last option.
Right now I have stared at XSLT for an hour and tried to force "str:replace" to do my bidding. I will spare you from viewing me feeble attempts that didn't work, but I will ask this: Is there a simple and know way to apply my redactions using XSLT, and could you post it here?
Thank you in advance.
Update: at the request of Martin Honnen I'm adding my input files, as well as the command I used to get the latest error message. From this it will be apparent that I'm a complete n00b when it comes to XSLT :-)
.html file:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>TodaysDate</title>
<meta name="created" content="2020-11-04T30:45:00"/>
</head>
<body>
<ol start="2">
<li><p> John Doe on 9. fux 2057 together with Henry
Fluebottom formed the company Doe &; Fluebottom Widgets
Inc. </p>
</ol>
</body>
</html>
The XSLT transformation file:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="p">
<xsl:copy>
<xsl:attribute name="matchesPattern">
<xsl:copy-of select='str:replace("John Doe", ".*", "[Person A]")'/>
</xsl:attribute>
<xsl:copy-of select='str:replace("Henry Fluebottom", ".*", "[Person B]")'/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The command and the output:
$ xsltproc -html transform.xsl example.html
xmlXPathCompOpEval: function replace bound to undefined prefix str
xmlXPathCompiledEval: 2 objects left on the stack.
<?xml version="1.0"?>
TodaysDate
<p matchesPattern=""/>
$
xsltproc is based on libxslt and that way supports various EXSLT functions like str:replace, to use it you will need to declare the namespace
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
exclude-result-prefixes="str"
version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p//text()">
<xsl:value-of select="str:replace(., 'John Doe', '[Person A]')"/>
</xsl:template>
</xsl:stylesheet>
There is no simple way in XSLT 1.0 to perform multiple replacements on the same string. You need to use a recursive named template, performing one replacement operation at a time, then moving to the next instance of the current find string or - when no next instance exists - to the next find/replace pair.
Consider the following example:
Input
<html>
<head>
<title>John Doe and Henry Fluebottom</title>
</head>
<body>
<p>John Doe is a person. John Doe on 9. fux 2057 together with Henry Fluebottom formed the company Doe & Fluebottom Widgets Inc. Henry Fluebottom is also a person.</p>
</body>
</html>
XSLT 1.0 (+ EXSLT node-set() function)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="dictionary">
<entry find="John Doe" replace="[Person A]"/>
<entry find="Henry Fluebottom" replace="[Person B]"/>
</xsl:variable>
<xsl:template match="text()">
<xsl:call-template name="multi-replace">
<xsl:with-param name="string" select="normalize-space(.)"/>
<xsl:with-param name="entries" select="exsl:node-set($dictionary)/entry"/>"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="multi-replace">
<xsl:param name="string"/>
<xsl:param name="entries"/>
<xsl:choose>
<xsl:when test="$entries">
<xsl:call-template name="multi-replace">
<xsl:with-param name="string">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="search-string" select="$entries[1]/#find"/>
<xsl:with-param name="replace-string" select="$entries[1]/#replace"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="entries" select="$entries[position() > 1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:param name="search-string"/>
<xsl:param name="replace-string"/>
<xsl:choose>
<xsl:when test="contains($string, $search-string)">
<xsl:value-of select="substring-before($string, $search-string)"/>
<xsl:value-of select="$replace-string"/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, $search-string)"/>
<xsl:with-param name="search-string" select="$search-string"/>
<xsl:with-param name="replace-string" select="$replace-string"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result
<html>
<head>
<title>[Person A] and [Person B]</title>
</head>
<body>
<p>[Person A] is a person. [Person A] on 9. fux 2057 together with [Person B] formed the company Doe & Fluebottom Widgets Inc. [Person B] is also a person.</p>
</body>
</html>
As you can see, this replaces all instances of the search strings anywhere in the input document (except for attributes), while preserving the document's structure.
Note that the input in your example does not actually contain the "Henry Fluebottom" search string. You might want to get around that by calling the first template with:
<xsl:with-param name="string" select="normalize-space(.)"/>
instead of:
<xsl:with-param name="string" select="."/>
The first problem is to find an XSLT processor that actually supports string replacement. The replace() function is standard in XSLT 2.0+, but does not exist in XSLT 1.0. Some XSLT 1.0 processors support an extension function str:replace() in a different namespace, but at the very least, you need to add the namespace declaration xmlns:str="http://exslt.org/strings" to your stylesheet in order to locate the function. I don't know if that will work (I don't know if there is any way of using this function with xsltproc); my advice would be to use an XSLT 2.0+ processor instead.
The next problem is the way you are invoking the function. Typically, a correct invocation would be
replace(., "John Doe", "[Person A]")
though you will have to jump through a few more hoops to make multiple replacements on the same string.
I've no idea what you are trying to achieve with the <xsl:attribute name="matchesPattern"> instruction.
I have problems with the Xpath expression test="$roles/roles/role='HOBSCS1GB'" . Can anyone help in solving. Thanks
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<xsl:variable name="roles">
<roles>
<role>HOBSCS1ROI</role>
<role>HOBSCS1GB</role>
<role>HOBSCS1FT</role>
</roles>
</xsl:variable>
<xsl:if test="$roles/roles/role='HOBSCS1GB'">
<xsl:value-of select="'YES'"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Assuming you want to find if a the roles element has one or more role elements with text = 'HOBSCS1GB': (Works in Saxon)
<xsl:if test="$roles/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
Note that certain parsers like Microsoft may require you to tell the parser that $roles is a result tree fragment, by using node-set(), like so: (Works in msxsl)
<xsl:stylesheet ... xmlns:msxsl="urn:schemas-microsoft-com:xslt" ... />
<xsl:if test="msxsl:node-set($roles)/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
Or in xsltproc:
<xsl:stylesheet ... xmlns:exsl="http://exslt.org/common" ... />
<xsl:if test="exsl:node-set($roles)/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
I want to create multiple html table pages using XML as the input and xsl as the transformation language.
Now these tables should always have a fixed height, whether it's just one row or ten.
I can't get it to work with CSS (min-height).
So I was wondering, if it is possible to get xsl to always output ten rows and add empty rows in case there are less then ten rows or adding rows in case there are more then ten rows existent in the XML and therefore resizing the table.
Any ideas how this can be achieved?
You sure can do that. I can show you how you would split your data into tables each having ten rows stuffing up the last one (or maybe the only one) with dummy rows when you don't have enough. It should help you get going where you need to go (without an example XML input and desired HTML output this is as much as I can do)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:apply-templates select="data/row[position() mod 10 = 1]" mode="newtable"/>
</xsl:template>
<xsl:template match="row" mode="newtable">
<table>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::row[position() < 10]"/>
<xsl:call-template name="dummy-rows">
<xsl:with-param
name="how-many"
select="9 - count(following-sibling::row[position() < 10])"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="row">
<tr><td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template name="dummy-rows">
<xsl:param name="how-many" select="0"/>
<xsl:if test="$how-many > 0">
<tr><td>dummy</td></tr>
<xsl:call-template name="dummy-rows">
<xsl:with-param name="how-many" select="$how-many - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The idea is that you start your table with the "first" node of each set of 10. That's the [position() mod 10 = 1] predicate. When you get a hold of the starting point of your table you create the table boundaries and process that node again in a normal mode. Then you get the next nine data rows that follow it. Finally, you add as many dummy nodes as you need to make sure you got the 10 total in each table. The dummy-rows template is a recursion. So two techniques here: splitting the set by position() mod and using a recursion to implement iteration.
UPDATE If you only need to make sure you have at least ten rows in your table then you don't need the split logic:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="data/row"/>
<xsl:call-template name="dummy-rows">
<xsl:with-param
name="how-many"
select="10 - count(data/row)"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="row">
<tr><td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template name="dummy-rows">
<xsl:param name="how-many" select="0"/>
<xsl:if test="$how-many > 0">
<tr><td>dummy</td></tr>
<xsl:call-template name="dummy-rows">
<xsl:with-param name="how-many" select="$how-many - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can try this with an input like this:
<data>
<row>1</row>
<row>1</row>
<row>3</row>
</data>
or an input like this:
<data>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
<row>5</row>
<row>6</row>
<row>7</row>
<row>8</row>
<row>9</row>
<row>10</row>
<row>11</row>
<row>12</row>
</data>
In both cases the result was as expected. Try it. You should be able to take it from here.
I am attempting to transpose a java function to an xsl:function spec.
The function basically places html tags around substrings.
I now bump into difficulties: using the java inline code this works perfectly, but I am unable to figure out how to prevent output escaping when using the xsl:function.
How can I achieve the output to contain the wanted html tags?
A simplified example of what I am trying to achieve is the following:
input parameter value "AB" should lead to a string A<b>B</b>, shown in html browser as AB of course.
Example function I tried is the below; but then the resulting string is A< ;b> ;B< ;/b> ; (note that I had to add blanks to prevent the entities from getting interpreted in this editor), which of course shows up in browers as A<b>B</b>.
Note that xsl:element cannot be used in the xsl:function code, because that has no effect; I want the string result of the function call to contain < and > characters, and then add the string result to the output result file.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:custom="http://localhost:8080/customFunctions">
<xsl:output method="html" version="4.0" encoding="UTF-8" indent="yes"/>
<xsl:function name="custom:test">
<xsl:param name="str"/>
<xsl:value-of select="substring($str,1,1)"/>
<xsl:text disable-output-escaping="yes"><![CDATA[<b>]]></xsl:text>
<xsl:value-of select="substring($str,2)"/>
<xsl:text disable-output-escaping="yes"><![CDATA[</b>]]></xsl:text>
</xsl:function>
<xsl:template match="/">
<xsl:element name="html">
<xsl:element name="body">
<xsl:value-of select="custom:test('AB')"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Here is an example, use sequence instead value-of and make sure your function returns nodes (which is usually simply done by writing literal result elements):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:custom="http://localhost:8080/customFunctions"
exclude-result-prefixes="custom">
<xsl:output method="html" version="4.0" encoding="UTF-8" indent="yes"/>
<xsl:function name="custom:test">
<xsl:param name="str"/>
<xsl:value-of select="substring($str,1,1)"/>
<b>
<xsl:value-of select="substring($str,2)"/>
</b>
</xsl:function>
<xsl:template match="/">
<xsl:element name="html">
<xsl:element name="body">
<xsl:sequence select="custom:test('AB')"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I have the following xml
<results>
<first-name>Carl<first-name>
<data><b> This is carl's data </b></data>
</results>
How do I include the bold tags which is present in the <data> tag to be a part of the output but rendered as an HTML
When I say <xsl:value-of select="results/data"/> The output is
<b> This is carl's data </b>
I want to achieve "This is carl's data" as the output in bold.
Well <xsl:copy-of select="results/data/node()"/> is a start but if the requirement is part of a larger problem then you are better off writing a template for data elements which uses apply-templates to push the child nodes through some template(s) for copying HTML elements through to the output.
I am sure someone will let me know if I am being naive:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/results">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="first-name">
<xsl:value-of select="." />
<xsl:text>: </xsl:text>
</xsl:template>
<xsl:template match="data">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="b">
<b>
<xsl:value-of select="." />
</b>
</xsl:template>
</xsl:stylesheet>