On an already loaded HTML page I want to load a XML file and append the resultig HTML to the loaded page. The XML file is linked with an XSL file. When I open it in the browser (Firefox) the XSL transformation is correctly applied. The result is HTML without html, head and body tags. That’s what I want.
Loading the XML with Ajax gives me the untransformed XML, not the HTML I wish.
My workaround so far is to load the XML file in a hidden iframe. In there the XSL transformation is correctly carried out.
I now fail to copy the HTML form the iframe and append it to an existing div in the page outside the iframe. I only manage to:
Get an HTML Collection (with jquery or plain JS):
iframe = $('#iframe');
iframeHtml = iframe.contents()[0].children;
iframe = document.getElementById('bausteine__liste__zwischenspeicher');
resultHtml = iframe.contentDocument || temp.contentWindow.document;
Get all HTML but only starting below the first div (with jquery). The uppermost div which contains everything is left out.
iframe = $('#iframe');
resultHtml = iframe.contents().find('div').html();
This is the iframe as I see it in the dev toolbar of Firefox
<iframe id="iframe" src="file-with-xslt.xml">
<div class="uppermost-element">
<div>
…
</div>
<div>
…
</div>
</div>
</iframe>
How can I get the HTML including the uppermost element? Either via the iframe or – even better – via Ajax.
Thanks for your help!
The solution is to load both the XML and XSL with an XMLHttpRequest and to do the transformation with Javascript. documentElement.outerHTML will output the resulting HTML.
function XMLtransformation(xsl, xml) {
var xslStylesheet;
var xsltProcessor = new XSLTProcessor();
var xmlDoc;
// load XSL file
var myXMLHTTPRequest = new XMLHttpRequest();
myXMLHTTPRequest.open('GET', urlXSL, false);
myXMLHTTPRequest.send(null);
xslStylesheet = myXMLHTTPRequest.responseXML;
xsltProcessor.importStylesheet(xslStylesheet);
// load XML file
myXMLHTTPRequest = new XMLHttpRequest();
myXMLHTTPRequest.open('GET', urlXML, false);
myXMLHTTPRequest.send(null);
xmlDoc = myXMLHTTPRequest.responseXML;
var htmlDocument = xsltProcessor.transformToDocument(xmlDoc, document);
return htmlDocument.documentElement.outerHTML;
}
XMLtransformation('file.xsl', 'file.xml');
I liked #Moritz's solution above, but it needed a bit of tweaking so here's my version:
function XMLtransformation(xslUrl, xmlUrl) {
const errorMessage = 'Unable to load the content';
const parser = new DOMParser();
// attempt to load the XSL file
const xslRequest = new XMLHttpRequest();
xslRequest.open('GET', xslUrl, false); // `false` makes the request synchronous
xslRequest.send(null);
if (xslRequest.status < 300) {
const xslStylesheet = parser.parseFromString(xslRequest.response, "application/xml");
const xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xslStylesheet);
const xmlRequest = new XMLHttpRequest();
xmlRequest.open('GET', xmlUrl, false);
xmlRequest.send(null);
if (xmlRequest.status < 300) {
const htmlDocument = xsltProcessor.transformToDocument(
parser.parseFromString(xmlRequest.response, "application/xml"),
document
);
return htmlDocument.documentElement.outerHTML;
} else {
console.error('xml load failure:');
console.error(xmlRequest.status, xmlRequest.responseText);
}
} else {
console.error('xsl load failure:');
console.error(xslRequest.status, xslRequest.responseText);
}
return errorMessage;
}
Related
When a user clicks on a link instead of loading a whole new page I load the new page's HTML data through an ajax request (and also with a query string I get the server to not send the nav bar data each time) the resulting data from the ajax request I then put through DOMParser to allow me to just get the content from the div with the id of "content" and replace the current document's "context" div's innerHTML.
After doing a request through this method though any script tags within the newDOM don't run after being put in the content div. Also, it does appear to run while it is in newDOM either, because if you have a script that instantly edits the document while it loads there is no effect when you log out newDOM
AjaxRequest(href, function(data) {
var parser = new DOMParser();
var newDOM = parser.parseFromString(data.responseText, "text/html");
//Setup new title
var title = '';
if (newDOM.getElementsByTagName('title').length > 0 && newDOM.getElementsByTagName('title')[0] !== null) {
title = newDOM.getElementsByTagName('title')[0].innerHTML;
} else {
title = rawhref;
}
document.title = title;
history.pushState({}, title, rawhref);
if (newDOM.getElementById('content') === null) {
//If there is an error message insert whole body into the content div to get full error message
document.getElementById('content').appendChild(newDOM.getElementsByTagName('body')[0]);
} else {
document.getElementById('content').appendChild(newDOM.getElementById('content'));
}
MapDOM();
if (typeof(onPageLoad) == "function") {
onPageLoad();
}
});
Note: the variable "rawhref" is just the request URL without ?noheader so that it will be easier for users to go back though their history.
NOTE: Also after any new load I also have a function that overwrites any new a tag so that it will work though this method for the next new page.
Also, it would be much preferred if the answer didn't use jQuery.
Some one just answered this and while I was testing it they deleted their solution.... Um, thanks so much who ever you were, and for anyone in the future who has this problem here is the code they showed, but I didn't have time to fully understand why it worked.... but I think can work it out.
function subLoader(dest, text) {
var p = new DOMParser();
var doc = p.parseFromString(text, 'text/html');
var f = document.createDocumentFragment();
while (doc.body.firstChild) {
f.appendChild(doc.body.firstChild);
}
[].map.call(f.querySelectorAll('script'), function(script) {
var scriptParent = script.parentElement || f;
var newScript = document.createElement('script');
if (script.src) {
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
}
scriptParent.replaceChild(newScript, script);
});
dest.appendChild(f);
}
I've a menu on the left side of a frame.
I'm trying to call a javascript that will load a new html in the right side of the frame generated from XML data and XSL template.
The code to do it in one page with fixed parameters works fine. The two issues I struggle with is:
How to make it flexible so I can use a different xml data and template on the call to the script
How to present the data in the right hand frame pane.
This is the code for the left hand pane:
<html>
<head>
<script>
function loadXMLDoc(filename)
{
if (window.ActiveXObject)
{
xhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
else
{
xhttp = new XMLHttpRequest();
}
xhttp.open("GET", filename, false);
try {xhttp.responseType = "msxml-document"} catch(err) {} // Helping IE11
xhttp.send("");
return xhttp.responseXML;
}
function displayResult(xmlid,xslid)
{
xml = loadXMLDoc("XMLDATA/xmlid");
xsl = loadXMLDoc("XMLDATA/xslid");
// code for IE
if (window.ActiveXObject || xhttp.responseType == "msxml-document")
{
ex = xml.transformNode(xsl);
document.getElementById("example").innerHTML = ex;
}
// code for Chrome, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument)
{
xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
resultDocument = xsltProcessor.transformToFragment(xml, document);
document.getElementById("example").appendChild(resultDocument);
}
}
</script>
</head>
<body>
<p>TEST</p>
<p>TEST</p>
<div id="example" />
</body>
</html>
If I omit the both arguments in the script call:
TEST
and assign the data sources in the parameters here:
xml = loadXMLDoc("XMLDATA/cdcatalog.xml");
xsl = loadXMLDoc("XMLDATA/cdcatalog.xsl");
Then it works on the same page.
I'm not a programmer so some help would be great.
You're not using the variables that you're passing in, you're simply using static text.
At the start of function displayResult(xmlid,xslid) change...
xml = loadXMLDoc("XMLDATA/xmlid");
xsl = loadXMLDoc("XMLDATA/xslid");
To...
xml = loadXMLDoc(xmlid);
xsl = loadXMLDoc(xslid);
Update - in response to the OPs comments...
If you want to move elements around the page, I would seriously consider looking at jquery which will easily allow you to detach the items and append them to another area.
For example...
$(function(){
var $rightArea = $("#rightarea");
$("#leftarea *").each(function(){
$(this).detach().appendTo($rightArea);
});
});
Hi I have markup sent to me from a server and I set it as the innerHTML of a div element for the purpose of traversing the tree, finding image nodes, and changing their src values. Is there a way to prevent the original src value from being downloaded?
Here is what I am doing
function replaceImageSrcsInMarkup(markup) {
var div = document.createElement('div');
div.innerHTML = markup;
var images = div.getElementsByTagName('img');
images.forEach(replaceSrc);
return div.innerHTML;
}
The problem is that in browsers as soon as you do:
var img = document.createElement('img'); img.src = 'someurl.com' the browser fires off a request to someurl.com. Is there a way to prevent this without resorting to parsing the markup myself? If there is in no other way does anyone know a good way of parsing the markup with as little code as possible to accomplish my goal?
I know you are already happy with your solution, but I think it would be worth sharing a safe method for future users.
You can now simply use the DOMParser object to generate an external document from your HTML string, instead of using a div created by your current document as container.
DOMParser specifically avoids the pitfalls mentioned in the question and other threats: no img src download, no JavaScript execution, even in elements attributes.
So in your case you can safely do:
function replaceImageSrcsInMarkup(markup) {
var parser = new DOMParser(),
doc = parser.parseFromString(markup, "text/html");
// Manipulate `doc` as a regular document
var images = doc.getElementsByTagName('img');
for (var i = 0; i < images.length; i += 1) {
replaceSrc(images[i]);
}
return doc.body.innerHTML;
}
Demo: http://jsfiddle.net/94b7gyg9/1/
Note: with your current code, browsers will still try downloading the resource initially specified in your img nodes src attribute, even if you change it before the end of JS execution. Trace network transactions in this demo: http://jsfiddle.net/94b7gyg9/
Rather than append the new markup to the DOM before you change the img sources, create an element, set it's inner HTML, change the source of the images and then finally, append the changed markup to the page.
Here's a fully-worked sample.
<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
//function allByClass(className,parent){return (parent == undefined ? document : parent).getElementsByClassName(className);}
function allByTag(tagName,parent){return (parent == undefined ? document : parent).getElementsByTagName(tagName);}
function newEl(tag){return document.createElement(tag);}
//function newTxt(txt){return document.createTextNode(txt);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
byId('goBtn').addEventListener('click', onGoBtnClick, false);
}
var dummyString = "<img src='img/girl.png'/><img src='img/gfx07.jpg'/>";
function onGoBtnClick(evt)
{
var div = newEl('div');
div.innerHTML = dummyString;
var mImgs = allByTag('img', div);
for (var i=0, n=mImgs.length; i<n; i++)
{
mImgs[i].src = "img/murderface.jpg";
}
document.body.appendChild(div);
}
</script>
<style>
</style>
</head>
<body>
<button id='goBtn'>GO!</button>
</body>
</html>
You could directly parse the markup string using a regex to replace the img src. Searching for all the img src urls in the string and then replacing them with the new url.
var regex = /<img[^>]+src="?([^"\s]+)"?\s*\/>/g;
var imgUrls = [];
while ( m = regex.exec( markup ) ) {
imgUrls.push( m[1] );
}
imgUrls.forEach(function(url) {
markup = markup.replace(url,'new-url');
});
Another solution might be, if you have access to it, to set the all the img src to an empty string, and put the url in in a data-src attribute. Having your markup string look like something like this
markup = '
';
Then setting this markup to your div.innerHTML won't trigger any download from the browser. And you can still parse it using regular DOM selector.
div.innerHTML = markup;
var images = div.getElementsByTagName('img');
images.forEach(function(img){
var oldSrc = img.getAttribute('data-src');
img.setAttribute('src', 'new-url');
});
My goal is to get the text from a HTML document which does not call any functions from my .jsp file.
I've looked around and I thought I had found the answer to my problem but it doesn't seem to be working, and other answers consist of using jQuery (which I am both unfamiliar with and not allowed to use).
This is my code so far:
function getText(divID) {
var w = window.open("test.html");
var body = w.document.body;
var div = document.getElementById(divID);
var textContent = body.textContent || body.innerText;
console.log(textContent);
//div.appendChild(document.createTextNode(textContent));
}
So as you can see, I'm trying to get the body of one HTML document and have it appear in another. Am I on the right tracks?
EDIT: Ok so I seem to have made my problem quite confusing. I call the function in a HTML document called html.html, but I want to get the text from test.html, then have it appear in html.html. It has to be like this because I can't assume that the HTML document I want to read from will include my .jsp file in its head.
At the moment I am getting the following error.
Uncaught TypeError: Cannot read property 'body' of undefined
The reason document.body in the other window is undefined, is because the other window has not loaded and rendered the document yet.
One solution would be to wait for the onload event.
function getText(divID) {
var w = window.open("test.html");
w.addEventListener("load", function() {
var body = w.document.body;
var div = document.getElementById(divID);
var textContent = body.textContent || body.innerText;
console.log(textContent);
});
}
Make sure you run the getText function on a user event like a click, else window.open will fail.
If all you want to do is get the contents of the other window, using AJAX would probably be a better option.
function getText(divID) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 ) {
var body = xhr.response.body;
var div = document.getElementById(divID);
var textContent = body.textContent || body.innerText;
console.log(textContent);
}
};
xhr.open("GET", "test.html", true);
xhr.responseType = "document";
xhr.send();
}
I need to get whole content of iframe from the same domain. Whole content means that I want everything starting from <html> (including), not only <body> content.
Content is modified after load, so I can't get it once again from server.
I belive I've found the best solution:
var document = iframeObject.contentDocument;
var serializer = new XMLSerializer();
var content = serializer.serializeToString(document);
In content we have full iframe content, including DOCTYPE element, which was missing in previous solutions. And in addition this code is very short and clean.
If it is on the same domain, you can just use
iframe.contentWindow.document.documentElement.innerHTML
to get the content of the iframe, except for the <html> and </html> tag, where
iframe = document.getElementById('iframeid');
$('input.test').click(function(){
$('textarea.test').text($('iframe.test').contents());
});
You can get the literal source of any file on the same domain with Ajax, which does not render the html first-
//
function fetchSource(url, callback){
try{
var O= new XMLHttpRequest;
O.open("GET", url, true);
O.onreadystatechange= function(){
if(O.readyState== 4 && O.status== 200){
callback(O.responseText);
}
};
O.send(null);
}
catch(er){}
return url;
}
function printSourceCode(text){
var el= document.createElement('textarea');
el.cols= '80';
el.rows= '20';
el.value= text;
document.body.appendChild(el);
el.focus();
}
fetchSource(location.href, printSourceCode);