Edge and IE11 XSLT space issue - javascript

I have ran into an issue with Edge and IE11 using XSLT to convert from HTML to XML.
When converting, elements that contain only spaces (one or many) get turned into an empty or self closing element after transform in Edge and IE11 only; Chrome and Firefox persist the spaces. This is true going from XML to HTML and HTML to XML
I have created a Codepen example of the issue ging from HTML to XML which is an ultra chopped down version of the code to demonstrate with minimal noise what the process I am using is.
https://codepen.io/akealey/pen/YzyEmpz
Run the pen in Chrome and Edge and the result will demonstrate Edge removing the space.
Is there any way to preserve the space(s)? I have gone through all sorts of different attributes and settings to do so but nothing works.
The markup being transformed exists on a webpage (the webpage I have full control over, the document I do not).
var outStr, processor, implementationObject, transformedDocument;
// a trimmed down document all the way to the element in question
var xmlStr = '<div> </div>';
// an alternate bare bones xslt. also does not generate a space in the output
var xsltStr = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n<xsl:output method="xml" encoding="utf-8" indent="no"/>\n<xsl:template match="/">\n<xsl:copy-of select="*" />\n</xsl:template></xsl:stylesheet>';
// create the dom parser
var domParser = new DOMParser();
// parse both xml and xslt into actual dom objects. Note xml has the xml header prepended
var xmlDoc = domParser.parseFromString('<?xml version="1.0" ?>' + xmlStr, 'text/xml');
var xsltDoc = domParser.parseFromString(xsltStr, 'text/xml');
// test what xslt processors are available. if chrome, firefox, edge - else ie11
if (typeof XSLTProcessor !== "undefined" && XSLTProcessor !== null) {
// Chrome
// Edge
// Firefox
processor = new XSLTProcessor();
processor.importStylesheet(xsltDoc);
//edge has the space inside xmlDoc up to this point
transformedDocument = processor.transformToFragment(xmlDoc, document);
// inspecting the tansformed document in Edge shows the element has no space but chrome and firefox does
} else if ('transformNode' in xmlDoc) {
// IE11
transformedDocument = xmlDoc.transformNode(xsltDoc);
} else {
console.log('no transform engine found');
}
// turn the converted xml document into a string
var xmlSerializer = new XMLSerializer();
var transformResult = xmlSerializer.serializeToString(transformedDocument);
console.log(transformResult);
// In Edge .serializeToString() turns the element in to a self closing tag (as there is no content)
var hasSpace = /> <\//.test(transformResult);
console.log(hasSpace);

For IE using MSXML directly I think you need to set preserveWhiteSpace to true on any DOMDocument before using load or loadXML, as the default of that property is false (https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ms761353(v%3Dvs.85)).
For Edge it might help to set up <div xml:space="preserve"> </div>.

