I am trying to use Javascript to transform part of a XML file to a single row of an HTML table. The thought is that I will create multiple rows in the table from multiple XML files. For Firefox and Opera, this nice little chunk of code works beautifully.
var resTable = document.createElement('table');
for (i = 0; i < xmlNames.length; i++)
{
// code for IE
if (window.ActiveXObject)
{
}
// code for Mozilla, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument)
{
xml=loadXMLDoc(xmlNames[i]);
xsl=loadXMLDoc(xslName);
xsltProcessor=new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
resultDocument = xsltProcessor.transformToFragment(xml,document);
resTable.appendChild(resultDocument);
}
}
document.getElementById("theDoc").appendChild(resTable);
The problem is that I have tried a thousand things in the "if IE" part, and nothing ever works. I've done a lot of googling, and browsing here before I asked, but to no avail. In fact, there is an unanswered question on SO that sounds very similar, but there were no responses and no resolution, so I was hoping someone would be able to help me out here..
I've been successful in getting an entire doc to transform on IE, but the fact that I want to do this as a fragment is whats causing my problem.. Any help would be much appreciated! Thanks!
Edit: Sorry I forgot to provide my loadXMLDoc function in case thats important. Here is is:
function loadXMLDoc(dname)
{
if (window.XMLHttpRequest)
{
xhttp=new XMLHttpRequest();
}
else
{
xhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xhttp.open("GET",dname,false);
xhttp.send("");
return xhttp.responseXML;
}
After much more trial and error, I came up with something that works. Here's what I did:
Create a xsltProcessor as usual, and call the transform method. This results in xsltProcessor.output being a HTML formatted string. Of course, I want a DOM element, so I had to convert the HTML string to a DOM. Luckily, because I'm the author of the XSL stylesheet too, I know exactly what I'm expecting to come back. In my case, the output HTML string would be some number of <tr>...</tr> elements. I initially tried setting my resTable (a table DOM element) innerHTML to the output string, but that did not work. I'm still not sure why, but it seems like it has something specific to do with the fact they were <tr>s and it wasn't able to be parsed when set to innerHTML outside the context of a table tag.
At any rate, I created a temporary div element and set ITs innerHTML to a string having the xsltProcessor's output string encased in a <table></table> tag. Now the temp div element is DOM table, which I then stepped through and grabbed just the child nodes (which are the tr nodes that the xsl processor returned in the first place). Seems kind of ridiculous to do all this, but it works, and thats the first time I can say that.. Here's the final version that works in all the browsers I've tested..
var resTable = document.createElement('table');
for (i = 0; i < xmlNames.length; i++)
{
// code for IE
if (window.ActiveXObject)
{
var xml = new ActiveXObject("Microsoft.XMLDOM");
xml.async = false;
xml.load(xmlNames[i]);
var xslt = new ActiveXObject("Msxml2.XSLTemplate");
var xsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.3.0");
var xsltProcessor;
xsl.async = false;
xsl.resolveExternals = false;
xsl.load(xslName);
xslt.stylesheet = xsl;
xsltProcessor = xslt.createProcessor();
xsltProcessor.input = xml;
//This transform results in one or more tr.../tr HTML tag(s)
xsltProcessor.transform();
//Create a temp div element which is used to convert the HTML
//string to a DOM element so I can grab just the part I want..
tmp = document.createElement('div');
//Can't set innerHTML to tr tags directly I guess, so have to put
//in context of a table so it can be parsed...
tmp.innerHTML = "<table>" + xsltProcessor.output + "</table>";
//Now I need to grad the tr children from inside the table node, since
//the table was only to please the parser
for (tmpChildInd = 0; tmpChildInd < tmp.childNodes[0].childNodes.length; tmpChildInd++)
{
//finally, append the temporary elements children (the tr tags)
//to the overall table I created before the loop.
resTable.appendChild(tmp.childNodes[0].childNodes[tmpChildInd]);
}
}
// code for Mozilla, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument)
{
xml=loadXMLDoc(xmlNames[i]);
xsl=loadXMLDoc(xslName);
xsltProcessor=new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
resultDocument = xsltProcessor.transformToFragment(xml,document);
resTable.appendChild(resultDocument);
}
}
//put the full table at the div location "theDoc" now..
document.getElementById("theDoc").appendChild(resTable);
Not sure how often folks try to do this, but hopefully this helps someone else out there..
Related
I have a dynamic page where, with a bar button, I can change the main div content.
Most of the pages are static except one, which contains JavaScript (RGraph charts).
That's why in order to make it working I use the following code:
var data = new FormData();
data.append( 'action', 'charts' );
// clean the content
var myNode = document.getElementById("contentView");
while (myNode.firstChild)
{
myNode.removeChild(myNode.firstChild);
}
// set the new content
var div = document.createElement("div");
var t = document.createElement('template');
t.innerHTML = _connectToServer( data );
for (var i=0; i < t.content.childNodes.length; i++)
{
var node = document.importNode(t.content.childNodes[i], true);
div.appendChild(node);
}
document.getElementById("contentView").appendChild(div);
The problem is that as far as I see (and I read) such a code is not compatible with Microsoft Edge, and I would like to make it going with Edge as well.
What's the best way to succeed?
Ok, so I took this over to our DOM team to better understand this interop issue.
This is actually a Chrome bug per spec. What happens is when you create a template element and place innerHTML inside of it, it is treated as a DocumentFragment. And a template element's contents can't have executable script.
Here are the relavent spec links that cover this:
InnerHTML
Template Element
To work around this, as I pointed to earlier you'll need to create a <script> tag not within a <template> element and utilize textContent. Here's an example of this: http://jsbin.com/gizayuyape/edit?html,js,output
Or here is the code:
// Using textContent
var body = document.getElementsByTagName('body')[0];
var s = document.createElement('script');
s.textContent = 'document.write("Script textContent");';
body.appendChild(s);
Good news, at least on the interop front, is that Chrome has fixed this issue starting in version 67:
I have a huge page with really lots of data. Sometimes I need to reload this data using Ajax, so ajax request:
if (window.XMLHttpRequest) {
this._request = new XMLHttpRequest();
} else {
this._request = new ActiveXObject("Microsoft.XMLHTTP");
Then, I access this data with request.responseXML. I faced a problem, when size of responsibility more than 800k characters.
IE doesnt generate responseXML, its just empty XMLObject, but in responseText everithing is OK. I`ve tried to parse XML using the following code:
if (request.responseXML) {
var responseXML = request.responseXML;
//TODO: for huge xml responses some versions of IE can't automaticly parse XML
if (O$.isExplorer && responseXML && !responseXML.firstChild) {
var originalResponseText = request.responseText;
if (window.DOMParser) {
var parser = new DOMParser();
responseXML = parser.parseFromString(originalResponseText, 'text/xml');
} else {
responseXML = new ActiveXObject("Microsoft.XMLDOM");
responseXML.async = false;
responseXML.loadXML(originalResponseText);
}
}
But I faced the same problem.
Then, I tried to parse XML using jQuery:
responseXML = jQuery.parseXml(request.responseXML);
But the problem is still the same, everything is fine when the response length small, but for huge response I still get empty XML object with the parse error inside.
errorCode : -2147467259
filepos : 814853
reason : “Unspecified error\r\n”;
I check those position inside response string and everything is correct, just some ordinal symbol. Also I’ve recheck XML lots of time and I am sure that it’s valid. I don’t know what to do at all.
Also I tried to write my own xml parser, but I think that this is problem has more simple solution.
Thanks in Advance.
It would seem that IE notoriously has difficulties parsing XML data, and there may not be that much you can really do about it. A work around is that you can try to parse the XML data with IE, and if it fails, construct a new parser that will construct XML data out of the plaintext (which as you mentioned will work seemingly regardless of size). Take a look at the similar problem and answer here. It boils down to (pseudocode)
function() {
var XMLdata = reponse.XMLdata;
If (XMLdata.error) {
var parser = construct new DOMParser;
XMLdata = parser.XMLparse(response.plaintext);
}
return XMLdata;
}
I understand I cannot save XML content to a local file, because of security restrictions. but is there a way I can show the XML content in another browser window, as
Window.Open(xmlString, . .. );
that would work the same as -
Window.Open(URL, . . .);
I cannot use server-side language.
I can use javaScript \ jQuery. (I already use them to create the XML)
I can have a template XML file, near my HTML. Is there a way to display the template file and change its content ? almost the same as window.open: is it possible open a new window with modify its DOM or How to write JavaScript to a separate window? but I need to change XML nodes, and not HTML.
EDIT 1: try using myXmlWindow.document.write(xmlString)
=> I tried the suggested code -
var xmlString = xml2Str(xmlDocument);
myXmlWindow = window.open();
myXmlWindow.document.write(xmlString);
myXmlWindow.focus();
but it does not display the whole XML content, just the intern node values. and the new window still display "Connecting..." as it did not finish loading the content (missing close tag ???)
maybe I need to tell it is XML content and not HTML ???
my xmlString :
<root><device1>Name</device1><device2/><device3><Temprature_1>23.5</Temprature_1><Temprature_2>23.4</Temprature_2><Temprature_3>23.4</Temprature_3><Temprature_4>23.3</Temprature_4><Temprature_5>23.2</Temprature_5></device3></root>
the displayed content:
Name23.523.423.423.323.2
EDIT 2: my code -
function xml2Str(xmlNode) {
try {
// Gecko- and Webkit-based browsers (Firefox, Chrome), Opera.
return (new XMLSerializer()).serializeToString(xmlNode);
}
catch (e) {
try {
// Internet Explorer.
return xmlNode.xml;
}
catch (e) {
//Other browsers without XML Serializer
// alert('Xmlserializer not supported');
return('Xmlserializer not supported');
}
}
return false;
}
function fShow_xml_in_win() {
var xmlDocument = $.parseXML("<root/>");
var dev1 = xmlDocument.createElement('device1');
var dev2 = xmlDocument.createElement('device2');
var dev3 = xmlDocument.createElement('device3');
dev1.appendChild(xmlDocument.createTextNode('Name'));
xmlDocument.documentElement.appendChild(dev1);
xmlDocument.documentElement.appendChild(dev2);
xmlDocument.documentElement.appendChild(dev3);
var i;
var xNode;
for (i = 0; i < 5; i++) {
xNode = xmlDocument.createElement('Temprature_' + (i+1));
xNode.appendChild(xmlDocument.createTextNode( "myVal " + ((i+1) * 10) ));
dev3.appendChild(xNode);
}
var xmlString = xml2Str(xmlDocument);
alert(xmlString);
xmlString = "<?xml version='1.0' ?>" + xmlString; // I do not know how to add this node using parseXML :(
alert(xmlString);
myXmlWindow = window.open();
myXmlWindow.document.write(xmlString);
myXmlWindow.document.close(); // !! EDIT 3
myXmlWindow.focus();
return false;
}
EDIT 3: solved the "connecting..." problem
I just needed to add myXmlWindow.document.close();
You can open a blank window and then write content to it as follows:
myWindow=window.open('','','width=200,height=100')
myWindow.document.write(xmlString);
myWindow.focus()
You may need to do some work to format your xmlString, but I think this approach will do what you want. If your xmlString is formatted, try adding:
<?xml version="1.0" ?>
to the start of your string.
My understanding from your post, are
1.(From your firts point)
you get xml from somewhere which is not your control. My suggestion is why don't you get as JSON?
2.(From your second point)
If those XML is created by you means, Why aren't you try to write those XML from reference?
For example:
var reference = window.open();
reference.document.write(<some string goes here>)
3.(From your third point)
As per understanding from your second point. You can create xml. So why are you changing after write the document?
Note: Generally XML is used for Server-to-server communication, JSON is used for Server-to-client(browser) communication.
I have a c program which outputs a number of lines to another c program which stuffs them in a PHP page which is loaded with AJAX. The problem is that the output is a number of lines, which is fine to look at, but which, when viewed as HTML, lack line breaks.
The initial thought I had was obviously to put line breaks in with the output. -- That worked fine, especially since I was using responseText to handle the AJAX output. Now I have discovered that along with the raw text, a bit of metadata also needs to be part of the AJAX response. I jumped over to using responseXML, only to find that the tags no longer worked correctly. At this point I could slog through any number of tutorials to figure out how to work some more complicated mechanism, but I really just want a hack. Could I embed the metadata in an html comment and use the DOM to dig it out (I looked and don't see a good method to get to comments using the dom...)? Could I use the xml directly as html somehow? Could I use CDATA in the xml document(this doesn't seem hopeful)? Could I just use newlines until the code reaches the webpage and then have JS insert the br tags?
I don't need any other formatting, just line breaks, and all this needs to do is work, the less complex the better.
How about using a XSLT stylesheet to format your incoming XML. Save the following as an .html file for an example. Sources : http://www.w3schools.com/xsl/xsl_client.asp & http://www.w3schools.com/dom/dom_parser.asp
<html>
<head>
<script>
//YOUR XML FROM AJAX
var XML = "<top><meta><itemone>test meta</itemone></meta><rows><row>line one</row><row>line two</row><row>line three</row></rows></top>";
//A stylesheet to format the lines that come back.
var XSLT = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><h2>Lines</h2><xsl:for-each select="descendant::row"><xsl:value-of select="self::*"/><br /></xsl:for-each></xsl:template></xsl:stylesheet>'
function loadXMLDoc(xml)
{
var tempXML;
//IE
if (window.ActiveXObject)
{
tempXML=new ActiveXObject("Microsoft.XMLDOM");
tempXML.loadXML(xml);
}
else if(window.DOMParser)
{
parser=new DOMParser();
tempXML=parser.parseFromString(xml,"text/xml");
}
return tempXML;
}
function displayResult()
{
var xmlDoc = loadXMLDoc(XML);
var xsltDoc = loadXMLDoc(XSLT);
// code for IE
if (window.ActiveXObject)
{
var ex=xmlDoc.transformNode(xsltDoc);
document.getElementById("example").innerHTML=ex;
}
// code for Mozilla, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument)
{
var xsltProcessor=new XSLTProcessor();
xsltProcessor.importStylesheet(xsltDoc);
var resultDocument = xsltProcessor.transformToFragment(xmlDoc,document);
document.getElementById("example").appendChild(resultDocument);
}
}
</script>
</head>
<body onload="displayResult()">
<div id="example" />
</body>
</html>
Thanks for all the good suggestions, but I eventually decided to just prepend a fixed number of descriptor bytes to each text response and then use the substring command to get either the descriptor bytes or the main text response.
This allows me to keep using the simpler response-text mechanism, and is otherwise uncomplicated.
I would have used custom headers but I realized that that would have required buffering the whole output in yet ANOTHER place since the php script actually only contains a single system() call, and has no idea what the C program behind it is doing.
uri = 'http://www.nytimes.com/';
searchuri = 'http://www.google.com/search?';
searchuri += 'q='+ encodeURIComponent(uri) +'&btnG=Search+Directory&hl=en&cat=gwd%2FTop';
req = new XMLHttpRequest();
req.open('GET', searchuri, true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) {
if(req.status == 200) {
searchcontents = req.responseText;
myHTML = searchcontents;
var tempDiv = document.createElement('div');
tempDiv.innerHTML = myHTML.replace(/<script(.|\s)*?\/script>/g, '');
parsedHTML = tempDiv;
sitefound = sc_sitefound(uri, parsedHTML);
}
}
};
req.send(null);
function sc_sitefound(uri, parsedHTML) {
alert(parsedHTML);
gclasses = parsedHTML.getElementsByClassName('g');
for (var gclass in gclasses) {
atags = gclass.getElementsByTagName('a');
alert(atags);
tag1 = atags[0];
htmlattribute1 = tag1.getAttribute('html');
if (htmlattribute1 == uri) {
sitefound = htmlattribute1;
return sitefound;
}
}
return null;
}
parsedHTML is a XULElement
gclasses is an HTMLCollection
if there are many divs of class G in the Google Directory search results, why are the g classes empty?
var tempDiv = document.createElement('div');
If you're in an XUL environment, that's not creating an HTML element node: it'll be an XUL element. Since the innerHTML property is exclusive to HTMLElement and not other XML Elements, setting innerHTML on tempDiv will do nothing (other than adding a custom property containing the HTML string). Consequently there are no elements with class ‘g’ inside tempDiv... there are no elements at all inside it.
If you have a plain HTML document loaded in the browser, you could try using content.document.createElement to get an HTML wrapper element on which innerHTML will be available. This still isn't a brilliant way to parse a whole page of HTML because the document in question might have <head> content you can't put in a div, and HTTP headers that you'll be throwing away. Probably better to load the target file into an HTMLDocument object of its own. A good way to do that would be using an iframe. See this page for examples of both these approaches.
tempDiv.innerHTML = myHTML.replace(/<script(.|\s)*?\/script>/g, '');
It's seven shades of not-a-good-idea to process HTML with regex; this could go wrong in many ways when Google slightly change their page markup. Let the browser do the job of parsing instead. Setting innerHTML does not cause script elements to be executed straight away (futher DOM manipulations can though); you can pick out the unwanted script elements later, if you need to. With the XUL iframe approach you can simply disable JavaScript on the iframe.
for (var gclass in gclasses) {
The for...in loop is for use against Objects used as mappings. It should not be used for iterating a sequence (such as Array, NodeList or in this case HTMLCollection) as it doesn't do what you might expect. For iterating sequences, stick to the standard C-style for (var i= 0; i<sequence.length; i++) loop.
You could also do with adding var declarations for all your other local variables.