1. | Literate Programming | |||
Literate Programming is about embedding code fragments in human-readable documentation (rather than the usual reverse situation) so that the information is presented is the order which best suits people, rather than the order which best suits compilers. For more information about literate programming, see literateprogramming.com xmLP is a literate programming tool for XML, written as a set of XSL-T scripts. Version 1.0 is now available from :SourceForge | ||||
2. | Documenting Stylesheets | |||
I agree about basic template-level comments being more useful than code-level comments. The main comments that I find helpful are those concerning the assumptions that have been made in the stylesheet about the structure of the source document. For example, if you have an xsl:choose like: <xsl:choose> <xsl:when test=". = 'value1'"> ... </xsl:when> <xsl:when test=". = 'value2'"> ... </xsl:when> </xsl:choose> then it's helpful to know whether value1 and value2 are the only possible values, or if there are other possibilities that are (correctly) being ignored. Or if you have a plain xsl:apply-templates, with no select attribute, it's helpful to know what kinds of nodes you're assuming will be processed by that instruction (just descriptors like "inline elements" rather than listing them all). Or a comment that indicates the expected type of a parameter. These kinds of comments make following the flow of the stylesheet easier, and they help identify areas that might need to change when/if the source markup language changes. Other comments that I think can be helpful are those that explain complex XPath expressions in plain language. So things like: <!-- $rows holds a node set of unique row elements, by their code attribute --> <xsl:variable name="rows" select="//row[count(.|key('rows', @code)[1]) = 1]" /> XPath 2.0 allows comments *within* expressions (using the syntax {-- ... --}), which would mean you could do: <xsl:variable name="rows" select="//row[count(.|key('rows', @code)[1]) = 1] {-- select unique row elements by their code attribute --}" /> You can see why this might be necessary, given that XPath expressions will contain for and if statements... The other thing to mention is that you can go quite a long way towards making a stylesheet more readable and understandable without adding comments all over the place, just by using helpful variable names, key names, mode names, template names and so on. (P.S. I wrote an article about documentation for the "XPath & XSLT on the Edge unlimited edition" website (http://www.unlimited-edition.com/), but it doesn't seem to have appeared yet...) - April 2002 | ||||
3. | Documenting XSLT code | |||
I think that David Carlisle's stylesheet was meant to do two things: 1. demonstrate how extension elements (the ones in the doc namespace) could be used to hold documentation that would be ignored by the processor, with an *empty* xsl:fallback being used to stop processors complaining that they don't recognise the extension element (it gives them something to do instead) 2. demonstrate the effect of exclude-result-prefixes on the appearance of namespace nodes (and elements within that namespace) within the output that's generated I'm going to try to clear up my confusion by going through David's example step by step. I'm afraid this is going to be another one of my long emails - - please someone shout at me if I'm using up too much bandwidth here. David used: <xxx xmlns="http://a/b/c"> <b a="2"> <x:c xmlns:x="http:/x/y/z"/> <c>text</c> </b> </xxx> as the example of a fairly complex, but nevertheless short, XML document that used a couple of namespaces and: <?xml version="1.0" encoding="utf-8" ?> <x xmlns:y="http:/x/y/z">[xxx]</x> <x:c xmlns:x="http:/x/y/z" xmlns="http://a/b/c"/> as the (non-well-formed) output that he was after. The stylesheet included: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" extension-element-prefixes="doc" exclude-result-prefixes="main" xmlns:doc="http://doc.com" xmlns:main="http://a/b/c" xmlns:y="http:/x/y/z"> Here he declared three namespaces (in addition to the standard 'xsl'): doc - the documentation namespace main - the primary namespace, the default namespace throughout the input document y - the namespace equivalent to 'x' for the x:c element in the input Mapping those namespace declarations so that the same prefixes are used in the input gives helps make the XSLT stylesheet more understandable: <main:xxx xmlns:main="http://a/b/c"> <main:b a="2"> <y:c xmlns:y="http:/x/y/z"/> <main:c>text</main:c> </main:b> </main:xxx> The 'main' namespace is specified as a namespace to be excluded from the result. This means that the 'main' namespace will be declared as being the default namespace in the output (so wherever the result elements are in the 'main' namespace within the stylesheet, they will be included in the output without a prefix). The 'doc' namespace is specified as a namespace for extension elements. You'd normally expect those elements to do something in terms of altering what the XSLT processor does (like saxon:output). Here, we're declaring the 'doc' namespace to be a namespace for extension elements so that the XSLT processor doesn't treat them as result elements, to be outputted. David's 'doc' namespace included two elements: 'template' to give documentation on templates and 'select' to give documentation on select expressions within templates. He didn't have any particular structure within those, though you can easily imagine having structured documentation within the documentation on a template, commenting on, say, each of the parameters that it takes. In fact I'm sure someone will straightway volunteer an XML dialect for documentation of XSLT stylesheets... Anyway, within the stylesheet, then, he used the following 'design pattern' for including documentation within a template: <xsl:template match="..."> <doc:template><xsl:fallback /> <!-- documentation on the template --> </doc:template> <!-- body of the template --> </xsl:template> When an extension-element-aware (and XSLT-Recommendation-compliant) processor comes across this, it knows (because it's been told in the xsl:stylesheet) that the 'doc:template' element, being in the 'doc' namespace, is an extension element. It does a bit of introspection and finds out that it doesn't know how to support these extensions, so it gets ready to complain that it can't process the stylesheet, but first checks whether there is an xsl:fallback element within the extension element. When it finds one, it operates on the content of the xsl:fallback as if it were just a normal part of the template (here it's empty, so it does nothing), and ignores the rest of the content of the extension element. Hence the documentation is not included in the output. The fact that an XSLT processor ignores the content of extension elements like this is important because it means that you can put anything you like in it and it won't be processed. This includes any XSLT elements that you include in it. There is no point in doing: <doc:template><xsl:fallback /> This template matches <xsl:value-of select="name()" />. </doc:template> because the xsl:value-of element is never processed (by an XSLT processor that doesn't understand doc:template). David points out that the fact that an XSLT processor does process the content of the xsl:fallback element is important because it means you could use it to contain the stuff that you're actually commenting on. So, you could use: <xsl:template match="..."> <doc:template> <!-- documentation on the template --> <xsl:fallback> <!-- body of the template --> </xsl:fallback> </doc:template> </xsl:template> as the design pattern for including documentation instead. In a previous email, I've pointed out that this probably isn't a good idea just in case some XSLT processor comes along that *does* understand the 'doc:template' extension element, because then the content of the xsl:fallback element (which is the content of the template) will not be processed, and your legacy stylesheet won't work. The point, of course, of using XML to represent the documentation within the stylesheet is that you can get at it with things that understand XML, most importantly XSLT. So, you can then have another XSLT stylesheet that takes our documented XSLT stylesheet as input and produces a nice HTML frameset that explain what the stylesheet does and how it works. I'm sure whoever volunteers the XML dialect for documentation will include such a stylesheet in their contribution... What the documentation included in this way *doesn't* do, cannot do, and isn't really designed to do, is perform any run-time evaluation of the performance of the stylesheet. To do that, the documentation would need to be understandable by an XSLT processor. It would be possible to extend an XSLT processor so that it understood the extension elements in a particular namespace that was used for documentation (in this case "http://doc.com"), and did something clever with them. I'm not exactly sure what it could do other than generate messages in a similar way to xsl:message, but I'm sure there are plenty of ideas out there. And I'm sure that someone will volunteer to write extensions for SAXON that will carry them through... | ||||
4. | Documenting XSLT stylesheets | |||
(for a background on tangle, weave, David Carlisle suggests This terminology (which is pretty bad, really) was introduced by Knuth in his work on "literate programming" (and specifcally in the construction of the TeX typesetting system). There is a newsgroup just for this topic (which I haven't looked at for a while:-) but its FAQ appears to be here (among other places) FAQ ) Three files below.
Usage: 1. To extract the code (xslt processor) doc.xsl doc-logic.xsl op.html \ "logic-ns=http://www.dpawson.co.uk" \ "doc-ns=http://org.hedley.html" These are the namespaces used in doc.xsl, but use what you will. To extract the documentation. (xslt processor) doc.xsl doc-doc.xsl op.html \ "logic-ns=http://www.dpawson.co.uk" \ "doc-ns=http://org.hedley.html" To do. 1. Add coloring (preferably via CSS) to any wanted elements in doc-doc 2. Generalise to any namespaces for any purpose. Enjoy and let us know what you think. Regards, DaveP, on behalf of. 1. doc.xsl <?xml version="1.0"?> <!-- --> <!-- This is the 'source document' --> <!-- Containing both documentation and 'code' --> <logic:stylesheet xmlns:doc="http://org.hedley.html" xmlns:logic="http://www.dpawson.co.uk" > <doc:h1>Root Element</doc:h1> <doc:p>My root element doesn't really do anything useful.</doc:p> <logic:output method="xml" indent="yes"/> <doc:h3>Variable <doc:i>root</doc:i> holds the src tree</doc:h3> <logic:variable name="root" select="/"/> <logic:template match="/"> <logic:apply-templates/> </logic:template> <doc:hr/> <doc:h2>Main processing.</doc:h2> <doc:p>This template uses a pull technique to extract all required elements from the source document required by the output xml file</doc:p> <doc:p>Process all the related list section elements</doc:p> <logic:template match="doc"> <elems id="h31"> <logic:for-each select="listrelsects/listitem"> <elem><logic:copy-of select="."/></elem> </logic:for-each> </elems> </logic:template> <doc:hr/> <doc:h3>Ignore remaining elements</doc:h3> <logic:template match="*"/> </logic:stylesheet> doc-logic.xsl <?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <!-- A command line might look like this xslt-processor \ doc.xsl \ # in doc-logic.xsl \ # transform logic_ns=http://www.dpawson.co.uk \ # the logic namespace doc_ns=http://org.hedley.html # the documentation namespace e.g. sax doc.xsl doc-logic.xsl op.xsl "logic_ns=http://www.dpawson.co.uk" \ output_ns="org.hedley.html" - --> <!-- This one outputs basic code to perform the styling. - --> <xsl:param name="logic_ns" select="'***'" /> <xsl:param name="doc_ns" select="'***'" /> <xsl:variable name="xsl_ns" select="'http://www.w3.org/1999/XSL/Transform'" /> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:message>Param=<xsl:value-of select="$doc_ns"/> </xsl:message> <xsl:apply-templates/> </xsl:template> <xsl:template match="*" priority="2"> <xsl:choose> <!-- Documentation namespace, dump --> <xsl:when test="namespace-uri(.) = $doc_ns"> <xsl:message>Doc. <xsl:value-of select="namespace-uri()"/> </xsl:message> </xsl:when> <!-- output, logic namespace --> <xsl:when test="namespace-uri()=$logic_ns"> <xsl:element name="xsl:{local-name()}" namespace="{$xsl_ns}"> <xsl:copy-of select="@*"/> <xsl:apply-templates/> </xsl:element> </xsl:when> <!-- null namespace --> <xsl:when test="namespace-uri()=''"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates/> </xsl:copy> </xsl:when> <xsl:otherwise> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> 3. doc-doc.xsl - - Tks to David C for the 'hard-sums' code :-) <?xml version="1.0"?> <!-- Update 7 July 2000. Added null processing for stylesheet element, as being redundant. --> <!-- A command line might look like this sax doc.xsl doc-doc.xsl op.xml "doc_ns=http://org.hedley.html" "logic_ns=http://www.dpawson.co.uk" - --> <!DOCTYPE xsl:stylesheet [ <!ENTITY sp "<xsl:text> </xsl:text>"> <!ENTITY dot "<xsl:text>.</xsl:text>"> <!ENTITY nbsp " "> <!ENTITY nl " "><!--new line--> <!ENTITY pound "£"><!-- # POUND SIGN --> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <!-- This one outputs basic code to perform the styling. - --> <xsl:param name="logic_ns" select="'***'" /> <xsl:param name="output_ns" select="'***'" /> <xsl:param name="doc_ns" select="doc-format"/> <xsl:variable name="xsl_ns" select="'http://www.w3.org/1999/XSL/Transform'" /> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <html> <head><title>Documentation</title></head> <body> <xsl:apply-templates/> </body> </html> </xsl:template> <xsl:template match="*" priority="2"> <xsl:choose> <!-- Document Namespace. --> <xsl:when test="namespace-uri(.) = $doc_ns"> <xsl:element name="{local-name(.)}"> <xsl:copy-of select="@*" /> <xsl:apply-templates /> </xsl:element> </xsl:when> <xsl:when test="namespace-uri()=$logic_ns"> <xsl:if test= "not(local-name()='stylesheet')"> <pre> <xsl:apply-templates mode="verb" select="."/> </pre> </xsl:if> <xsl:apply-templates/> </xsl:when> <xsl:when test="namespace-uri()=''"/> <pre> <xsl:apply-templates mode="verb" /> </pre> <xsl:apply-templates /> <xsl:otherwise> <xsl:apply-templates /> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- Copyright 1999 David Carlisle Render XML in an HTML pre element. --> <!-- verb mode --> <!-- Does not really give verbatim copy of the file as that information not present in the parsed document, but should give something that renders in HTML as a well formed XML document that would parse to give same XML tree as the original - --> <!-- DaveP. Added prefix and postfix to replace logic_ns prefix with xsl: Called from both empty elements and those with content. --> <xsl:template name="prefix"> <xsl:choose> <xsl:when test="namespace-uri(.)=$logic_ns"> <xsl:value-of select="concat('<xsl:',local-name(.))"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat('<',name(.))"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="postfix"> <xsl:choose> <xsl:when test="namespace-uri(.)=$logic_ns"> <xsl:value-of select="concat('</xsl:',local-name(.),'>')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat('</',name(.),'>')"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- non empty elements and other nodes. --> <xsl:template mode="verb" match="*[*]|*[text()]|*[comment()]|*[processing-instruction()]"> <xsl:call-template name="prefix"/> <!-- <xsl:value-of select="concat('<',name(.))"/> --> <xsl:apply-templates mode="verb" select="@*"/> <xsl:text>></xsl:text> <xsl:apply-templates mode="verb"/> <xsl:call-template name="postfix"/> <!-- <xsl:value-of select="concat('</',name(.),'>')"/> --> </xsl:template> <!-- empty elements --> <xsl:template mode="verb" match="*"> <!-- <xsl:value-of select="concat('<',name(.))"/> --> <xsl:call-template name="prefix"/> <xsl:apply-templates mode="verb" select="@*"/> <xsl:text>/></xsl:text> </xsl:template> <!-- attributes Output always surrounds attribute value by " so we need to make sure no literal " appear in the value --> <xsl:template mode="verb" match="@*"> <xsl:value-of select="concat(' ',name(.),'=')"/> <xsl:text>"</xsl:text> <xsl:call-template name="string-replace"> <xsl:with-param name="from" select="'"'"/> <xsl:with-param name="to" select="'"'"/> <xsl:with-param name="string" select="."/> </xsl:call-template> <xsl:text>"</xsl:text> </xsl:template> <!-- pis --> <xsl:template mode="verb" match="processing-instruction()"> <xsl:value-of select="concat('<?',name(.),' ',.,'?>')"/> </xsl:template> <!-- only works if parser passes on comment nodes --> <xsl:template mode="verb" match="comment()"> <xsl:value-of select="concat('<!--',.,'-->')"/> </xsl:template> <!-- text elements need to replace & and < by entity references do > as well, just for balance --> <xsl:template mode="verb" match="text()"> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'>'"/> <xsl:with-param name="from" select="'>'"/> <xsl:with-param name="string"> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'<'"/> <xsl:with-param name="from" select="'<'"/> <xsl:with-param name="string"> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'&'"/> <xsl:with-param name="from" select="'&'"/> <xsl:with-param name="string" select="."/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:template> <!-- end verb mode --> <!-- replace all occurences of the character(s) `from' by the string `to' in the string `string'.--> <xsl:template name="string-replace" > <xsl:param name="string"/> <xsl:param name="from"/> <xsl:param name="to"/> <xsl:choose> <xsl:when test="contains($string,$from)"> <xsl:value-of select="substring-before($string,$from)"/> <xsl:value-of select="$to"/> <xsl:call-template name="string-replace"> <xsl:with-param name="string" select="substring-after($string,$from)"/> <xsl:with-param name="from" select="$from"/> <xsl:with-param name="to" select="$to"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$string"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | ||||
5. | Documenting XSLT | |||
I have been working (albeit slowly) on a system for doing literate programming from XML source documents. I call the tool "xmLP"; it is still at an "alpha" stage, but nonetheless does both "weave" (generate documentation) and "tangle" (assemble the product source files). At the bottom of this message is a sample of the documentation produced (currently) by "xmLP". This is based heavily on my FunnelWeb experience. PS If you have questions about literate programming & XML, you could do worse than join the (very low volume) "xml-litprog-l" list. { URL to follow when the code is ready - Juli 2000)} | ||||
6. | Xsldoc - XSLT Source Code Documentation Tool | |||
Announcing Xsldoc 0.9. This tool is for generating documentation of your XSLT files. The output is similar to Javadoc output for Java source files. The same commenting tags are used as well. See xsldoc for more info and to download this free software. | ||||
7. | Documenting Stylesheets | |||
I agree about basic template-level comments being more useful than code-level comments. The main comments that I find helpful are those concerning the assumptions that have been made in the stylesheet about the structure of the source document. For example, if you have an xsl:choose like: <xsl:choose> <xsl:when test=". = 'value1'"> ... </xsl:when> <xsl:when test=". = 'value2'"> ... </xsl:when> </xsl:choose> then it's helpful to know whether value1 and value2 are the only possible values, or if there are other possibilities that are (correctly) being ignored. Or if you have a plain xsl:apply-templates, with no select attribute, it's helpful to know what kinds of nodes you're assuming will be processed by that instruction (just descriptors like "inline elements" rather than listing them all). Or a comment that indicates the expected type of a parameter. These kinds of comments make following the flow of the stylesheet easier, and they help identify areas that might need to change when/if the source markup language changes. Other comments that I think can be helpful are those that explain complex XPath expressions in plain language. So things like: <!-- $rows holds a node set of unique row elements, by their code attribute --> <xsl:variable name="rows" select="//row[count(.|key('rows', @code)[1]) = 1]" /> XPath 2.0 allows comments *within* expressions (using the syntax {-- ... --}), which would mean you could do: <xsl:variable name="rows" select="//row[count(.|key('rows', @code)[1]) = 1] {-- select unique row elements by their code attribute --}" /> You can see why this might be necessary, given that XPath expressions will contain for and if statements... The other thing to mention is that you can go quite a long way towards making a stylesheet more readable and understandable without adding comments all over the place, just by using helpful variable names, key names, mode names, template names and so on. (P.S. I wrote an article about documentation for the "XPath & XSLT on the Edge unlimited edition" website (http://www.unlimited-edition.com/), but it doesn't seem to have appeared yet...) | ||||
8. | Documenting stylesheets | |||
No -- the doc:* elements are labelled as being extension elements through the extension-element-prefixes attribute. That means that the namespace is automatically excluded from the result tree. However, it also means that the processor will try to interpret the elements as instructions, which leads on to...
When a processor encounters the doc:template element, because it's labelled as an extension element, the processor will interpret it as an instruction. If it doesn't understand the instruction, it will usually come out with an error. However, if you place an xsl:fallback element inside, it won't raise an error, but rather will try to process the content of the xsl:fallback element instead. (If the xsl:fallback element is empty, then it does nothing, of course.) To give another, more common, example, say you were using Saxon's saxon:group extension element: <xsl:template match="people"> <saxon:group select="person" group-by="surname"> ... </saxon:group> </xsl:template> If you ran that with MSXML, you'd get an error because it doesn't understand the saxon:group element. To prevent that, you can add an xsl:fallback which either halts processing with a helpful error message: <xsl:template match="people"> <saxon:group select="person" group-by="surname"> ... <xsl:fallback> <xsl:message terminate="yes"> You must use Saxon to run this stylesheet! </xsl:message> </xsl:fallback> </saxon:group> </xsl:template> or provides some alternative XSLT that does the same thing (more or less) as the saxon:group. For example: <xsl:template match="people"> <saxon:group select="person" group-by="surname"> ... <xsl:fallback> <xsl:for-each select="person"> <xsl:sort select="surname" /> ... </xsl:for-each> </xsl:fallback> </saxon:group> </xsl:template> Using xsl:fallback in an element that you're using for documentation is a hack that enables you to take advantage of the fact that extension elements don't get output in the result tree, but it's a nasty hack, and a confusing use of xsl:fallback, which is why I think that we should have something that's specific for documentation instead, such as: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc="doc:documentation" documentation-element-prefixes="doc"> <doc:module>Here's some documentation of my stylesheet</doc:module> <xsl:template match="/"> <doc:template name="templateName" match="whatever"> <doc:descr>And I can use it within templates too!</doc:descr> <doc:result type="tNode-set">result description</doc:result> <doc:param name="someName">Param description</doc:param> <doc:param name="someOtherName">Param description</doc:param> ............... </doc:template> ... </xsl:template> </xsl:stylesheet> | ||||
9. | Adding documentation inside a template | |||
declare the documentation namespace to be an extension namespace and add an empty xsl:fallback child. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:extension-element-prefixes="d " version="2.0"> <d:doc xmlns:d="rnib.org.uk/tbs#"> <xsl:template match="/"> <d:documentation> This is documentation within a template <xsl:fallback/> </d:documentation> </xsl:template> | ||||
10. | Documentation method for XSLT | |||
a documentation methodology for XSLT stylesheets, and I have (finally!) prepared a major update: link The XSLStyle vocabulary sits at the top-level of your XSLT stylesheet, between all of the top-level XSLT constructs, documenting each top-level construct. XSLStyle is only scaffolding, however, as the actual paragraphs, lists, figures, tables, and other constructs are authored by you in your choice of one of three vocabularies: - HTML/XHTML You can use as much of the embedded vocabulary you need inside of the scaffolding vocabulary. You can then format the documentation to HTML and, something new in this delivery, engage CSS stylesheets to enhance the presentation that has been rather dull (though effective!) since its release. Many thanks to Florent Georges, George Bina and Dominic Marcotte who have supplied two examples of CSS formatting of the report results. During development of your stylesheets you can drag your stylesheet onto a browser and have the browser render the documentation as it stands. Then, as you edit your XSLT stylesheet, you can simply save the file and refresh the browser in order to review your updated documentation. When complete you can invoke an XSLT process on your XSLT stylesheet to create standalone HTML documentation. XSLStyle implements a set of "stylesheet writing rules" that I have for myself, which you are welcome to use, or hack out, or ignore, or augment with your own rules. Such rules include mandatory documentation on all template rules, functions and all parameters to them. All stylesheet fragments in your XSLT stylesheet import/include tree are included in the one generated HTML file, and at the end of the file is an alphabetized index hyperlinked to the declaration of every named top-level construct. This helps one find where globals are defined when using very large import/include trees. When I am contracted for XSLT development, I deliver my end results complete with the formatted documentation. The one HTML file includes tables, summaries, references to diagrams, and then the actual stylesheet constructs. When using Florent's stylesheet, the Oxygen icons for constructs are presented next to each declaration (used with permission, thanks George!). Feedback is welcome! For those already using XSLStyle, this is a major release and there have been a number of changes behind the scenes, though your stylesheets do not have to (should not have to!) change. Please let me know if you find I've introduced any problems. Input is welcome! If anyone develops nice visual CSS presentations for XSLStyle to include in future deliveries, I would consider including them. |