Your codepen has errors in IE. transformNode is undefined in IE. You need to use new ActiveXObject('Msxml2.DOMDocument.6.0') and loadXML in IE instead of DOMParser to parse XML. For Edge, I'm in favor of Martin's answer: Apply xml:space="preserve" on the root element then it will apply to all descendants as well.
The final code sample is like this which can work well in IE and Edge (pay attention to the //IE11 parts in the code):
function textOrConsole(text, elementSelector) {
var processorElement = document.querySelector(elementSelector);
if (processorElement)
processorElement.innerText = text;
else
console.log(text);
}
var outStr, processor, implementationObject, transformedDocument, transformResult;
// a trimmed down document all the way to the element in question
var xmlStr = '<div xml:space="preserve"><div> </div></div>';
// an alternate bare bones xslt. also does not generate a space in the output
var xsltStr = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n<xsl:output method="xml" encoding="utf-8" indent="no"/>\n<xsl:template match="/">\n<xsl:copy-of select="*" />\n</xsl:template></xsl:stylesheet>';
// create the dom parser
var domParser = new DOMParser();
// parse both xml and xslt into actual dom objects. Note xml has the xml header prepended
var xmlDoc = domParser.parseFromString('<?xml version="1.0" ?>' + xmlStr, 'text/xml');
var xsltDoc = domParser.parseFromString(xsltStr, 'text/xml');
// test what xslt processors are available. if chrome, firefox, edge - else ie11
if (typeof XSLTProcessor !== "undefined" && XSLTProcessor !== null) {
// Chrome
// Edge
// Firefox
textOrConsole('XSLTProcessor (transformToFragment)', '#transform');
processor = new XSLTProcessor();
processor.importStylesheet(xsltDoc);
//edge has the space inside xmlDoc up to this point
transformedDocument = processor.transformToFragment(xmlDoc, document);
// inspecting the tansformed document in Edge shows the element has no space but chrome and firefox does
} else if (!!window.ActiveXObject || "ActiveXObject" in window) {
// IE11
var docxml = new ActiveXObject('Msxml2.DOMDocument.6.0');
docxml.loadXML(xmlStr);
var docxsl = new ActiveXObject('Msxml2.DOMDocument.6.0');
docxsl.loadXML(xsltStr);
transformedDocument = docxml.transformNode(docxsl);
textOrConsole('xmlDoc.transformNode', '#transform');
} else {
console.log('no transform engine found');
}
// turn the converted xml document into a string
var xmlSerializer = new XMLSerializer();
if (!!window.ActiveXObject || "ActiveXObject" in window) {
// IE11
transformResult = transformedDocument;
} else {
transformResult = xmlSerializer.serializeToString(transformedDocument);
}
// In Edge .serializeToString() turns the element int oa self closing tag (as there is no content)
var hasSpace = /> <\//.test(transformResult);
textOrConsole("Transformed element: " + transformResult, '#text');
textOrConsole("Has space: " + hasSpace, '#hasSpace');
<h3>Result</h3>
<span>Transform used: </span><span id="transform"></span>
<div id="text"></div>
<div id="hasSpace"></div>
</body>

Related

Why does Safari's document.adoptNode() convert css class names to lowercase when source document is in quirks mode?

I am using XHR (XML Http Request) to load an html fragment. I am using responseType = "document" in order to offload the html parsing to the browser. When the ajax call completes, I am using document.adoptNode() to include the ajax html elements in my main document.
I noticed a weird bug that affects Safari only (v9.1.1 on El Capitan and iOS 9.3.2). It seems that when Safari adopts the node, it will convert css class names into lower case. For a full demonstration, see this jsfiddle: https://jsfiddle.net/theblueslate/wxo7zst5/2/
This bug doesn't occur on Chrome v51 or IE11.
The code from the jsfiddle is included here:
function buildDataLoadedCallback (containerId, useAdopt) {
return function() {
var parsedDoc = this.response;
var parsedBodyChild = parsedDoc.body.children[0];
var newNode;
if (useAdopt) {
newNode = document.adoptNode(parsedBodyChild);
} else {
newNode = document.importNode(parsedBodyChild, true);
}
var container = document.getElementById(containerId);
container.appendChild(newNode);
}
}
function requestAjaxHtmlFragment(pageName, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = "document";
xhr.addEventListener("load", callback);
/* this fragment.html file simply contains:
<div class="myClass">
<p>MyClass</p>
</div>
*/
xhr.open("GET","https://dl.dropboxusercontent.com/u/15211879/js-fiddle/" + pageName + ".html", /*async:*/true);
xhr.send();
}
var pageName = "fragment";
requestAjaxHtmlFragment(pageName, buildDataLoadedCallback(pageName + "-adopt-container", true));
Is there an obvious error that I am missing? I can't spot it, and I have raised a webkit bug: https://bugs.webkit.org/show_bug.cgi?id=159555, but I am hoping I am wrong.
Turns out this was a bug. Now fixed in WebKit: https://bugs.webkit.org/show_bug.cgi?id=159555
I think it is still useful posting this to SO. Posting increases the visibility for anybody else who is struggling with this issue, as I was.

getSelection / collapse Microsoft Edge

I'm on a website, using an old version of Dojo, https://dojotoolkit.org/ (old because it's will be to difficult to change every thing in my office).
with Edge, we have troubles.... One is on the function "collapse".
Our dojo was modified to have "dojo.isEdge" to detect this new browser.
first I had
if(window.getSelection && !dojo.isIE){
var selection = dojo.global.getSelection();
if(selection.removeAllRanges){ // Mozilla
if(beginning){
selection.collapseToStart();
}else{
selection.collapseToEnd();
}
}else if(dojo.isSafari) { // Safari
// pulled from WebCore/ecma/kjs_window.cpp, line 2536
selection.collapse(beginning);
}
}else if(dojo.isIE){ // IE
var range = dojo.doc.selection.createRange();
range.collapse(beginning);
range.select();
}
So with Edge it were in the first part. And we had an error...
No we have
if(window.getSelection && !dojo.isIE && !dojo.isEdge){
var selection = dojo.global.getSelection();
if(selection.removeAllRanges){ // Mozilla
if(beginning){
selection.collapseToStart();
}else{
selection.collapseToEnd();
}
}else if(dojo.isSafari) { // Safari
// pulled from WebCore/ecma/kjs_window.cpp, line 2536
selection.collapse(beginning);
}
}else if(dojo.isIE){ // IE
var range = dojo.doc.selection.createRange();
range.collapse(beginning);
range.select();
}else if(dojo.isEdge){ // IE
var sel = dojo.global.getSelection();
sel.collapse(beginning);
sel.select();
}
... sel is good... But, sel.collapse(beginning) is sending an error... "Argument obligatoire" (sorry, It's in French, the translation is... mandatory arguement ?).... But beginning is defined (and is "true"). I replaced "beginning" by true, btw, same error....
do you have any idea ?
thanks .

onreadystatechange not working consistently between browsers

In short I'm trying to get the dimensions of any given div's background-image so I can do some fancy sprite animations with it. The code below is a simplified version but illustrates the blocking issue.
Given a div with a styled background-image, with the class "animation", the following will return the naturalWidth of the background image in FF/Safari/Chrome, but not in any IE:
var testarray = [];
var divs = document.getElementsByClassName("animation");
console.log("testarray.length: ", testarray.length);
onload = function starttest(){
console.log("in onload");
console.log("testarray.length: ", testarray.length); //returns correct length in all browsers
console.log("testarray[0].src: ", testarray[0].src); //returns filename in all browsers
console.log("testarray[0].naturalWidth: ", testarray[0].naturalWidth); //returns 0 in IE only
}
document.onreadystatechange = function(){
console.log("in readystatechange");
console.log("document.readystate: ", document.readyState);
var thing = new Image();
testarray.push(thing);
if (document.readyState === "interactive") {
var bi = window.getComputedStyle(divs[0], false).backgroundImage.slice(4, -1);
testarray[0].src = bi;
document.body.appendChild(testarray[0]);
}
}
The above is run in an embedded script below the body tag.
Why does the loaded image have no dimensions in IE? appendChild fails silently in IE as well, though works great in other browsers. I'd prefer a native JS answer over jQuery.

Javascript error: Object Required in ie6 & ie7

I have a javascript function (epoch calendar) which displays a calendar when focus is set on certain text boxes. this works fine in ie8, ff (all versions as far as I can test), opera etc but doesn't work in ie7 or previous.
If i have it set up in a blank html test page it will work so I'm fairly sure it's a conflict with my css (provided to me by a designer).
I've traced the error to these lines of code -
Epoch.prototype.getTop = function (element) //PRIVATE: returns the absolute Top value of element, in pixels
{
var oNode = element;
var iTop = 0;
while(oNode.tagName != 'BODY') {
iTop += oNode.offsetTop;
oNode = oNode.offsetParent;
}
return iTop;
};
Epoch.prototype.getLeft = function (element) //PRIVATE: returns the absolute Left value of element, in pixels
{
var oNode = element;
var iLeft = 0;
while(oNode.tagName != 'BODY') {
iLeft += oNode.offsetLeft;
oNode = oNode.offsetParent;
}
return iLeft;
};
More specifically, if i remove the actual while loops then the calendar will display OK, just that its positioning on the page is wrong?
EDIT
Code below which sets 'element'
<script type="text/javascript">
window.onload = function() {
var bas_cal, dp_cal, ms_cal;
dp_cal = new Epoch('epoch_popup', 'popup', document.getElementById('<%=txtDateOfDiag.ClientID%>'));
dp_cal = new Epoch('epoch_popup', 'popup', document.getElementById('<%=txtDOB.ClientID%>'));
};
</script>
Note: I am using asp.net Master pages which is why there is a need for the .ClientID
EDIT
A further update - I have recreated this without applying css (but including the .js file provided by the designer) the code still works fine which, there must be some sort of conflict between the CSS and my JavaScript?
That would lead me to believe that the tagName does not match, possibly because you have it in upper case. You might try while(!oNode.tagName.match(/body/i)) {
what happens if you add a line of debug code like this:
var oNode = element;
var iLeft = 0;
alert(oNode);
This might give different results in different browsers; I think it may be NULL for IE.
You may want to have a look at the code that provides the value of the 'element' parameter to see if there's a browser-dependant issue there.

Jquery .html, Firefox encodes qoutes in attributes

I have a hotfix app which generates HTML slides. The modules are built in Jquery with the background as inline CSS (the best solution i could come up with since they are unique per instance).
The problem is that firefox converts the quotes in the style attribute into:
<div style="background-image: url("bigspace-template.jpg");"
class="nuiOpenspace t1 skin1">
The webkit browsers have no issues with this.
They only way i have been able to get the background attribute is by:
// Build function, shortened
openspace.build = function(){
// ...
var bgstr = 'background-image: url('+ this.val_image + ')';
$o = $('<div class="nuiOpenspace"></div>').attr('style', bgstr);
// ...
}
This is then output appended to the document:
function Sandbox(){
var $sandbox = $("#sandbox");
this.fill = function(o) {
$sandbox.empty();
$sandbox.append(o);
};
// ...
}
I then get the HTML from the dom, convert to string and then output it in a textarea:
function Source(){
this.print = function(o, c_val){
//var parsed_html = this.parse(o, c_val);
//var pretty_html = "";
//pretty_html = style_html( parsed_html );
//console.info(x.replaceAll('&qout;', 'x'));
$code.text( style_html($("#sandbox").html()) );
};
}
var source = new Source();
I´ve tried search and replace but firefox keeps changing to / adding ". Any ideas?
As far as I know, the " is a ", so you have " inside something that is in between its own ". That can never work like this I think.
If you would've changed the origional code (the one that didn't work in firefox) to valid code (using either escapes or a combination of ' and " instead of "nested" "), wouldn't you be closer to a solution?

Categories