Can anyone please help me on masking of non-XML input in Datapower.
I am logging the input first in Datapower log store and taking that input to mask, I have written a piece of code which works for XML but not for non-XML,
attaching my input non-XML and code for the reference.
Attached masking file.xsl has two templates, one for non-XML and another for XML. XML is working fine for numeric values and not masking the characters.
Non-XML template is called but not doing masking.(I can see in system logs it is being called)
The control given to masking template is given by a different log.xml file attached.
Quick response is appreciated.
Thanks,
Anuj
Input Non-xml.
PCTM-ODS-MTRF-WRSP-RSP PCTM-ODS-MTRF-WRSP-RSP 0200000298111 00000000ODS00000000000000834978953 00LIQ055003241NYYNYNNY 10000020130823Y000000000018765-000000000018765-000000000018765-010000100000000000000000000000-000000000000000-000000000000000-
My Code: In the below maskFldName is called from the log.xml file.
<xsl:template name="NONXMLmaskingTemplate">
<xsl:param name="maskFldName"/>
<xsl:param name="Input"/>
<xsl:variable name="maskchars" select="'******************************'"/>
<xsl:message>maskFldName:<xsl:copy-of select="$maskFldName"/>
</xsl:message>
<xsl:variable name="logInput">
<!--<dp:serialize select="$Input" omit-xml-decl="yes"/>-->
<xsl:copy-of select="translate($Input,'" : "','":"')"/>
</xsl:variable>
<xsl:message>MaskInputMessage:<xsl:copy-of select="$logInput"/>
</xsl:message>
<dp:set-local-variable name="'var://local/input'" value="$logInput"/>
<xsl:message>!!!!
<xsl:copy-of select="translate($Input,' ','')"/>
</xsl:message>
<xsl:for-each select="$maskFldName">
<xsl:variable name="startPosition" select="./#startPosition"/>
<xsl:variable name="noOfChars" select="./#numOfChars"/>
<xsl:message>startPosition:<xsl:value-of select="$startPosition"/>
</xsl:message>
<xsl:message>noOfChars:<xsl:value-of select="$noOfChars"/>
</xsl:message>
<xsl:variable name="maskString" select="substring($maskchars,1,$noOfChars)"/>
<xsl:message>Matches:<xsl:copy-of select="regexp:match($logInput,.,'g')"/>
</xsl:message>
<xsl:message>InputMessage1:<xsl:copy-of select="$Input"/>
</xsl:message>
<xsl:for-each select="regexp:match($logInput,.,'g')">
<xsl:message>InputMessage:<xsl:copy-of select="$logInput"/>
</xsl:message>
<xsl:message>ValueX:<xsl:copy-of select="."/>
</xsl:message>
<xsl:variable name="strToReplace" select="substring(.,$startPosition,$noOfChars)"/>
<xsl:variable name="strToReplace2" select="regexp:replace(.,$strToReplace,'g',$maskString)"/>
<xsl:message>strToReplace:<xsl:copy-of select="$strToReplace"/>
</xsl:message>
<xsl:message>strToReplace2:<xsl:value-of select="$strToReplace2"/>
</xsl:message>
<dp:set-local-variable name="'var://local/input'" value="regexp:replace(dp:local-variable('var://local/input'),.,'g',$strToReplace2)"/>
</xsl:for-each>
</xsl:for-each>
<xsl:copy-of select="dp:local-variable('var://local/input')"/>
<!--
If message to be proper xml i.e. < needs to be output as < then the following code to be used
<xsl:copy-of select="dp:transform('detailLog.xsl',dp:parse(dp:local-variable('var://local/input')))"/>
<xsl:message>MaskOutputMessage:<xsl:copy-of select="dp:local-variable('var://local/input')"/> </xsl:message>
-->
</xsl:template>
</xsl:stylesheet>
log.xml
<?xml version="1.0" encoding="UTF-8"?>
<logConfig>
<!-- ALL/NONE/FRONT/BACK -->
<log-enabled value="ALL"/>
<!-- Y/N -->
<detail-log-enabled value="Y"/>
<!-- Y/N -->
<slim-log-enabled value="N"/>
<masking isMaskRequire="Y">
<logpoint id="service_req_entry">
<contentType>NONXML</contentType>
<maskPattern attribute="detail" startPosition="50" numOfChars="4">(PCTM[\w])</maskPattern>
</logpoint>
<logpoint id="SCRq-out">
<contentType>XML</contentType>
<maskPattern attribute="detail" startPosition="11" numOfChars="4">(Severity>[\w]+<)</maskPattern>
</logpoint>
</masking>
</logConfig>
Perhaps you could use this technote to wrap the non-xml in XML and then use your XSLT
http://www-01.ibm.com/support/docview.wss?uid=swg21321379
Relevant portion:
Transforming raw text to pseudo-XML:
This example will take the following text as input:
some text here
and here is some more
and some more
and some more
and wait, some more here
Steps to Create:
1.Create a Transform action in the service that will be receiving non-XML requests.
2.Select "Use XSLT specified in this action on a non-XML message" to specify that this is a Binary Transform.
3.Upload the following style sheet as the Processing Control File.
4.Click Done, and Apply all windows.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
version="1.0">
<dp:input-mapping href="sample.ffd" type="ffd"/>
<!-- This stylesheet copies the input to the output -->
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:value-of select="sampleFile/sampleTag" disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>
Upload the following FFD file into the local://sample.ffd directory from the File Manager.
<File name="sampleFile">
<!-- capture all data into this tag -->
<Field name="sampleTag" />
</File>
Sending the sample input above will result in the following output
<?xml version="1.0" encoding="utf-8"?>
<sampleFile>
<sampleTag>
some text here
and here is some more
and some more
and some more
and wait, some
</sampleTag>
</sampleFile>
Related
I am trying to modify an existing XSLT to include the originalfilename attribute of the file node in the input XML as the file attribute of feedback in the output xml. I think I'm misunderstanding the copy-of statement and I would be very extremely grateful for any help. At the moment my desired output is displaying an empty file attribute on the feedback node.
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<document>
<xsl:for-each select="files/file/segmentpair[Comments/Comment]">
<xsl:apply-templates select="Comments/Comment" />
<xsl:copy-of select="files|file|source|target" />
</xsl:for-each>
</document>
</xsl:template>
<xsl:template match="Comment">
<feedback>
<xsl:attribute name="id">
<xsl:value-of select="count(preceding::Comments)+1" />
</xsl:attribute>
<xsl:attribute name="file">
<xsl:value-of select="files/file/#originalfilename" />
</xsl:attribute>
<xsl:value-of select="." />
</feedback>
</xsl:template>
</xsl:stylesheet>
Input
<files>
<file originalfilename="C:\Users\A\Documents\Studio 2014\Projects\15_002_\de-DE\master\advanced-materials-and-processes-msc-hons.xml">
<segmentpair id="1" locked="False" color="245,222,179" match-value="86">
<source>Advanced Materials and Processes (M.Sc.hons.)</source>
<target>Advanced Materials and Processes (MSc)</target>
<Comments>
<Comment>[ic14epub 20.01.2015 09:28:43] 'hons' taken out (discussion of this still ongoing as far as I'm aware)</Comment>
</Comments>
</segmentpair>
</file>
</files>
Desired Output
<feedback id="1" file="C:\Users\A\Documents\Studio 2014\Projects\15_002_\de-DE\master\advanced-materials-and-processes-msc-hons.xml">[ic14epub 20.01.2015 09:28:43] 'hons' taken out (discussion of this still ongoing as far as I'm aware)</feedback>
<source>Advanced Materials and Processes (M.Sc.hons.)</source>
<target>Advanced Materials and Processes (MSc)</target>
You want the #originalfilename of the first <file> ancestor for that <Comment>.
<xsl:template match="Comment">
<feedback
id="{count(preceding::Comments)+1}"
file="{ancestor::file[1]/#originalfilename}"
>
<xsl:value-of select="." />
</feedback>
</xsl:template>
Note the attribute value templates (curly braces). They can save quite a lot of typing.
You can get your desired output simply by adding a / before files in your <xsl:value-of>
<xsl:value-of select="/files/file/#originalfilename"/>
However, I would recommend to use
<xsl:value-of select="../../../#originalfilename"/>
instead of the absolute path, so it will still works if you have more files.
I need to get a value which is coming from two different nodes in the same XML file. For instance, my xml:
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
and I want to get equivalent of 50 Dollars in Euro.
I tried :
<xsl:value-of select="(Asset/value * /exchangeRates[node() = curr]/curr_wanted)"/>
But it didn't work. Also I have to use XSLT 1.0. How can I get that value in Euro?
I did not test it very much but for input like
<?xml version="1.0" encoding="UTF-8"?>
<root>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>25</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
</root>
something like following could work
for $asset in /root/asset, $rate in /root/exchangeRates
return $asset/value*$rate/*[name() = $asset/curr]/*[name() = $asset/curr_wanted]
But it will work only in xpath 2.0 and it also depends on the whole input xml (like if there might exist more asset elements, more exchangeRates elements, etc.).
Edit: In xslt 1.0 you could use xsl:variable to store some information and prevent them from context changes during xpath evaluation. Look for example at following template
<?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:output method="text" />
<!-- Store "exchangeRates" in a global variable-->
<xsl:variable name="rates" select="/root/exchangeRates" />
<xsl:template match="/root">
<xsl:apply-templates select="asset" />
</xsl:template>
<xsl:template match="asset">
<!-- Store necessary values into local variables -->
<xsl:variable name="currentValue" select="value" />
<xsl:variable name="currentCurrency" select="curr" />
<xsl:variable name="wantedCurrency" select="curr_wanted" />
<xsl:variable name="rate" select="$rates/*[name() = $currentCurrency]/*[name() = $wantedCurrency]" />
<!-- Some text to visualize results -->
<xsl:value-of select="$currentValue" />
<xsl:text> </xsl:text>
<xsl:value-of select="$currentCurrency" />
<xsl:text> = </xsl:text>
<!-- using variable to prevent context changes during xpath evaluation -->
<xsl:value-of select="$currentValue * $rate" />
<!-- Some text to visualize results -->
<xsl:text> </xsl:text>
<xsl:value-of select="$wantedCurrency" />
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
which produces following output for input xml above.
50 USD = 36.5 EUR
25 USD = 18.25 EUR
I have some XML of the form:
<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24 />
...
</reg>
...
</definitions>
I want the XML to be the definitive reference and use XSLT to transform it to HTML for documentation, .h for building (and maybe other forms too).
The HTML version is working fine and produces a table per register, with a row per field:
... (header boilerplate removed)
<xsl:for-each select="definitions/reg">
<table>
<tr>
<th><xsl:value-of select="#offset"/></th>
<th><xsl:value-of select="#mnem"/></th>
</tr>
<xsl:for-each select="field">
<tr>
<td><xsl:value-of select="#msb"/>..<xsl:value-of select="#lsb"/></td>
<td><xsl:value-of select="#mnem"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
Converting to a .h isn't going so well. I'm completely failing to generate the required spaces in the output:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
#define <xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'd hope for that to produce the output:
#define SOMEREGISTER 0x0000
But I actually get:
#define SOMEREGISTER0x0000
I don't understand why I get the space after the '#define', but not the one after the transformed mnemonic. I've tried a simpler solution with just an inline space, with the same results.
I'm too new to this (XSLT) to know whether I'm a) doing it wrong or b) finding a limitation in tDOM.
Testing with this:
# I could have read these from a file I suppose...
set in {<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24" />
</reg>
</definitions>}
set ss {<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text>#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>}
# Interesting code starts here
package require tdom
set indoc [dom parse $in]
set xslt [dom parse -keepEmpties $ss]
set outdoc [$indoc xslt $xslt]
puts [$outdoc asText]
I find that this works. The issue is that the tDOM parser doesn't handle the xml:space attribute correctly; without the magical -keepEmpties option, all the empty strings are stripped from the stylesheet and that leads to a wrong XSLT stylesheet being applied. But with the option, it appears to do the right thing.
Note that the XSLT engine itself is doing the right thing. It's the XML parser/DOM builder. (I think it's a bug; I'll look up where to report it.)
Per:
http://www.ibm.com/developerworks/xml/library/x-tipwhitesp/index.html
Try using the preserve space directive:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text xml:space="preserve">#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You don't have an output method specified in your second stylesheet, so the default is gonna be XML. I'd advice you to use output method "text", then use <xsl:text> elements for any literal output. Check this example:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="definitions/reg"><xsl:text>#define </xsl:text><xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/><xsl:text> </xsl:text><xsl:value-of select="#offset"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: by the way, that
at the end is a character code. It's simply the decimal value of the ASCII code for a line feed. This makes sure you start a new line for the next reg entry. If you need the Windows/DOS convention (carriage return + line feed), use
instead.
I have an XSL file which uses a a static website link as shown below:
<xsl:template match="my_match">
<xsl:variable name="variable1">
<xsl:value-of select="sel1/Label = 'Variable1'"/>
</xsl:variable>
<xsl:copy-of select="sites:testPath('http://testsite.com/services/testService/v1.0', $fname, $lname,
$email , $zip, $phone, $comments, $jps, boolean($myvar), string(cust/#custID), string(#paID))"/>
</xsl:template>
My question is that how to read a properties file(key value pair) in the xsl file. so in my properties file (e.g. site.properties) I have a key called site i.e. site=testsite.com/services/testService/v1.0
I want to use this site key in place of specifying url value in the xsl i.e. http://testsite.com/services/testService/v1.0. The reason for doing this is that this link changes depending on the various environments.
Is this possible?
Please give your suggestions or a sample code if possible...Also if this is not possible...is there any work-around?
As a proof of concept:
Input .properties file:
# You are reading the ".properties" entry.
! The exclamation mark can also mark text as comments.
website = http://example.com
language = English
key\ with\ spaces = This is the value that could be looked up with the key "key with spaces".
Stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="Functions"
version="2.0">
<xsl:variable name="properties" select="unparsed-text('.properties')" as="xs:string"/>
<xsl:template match="/" name="main">
<xsl:value-of select="f:getProperty('language')"/>
</xsl:template>
<xsl:function name="f:getProperty" as="xs:string?">
<xsl:param name="key" as="xs:string"/>
<xsl:variable name="lines" as="xs:string*" select="
for $x in
for $i in tokenize($properties, '\n')[matches(., '^[^!#]')] return
tokenize($i, '=')
return translate(normalize-space($x), '\', '')"/>
<xsl:sequence select="$lines[index-of($lines, $key)+1]"/>
</xsl:function>
</xsl:stylesheet>
The f:getProperty('language') will return 'English'.
See this as a proof of concept, this needs to be improved in many ways since it does not handle many of the different ways a .properties file can be authored.
I belive Alejandro or Dimitrie probably could improve this many times.
For an XSLT 1.0 solution, you could use an external (parsed) general entity in an XML file that will load the properties file as part of the XML content.
For example, if you had a properties file like this, named site.properties:
foo=x
site=http://testsite.com/services/testService/v1.0
bar=y
You could create a simple XML file, named properties.xml that "wraps" the content of the properties file and loads it using an external parsed general entity:
<!DOCTYPE properties [
<!ENTITY props SYSTEM "site.properties">
]>
<properties>
&props;
</properties>
Then, within your XSLT you can load that properties.xml using the document() function and obtain the value for a given key:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="props" select="document('properties.xml')" />
<xsl:template match="/">
<output>
<example1>
<!--simple one-liner -->
<xsl:value-of select="substring-before(
substring-after($props,
concat('site','=')),
'
')" />
</example1>
<example2>
<!--using a template to retrieve the value
of the "site" property -->
<xsl:call-template name="getProperty">
<xsl:with-param name="propertiesFile" select="$props"/>
<xsl:with-param name="key" select="'site'"/>
</xsl:call-template>
</example2>
<example3>
<!--Another example using the template to retrieve
the value of the "foo" property,
leveraging default param value for properties -->
<xsl:call-template name="getProperty">
<!--default $propertiesFile defined in the template,
so no need to specify -->
<xsl:with-param name="key" select="'foo'"/>
</xsl:call-template>
</example3>
</output>
</xsl:template>
<!--Retrieve a property from a properties file by specifying the key -->
<xsl:template name="getProperty">
<xsl:param name="propertiesFile" select="$props"/>
<xsl:param name="key" />
<xsl:value-of select="substring-before(
substring-after($propertiesFile,
concat($key,'=')),
'
')" />
</xsl:template>
</xsl:stylesheet>
When applied to any XML input the stylesheet above will produce the following output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<example1>http://testsite.com/services/testService/v1.0</example1>
<example2>http://testsite.com/services/testService/v1.0</example2>
<example3>x</example3>
</output>
Note: this strategy will only work if the content of the properties file is "XML safe". If it were to contain characters, like & or < it would result in an XML parsing error when the properties.xml file is loaded.
I've the following xslt file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- USDomesticCountryList - USE UPPERCASE LETTERS ONLY -->
<xsl:variable name="USDomesticCountryList">
<entry name="US"/>
<entry name="UK"/>
<entry name="EG"/>
</xsl:variable>
<!--// USDomesticCountryList -->
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="normalize-space(document('')//xsl:variable[#name='USDomesticCountryList']/entry[#name=$country]/#name)"/>
</xsl:template>
</xsl:stylesheet>
I need to replace the "document('')" xpath function, what should I use instead?
I've tried to remove it completely but the xsl document doesn't work for me!
I need to to so because the problem is :
I am using some XSLT document that uses the above file, say document a.
So I have document a that includes the above file (document b).
I am using doc a from java code, I am do Caching for doc a as a javax.xml.transform.Templates object to prevent multiple reads to the xsl file on every transformation request.
I found that, the doc b is re-calling itself from the harddisk, I believe this is because of the document('') function above, so I wanna replace/remove it.
Thanks.
If you want to access the nodes inside a variable you normally use the node-set() extension function. The availability and syntax depends on the processor you use. For MSXML and Saxon you can use exsl:node-set(). To use the extension function you will have to include the namespace that defines the function.
E.g. (tested wiht MSXML, returns US for countryName = 'US'):
<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"/>
<!-- USDomesticCountryList - USE UPPERCASE LETTERS ONLY -->
<xsl:variable name="USDomesticCountryList">
<entry name="US"/>
<entry name="UK"/>
<entry name="EG"/>
</xsl:variable>
<!--// USDomesticCountryList -->
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="exsl:node-set($USDomesticCountryList)/entry[#name=$country]/#name"/>
</xsl:template>
</xsl:stylesheet>
If you're trying to make the IsUSDomesticCountry template work without using document(''), you could rewrite the template to
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:choose>
<xsl:when test="$country='US'">true</xsl:when>
<xsl:when test="$country='UK'">true</xsl:when>
<xsl:when test="$country='EG'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
or
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="$country='US' or $country='UK' or $country='EG'"/>
</xsl:template>
or even
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country"
select="concat('-', normalize-space($countryParam),'-')"/>
<xsl:variable name="domesticCountries" select="'-US-UK-EG-'"/>
<xsl:value-of select="contains($domesticCountries, $country)"/>
</xsl:template>
Personally, I find the variant using document('') to be more readable.