I have an input XML File:
<Items>
<Item Name="1" Value="Value1"></Item>
<Item Name="2" Value="Value2"></Item>
</Items>
What I want to transform to following output file with Saxon-CE.
<Items>
<Item Name="1" Value="NewValue1"></Item>
<Item Name="2" Value="NewValue2"></Item>
</Items>
JS:
function TransformXML() {
xsltData = Saxon.requestXML("transformXML.xsl");
xmlData = Saxon.requestXML("myxml.xml");
var xsltProcessor = Saxon.newXSLT20Processor(xsltData);
var result = xsltProcessor.transformToDocument(xmlData);
}
I have a mapping, that tells me the value of an item and the NewValue it should have after the transformation.
What I have so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" name="xml" />
<xsl:variable name="Items">
<Item Name="1">
<Value OldValue="Value1" NewValue="NewValue1" ></Value>
</Item>
<Item Name="2">
<Value OldValue="Value2" NewValue="NewValue2" ></Value>
</Item>
</xsl:variable>
<xsl:template match="Items">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Item">
<xsl:copy>
<xsl:if test="not($Items/Item[#Name=current()/#Name]/Value[#OldValue = current()/#Value])">
<!-- if value mismatch throw exception and stop -->
</xsl:if>
<xsl:attribute name="Value">
<xsl:value-of select="$Items/Item[#Name=current()/#Name]/Value[#OldValue = current()/#Value]/#NewValue"/>
</xsl:attribute>
<xsl:copy-of select="#*[name()!= 'Value']" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is working, but what I want to do now is to check if there is a value mismatch.
If yes then the script should stop and throw an exception what I can catch in the Javascript function.
Is there a way to realize this? Can I set a callback function for this?
With XSLT, you can terminate processing using <xsl:message select="'some message'" terminate="yes"/>. It would be desirable that the transformToDocument method throws a Javascript exception you could catch with try/catch but it does not seem to happen, at least not in a quick test I did. I was however able to set an error handler and handle that message in that handler, see http://home.arcor.de/martin.honnen/xslt/test2014120301.html for an example, which does
function onSaxonLoad()
{
Saxon.setLogLevel('SEVERE');
var errors = [];
Saxon.setErrorHandler(function(error) { errors.push(error); });
var xslt = Saxon.requestXML("test2014120301.xsl")
var xsltProc = Saxon.newXSLT20Processor(xslt);
xsltProc.setInitialTemplate("main");
var errorCount = errors.length;
var doc = xsltProc.transformToDocument();
if (errorCount < errors.length) {
var msg = errors[errors.length - 1].message;
var pre = document.createElement('pre');
pre.textContent = msg;
document.body.appendChild(pre);
}
else {
// use result document doc here
}
}
The message Saxon pushes to the error handler is SEVERE: XPathException in invokeTransform: Processing terminated by xsl:message in test2014120301.xsl. Obviously there could be other errors pushed to the error handler so you will need more checks on the Javascript side to perhaps check that the message test contains Processing terminated by xsl:message.
Related
AJAX loaded xml and xsl then transform it to html. The result node is imported to html container. The result has script element with inline javascript code.
After Chrome update inline code won't executed.
For example:
HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<script>
let buttonElement = document.createElement("button");
buttonElement.textContent = "Press me";
document.body.appendChild(buttonElement);
buttonElement.addEventListener("click", event => {
Promise.all([fetch('/test.xml'), fetch('/test.xsl')]).then(result => {
Promise.all([result[0].text(), result[1].text()]).then(result => {
let parser = new DOMParser(),
xml = parser.parseFromString(result[0], "application/xml"),
xsl = parser.parseFromString(result[1], "application/xml"),
target = document.body,
processor = new XSLTProcessor(),
output;
processor.importStylesheet(xsl);
output = processor.transformToDocument(xml);
let newOutput = document.body.appendChild(output.documentElement);
/*
Uncomment the block below, the result node its replacement helps and inline script will be executed properly
*/
/* newOutput.querySelectorAll("script").forEach(element => {
let replacement = document.createElement("script");
replacement.text = element.text;
element.replaceWith(replacement);
});
*/
});
});
}, false);
</script>
</body>
</html>
XML
<?xml version="1.0" encoding="UTF-8"?>
<hello>
<world/>
</hello>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<div>
<p>Hello world</p>
<script>console.log("Test me!");</script>
</div>
</xsl:template>
</xsl:stylesheet>
So, is it security bug of Webkit? Because, this bug was also detected in Opera.
And if this is security solution, why inline script is succesfully executed after result node replacement?
I am trying to generate output like this through XSL:
<script>
var quickCatalogueCategories = {
for each values do {
'{title}': 'http://www.news.com/{search key}',
}
};
</script>
The problem is I am getting the output but after each title I should get url instead after all titles I am getting the URL. Even after the URL I should have only one key but continuously displaying all four keys.
Can you tell me how to fix it?
Trying to achieve output like this:
<script>
var quickCatalogueCategories = {
'{What's New}':'http: //www.news.com/{news}',
'{Featured}':'http: //www.news.com/{featured}',
'{Most Delivered}':'http: //www.news.com/{most-delivered}',
'{Online Only}':'http: //www.news.com/{online-only}',
}
</script>
try this code
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Skin: Default XSL -->
<!--xsl:include href="http://www.interwoven.com/livesite/xsl/HTMLTemplates.xsl"/-->
<!--xsl:include href="http://www.interwoven.com/livesite/xsl/StringTemplates.xsl"/-->
<xsl:template match="/">
<!-- TODO put section -->
<script>
var quickCatalogueCategories={
<xsl:for-each select="//Datum[#Name='Catalog']//CatalogContainer">
<!-- you are already in the node, just select the value of the children -->
<xsl:text> '{</xsl:text><xsl:value-of select="CatalogKey"/><xsl:text>'}</xsl:text>:'http://www.news.com/{'<xsl:value-of select="CatalogView"/>}',}
</xsl:for-each>
}
</script>
</xsl:template>
</xsl:stylesheet>
I am trying to generate an XML file that list XML files that are in a specified folder using xsl:
XML file :
<xml>
<folder>FolderPath-to-List</folder>
</xml>
Expected result:
<mergeData newRoot="newRoot">
<fileList>
<fileItem>path-to-file/file1.xml</fileItem>
<fileItem>path-to-file/file2.xml</fileItem>
<fileItem>path-to-file/file3.xml</fileItem>
<fileItem>path-to-file/file4.xml</fileItem>
</fileList>
</mergeData>
So far I am able to collect Files list using XSL and embedded script/ JScipt function as follow in the current folder:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://tempuri.org/msxsl"
>
<msxsl:script language="JScript" implements-prefix="user">
<![CDATA[
var fso = new ActiveXObject("Scripting.FileSystemObject");
function ShowFolderFileList(folderspec)
{
var f, f1, fc, s;
f = fso.GetFolder(folderspec);
fc = new Enumerator(f.files);
s = '<fileItem>';
for (; !fc.atEnd(); fc.moveNext())
{
s += fc.item();
s += '<fileItem>\n<fileItem>';
}
return(s);
}
]]>
</msxsl:script>
<xsl:template match="/">
<mergeData newRoot="Activity">
<fileList>
<xsl:value-of select="user:ShowFolderFileList('.')"/>
</fileList>
</mergeData>
</xsl:template>
</xsl:stylesheet>
But the result is that in place of getting <fileItem> and </fileItem>, I have :
<fileItem>path-to-xml\file.xml<fileItem>
How can I get <fileItem>path-to-xml\file.xml</fileItem>?
How can I get the "FolderPath-to-List" from my XML to be used when calling user:ShowFolderFileList() in place of '.' so far to get it running.
First thing to note is that is that currently you are doing this
<xsl:value-of select="user:ShowFolderFileList('.')"/>
When really you ought to be doing this to use your file path in your XML
<xsl:value-of select="user:ShowFolderFileList(string(xml/folder/text()))" />
Note the use of "string()" here, because "text()" actually returns a text node, a not a datatype of string.
Secondly, when you use javascript functions in XSLT in this way, I believe they can only return the simple data types of string and number. Your function is returning a string, not actual XML, when you use xsl:value-of on a string, any reserved symbols will be escaped.
Now, you can be a bit naughty and do this
<xsl:value-of select="user:ShowFolderFileList(string(xml/folder/text()))"
disable-output-escaping="yes" />
But this is not necessarily considered good practise, as disable-output-escaping is not widely supported (although obviously it works in Mircosoft's implementation).
However, the only other way to do this (in XSLT 1.0) that I can think of is to return a list of file names, separated by new lines, and write a recursive template. Try this XSLT as an example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://tempuri.org/msxsl"
exclude-result-prefixes="msxsl user">
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="JScript" implements-prefix="user">
<![CDATA[
var fso = new ActiveXObject("Scripting.FileSystemObject");
function ShowFolderFileList(folderspec)
{
var f, f1, fc, s;
f = fso.GetFolder(folderspec);
fc = new Enumerator(f.files);
s="";
for (; !fc.atEnd(); fc.moveNext())
{
s += fc.item() + "\n";
}
return s;
}
]]>
</msxsl:script>
<xsl:template match="/">
<mergeData newRoot="Activity">
<fileList>
<xsl:call-template name="files">
<xsl:with-param name="files" select="user:ShowFolderFileList(string(xml/folder/text()))"/>
</xsl:call-template>
</fileList>
</mergeData>
</xsl:template>
<xsl:template name="files">
<xsl:param name="files"/>
<xsl:if test="normalize-space($files) != ''">
<file>
<xsl:value-of select="substring-before($files, '
')"/>
</file>
<xsl:if test="contains($files, '
')">
<xsl:call-template name="files">
<xsl:with-param name="files" select="substring-after($files, '
')"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
There are a couple of other options though:
Don't use XSLT for this!
Upgrade to XSLT 2.0 (Mircosoft don't support it, but you can get other XSLT processors for .Net)
See this question which basically asks the same thing:
XSLT: How to get file names from a certain directory?
I saw a lot of posts online about MSXML4 to 6 or XSLT 1.0 versus 2.0 etc. But they could not answer my question.
I have a XSLT transformation code that works with MSXML4 APIs (XSLTransform and FreeThreadedDomDocument) on IE7 via Javascript.
Same code doesnt work with with MSXML6 APIs (XSLTransform and DomDocument) on IE9 via Javascript. It throws this error
"Namespace 'urn:mynamespace:mytable:transactions' does not contain any functions"
I have made sure that my ActiveX are enabled for MSXML4 and 6 both on IE9. Below is the code of the main tranformer XSLT, the reference XSLT & the JS code ...
Core XSLT: functions.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myfuncs="urn:mynamespace:mytable:transactions" >
<msxsl:script language="javascript" implements-prefix="myfuncs">
<![CDATA[
// convert system GMT time into local time
// usage: <xsl:value-of select="myfuncs:localDateTime(datetime)"/>
var openBalance = 0;
function setOpenBalance(openBal)
{
openBalance = openBal;
}
function getOpenBalance()
{
openBalance = openBal;
return openBalance ;
}
]]>
</msxsl:script>
</xsl:stylesheet>
Main XSLT: MyTransformer.xsl ... that refers functions.xsl
<?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"
xmlns:myfuncs="urn:mynamespace:mytable:transactions">
<xsl:output method="xml"/>
<xsl:include href="functions.xsl" />
<!--<xsl:variable name="trade_cur_bal" select="myfuncs:getOpenBalance(100)"/>-->
<xsl:template match="/">
<Response>
<!-- Some working code here -->
</Response>
</xsl:template>
</xsl:stylesheet>
JS Code
var domXsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.4.0");
/*
// In case of IE9 ....
var domXsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.6.0");
*/
var domHTML = new ActiveXObject("Msxml2.XSLTemplate.4.0");
/*
// In case of IE9 ....
var domHTML = new ActiveXObject("Msxml2.XSLTemplate.6.0");
*/
domXsl.async=false;
domXsl.load("MyTransformer.xsl");
domHTML.stylesheet = domXsl;
var domData = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.4.0");
var input = "<MyInputData></MyInputData>"
domData.loadXML(input);
var result = tranform(domHTML, domData); //Works for MSXML 4.0 and fails for MSXML 6.0
function transform(template_, input_) {
var output = "";
if (input_ != null && input_.xml != "") {
var proc = template_.createProcessor();
proc.input = input_;
proc.transform();
output = proc.output;
delete proc;
}
return output;
}
Can someone guide me where am I going wrong w.r.t. MSXML6 or IE9?
Thx.
With MSXML 6 use of script inside XSLT is disabled by default for security reasons, so you need to explicitly enable it by calling
var domXsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.6.0");
domXsl.setProperty("AllowXsltScript", true);
And additionally to allow the use of xsl:import or xsl:include you also need to set
domXsl.setProperty("ResolveExternals", true);
I got this fixed by removing the versions (4 and 6) form the activeX class ID
e.g. new ActiveXObject("Msxml2.FreeThreadedDomDocument") etc.
I try to access from a browser a simple mailing list service I developed. I adapted a working example I found but I don't know why the request doesn't seem to reach the service and so no response is returned.
Using a SOAP envelope is a requirement, I need to know what is possibly wrong with this code, not how I could do the same thing using other techniques. (The service is deployed correctly on a GlassFish server and I have a working Java client to test it, so no problem with the service)
Does anyone see something bad (I'm joining the WSDL too, if you need any other detail don't hesitate)? Thanks!
<html>
<head>
<title>Soap Invocation</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript">
var request = null;
function createRequest() {
if (window.XMLHttpRequest){
request=new XMLHttpRequest();
}
else{
if (new ActiveXObject("Microsoft.XMLHTTP")) {
request = new ActiveXObject("Microsoft.XMLHTTP");
} else {
request = new ActiveXObject("Msxml2.XMLHTTP");
}
}
}
function getMail() {
createRequest();
var envelope = "<?xml version='1.0' encoding='UTF-8'?>";
envelope += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>";
envelope += "<soap:Header/>";
envelope += "<soap:Body>";
envelope += "<ns2:getMails xmlns:ns2='http://service.inf4375.com/'>";
envelope += "</ns2:getMails>";
envelope += "</soap:Body>";
envelope += "</soap:Envelope>";
var url = "http://127.0.0.1:8080/SoapService/MailingService";
request.onreadystatechange = updatePage;
request.open("GET", url, false);
request.setRequestHeader("Content-Type", "text/html");
request.setRequestHeader("SOAPAction", "");
request.send(envelope);
}
function updatePage() {
if(request.readyState == 4) {
document.getElementById("get").innerHTML = "<p>" + request.responseXML.selectSingleNode("//return").text + "</p>";
} else {
document.getElementById("get").innerHTML = "Loading..."
}
}
</script>
</head>
<body>
<input type="button" value="GetMail" onclick="getMail();" />
<span id="get"></span>
</body>
</html>
The WSDL :
<?xml version='1.0' encoding='UTF-8'?>
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://service.inf4375.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://service.inf4375.com/" name="MailingService">
<types>
<xsd:schema>
<xsd:import namespace="http://service.inf4375.com/" schemaLocation="http://localhost:8080/SoapService/MailingService?xsd=1"/>
</xsd:schema>
</types>
<message name="getMails">
<part name="parameters" element="tns:getMails"/>
</message>
<message name="getMailsResponse">
<part name="parameters" element="tns:getMailsResponse"/>
</message>
<message name="addMail">
<part name="parameters" element="tns:addMail"/>
</message>
<message name="addMailResponse">
<part name="parameters" element="tns:addMailResponse"/>
</message>
<portType name="Mailing">
<operation name="getMails">
<input wsam:Action="http://service.inf4375.com/Mailing/getMailsRequest" message="tns:getMails"/>
<output wsam:Action="http://service.inf4375.com/Mailing/getMailsResponse" message="tns:getMailsResponse"/>
</operation>
<operation name="addMail">
<input wsam:Action="http://service.inf4375.com/Mailing/addMailRequest" message="tns:addMail"/>
<output wsam:Action="http://service.inf4375.com/Mailing/addMailResponse" message="tns:addMailResponse"/>
</operation>
</portType>
<binding name="MailingPortBinding" type="tns:Mailing">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="getMails">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="addMail">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="MailingService">
<port name="MailingPort" binding="tns:MailingPortBinding">
<soap:address location="http://localhost:8080/SoapService/MailingService"/>
</port>
</service>
I don't get any errors in the Error Console when I'm trying to execute it on Firefox, and Internet Explorer is only displaying the Loading... and nothing more.
SOAP must be sent via a POST, not a GET.
Also, it looks like your WS-Messaging headers are wrong. In fact, I don't even see them.
Try issuing this call using a .NET client ("Add Service Reference"), and watch on the wire with Fiddler or something like it to see what's happening. Then do the same thing.