How to alter DOM with xmldom by XPath in node.js? - javascript

I am trying to alter a DOM structure in node.js. I can load the XML string and alter it with the native methods in xmldom (https://github.com/jindw/xmldom), but when I load XPath (https://github.com/goto100/xpath) and try to alter the DOM via that selector, it does not work.
Is there another way to do this out there? The requirements are:
Must work both in the browser and server side (pure js?)
Cannot use eval or other code execution stuff (for security)
Example code to show how I am trying today below, maybe I simply miss something basic?
var xpath = require('xpath'),
dom = require('xmldom').DOMParser;
var xml = '<!DOCTYPE html><html><head><title>blah</title></head><body id="test">blubb</body></html>';
var doc = new dom().parseFromString(xml);
var bodyByXpath = xpath.select('//*[#id = "test"]', doc);
var bodyById = doc.getElementById('test');
var h1 = doc.createElement('h1').appendChild(doc.createTextNode('title'));
// Works fine :)
bodyById.appendChild(h1);
// Does not work :(
bodyByXpath.appendChild(h1);
console.log(doc.toString());

bodyByXpath is not a single node. The fourth parameter to select, if true, will tell it to only return the first node; otherwise, it's a list.

As aredridel states, .select() will return an array by default when you are selecting nodes. So you would need to obtain your node from that array.
You can also use .select1() if you only want to select a single node:
var bodyByXpath = xpath.select1('//*[#id = "test"]', doc);

Related

Input File element tag is not working in internal Web tool based on IE11

I am developing a web page in an internal tool that uses IE11. The tool has different infrastructure where not all native Javascript code works. We have included the jQuery library.
The issue is with reading the file from the file input element. After I browse and select a file, the code is able to read the inputFile element with no issues:
var selectedFile = $('#inputFile');
However, it does not find any files under this element:
if (selectedFile.files.length > 0)
I tried other alternatives which do not work too:
var input = document.getElementById('inputFile');
var file = input.files[0];
OR
var x = document.getElementById("inputFile");
if ('files' in x) {​​​​​​​​}
selectedFile is a jQuery object, whereas the files collection only exists on the Element object within that.
Either of these approaches will retrieve the files collection from the Element within the jQuery object:
// #1
var $selectedFile = $('#inputFile');
if ($selectedFile[0].files.length > 0) {
// do something...
}
// #2
var $selectedFile = $('#inputFile');
if ($selectedFile.prop('files').length > 0) {
// do something...
}
Note that I prefixed the selectedFile variable with a $. This is standard practice when working with jQuery as it makes it clear that the variable holds a jQuery object, not an Element.

Convert HTML tags to WordML with JavaScript

Do you know any way to convert HTML tags to WordML only using JavaScript. I need to get the content of a DOM element and convert what is inside to WordML.
Looking on npm there doesn't seem to be a library for this already.
So I think you're going to have to make your own. That being said, WordML is just a particular flavor of XML, right? This is the WordML you are referring to?
So to get the content of of a DOM element is pretty easy, you can do that with jQuery.
var ele = $('#wordml-element');
From there you will now want to convert it into WordML compatible XML. You could try using the xml library on npm for this.
So you will be transforming tree structured DOM elements into tree structured XML elements. The recommended pattern for doing this is known as the Visitor Pattern.
From there you will be left with an XML structure which you can further manipulate using the same pattern. At the end you will convert the XML structure into a string and that is what you will save to a file.
Now since I don't really know the structure of the HTML you are trying to convert into WordML I can only give you a very general code solution to the problem, which may look something like this:
var xml = require('xml')
function onTransformButtonClick() {
var options = {} // see documentation
var ele = $('#wordml-element')[0]
var wordml = transformElement(ele)
var text = xml(wordml, options);
fileSave(text);
}
function transformElement(ele) {
switch(ele.tagName) { // You could use attributes or whatever
case 'word-document':
return transformDocument(ele);
case 'word-body':
return transformBody(ele);
case 'word-p':
return transformParagraph(ele);
case 'word-r':
return transformRun(ele);
case 'word-text':
return transformText(ele);
}
}
function transformDocument(ele) {
var wordDocument = xml.element({...})
ele.childNodes.forEach(function (child) {
wordDocument.push(transformElement(child))
})
return [wordDocument]
}
function transformBody(ele) {
// create new element via xml library...
}
function transformParagraph(ele) {
// create new element via xml library...
}
function transformRun(ele) {
// create new element via xml library...
}
function transformText(ele) {
// create new element via xml library...
}
The specific implementations of which I will leave up to you since I don't know enough details to give you a more detailed answer.

Alfresco Activiti script task listener current scriptnode?

Is there a way to get the ScriptNode that initiated a state in an Activiti workflow in Alfresco? I have a ScriptTask in my workflow, and it has a Alfresco Script Listener set up for the Start event. When the script is called, I'd like the ScriptNode that transitioned into the ScriptTask in the workflow to be passed as a parameter to the function defined as the listener. Is that possible?
Editing for clarity:
Here's a screenshot of Eclispe with the Activiti plugin.
http://i.imgur.com/DAKtq.jpg
This workflow gets started by another workflow with webscripts.
var props = new Object();
var dd = new Date();
props[EL_PROP_WORK_UNIT_NAME] = "testNode" + DateISOString( dd );
props[EL_PROP_WORK_UNIT_SOURCE_CODE] = "ROB";
props[EL_PROP_WORK_UNIT_DELIVERY_DATE] = dd;
node = getHome().createNode(name, EL_TYPE_WORK_UNIT, props);
var EL_WORKFLOW = "activiti$The-workflow";
var activeWfs = node.activeWorkflows;
if( activeWfs === null || activeWfs.length === 0 )
{
var workflowPackage = workflow.createPackage();
workflowPackage.addNode( node );
var workflowDef = workflow.getDefinitionByName(EL_WORKFLOW);
var workflowPath = workflowDef.startWorkflow( workflowPackage, new Object());
}
So the listener calls another javascript method...
function artPDFRename()
{
logger.log("==============================");
logger.log("<START> artPDFRename");
var workflowDef = workflow.getDefinitionByName(EL_WORKFLOW);
var activeInstance = workflowDef.getActiveInstances();
// ????
}
The goal is to have this handling be automatic. We're trying to design this with as little of manual intervention as possible, and are not assigning tasks to users to perform. Yes, there's probably another way to rename a PDF file, but I can't seem to figure out from the documentation listed here how to get a pointer to the node I put in the bpm_package object. That's the question.
Or am I so far off base on how we're developing this that it makes no sense?
As an example check the ScriptTaskListener class. Here all the workflow variables are put in a map.
The following code is interesting:
// Add all workflow variables to model
Map variables = delegateTask.getExecution().getVariables();
for (Entry<String, Object> varEntry : variables.entrySet())
{
scriptModel.put(varEntry.getKey(), varEntry.getValue());
}
So with this you could use bpm_package as an object within your script within the workflow script task.
So if you need the node were the workflow has run on, the following code should work (where task is your delegateTask from your notify method of the Listener:
delegateTask.getVariable("bpm_package");
// or like the example above
delegateTask.getExecution().getVariable("bpm_package");
This will be a list so take the first one and that will be your node.
---------update
If you're using the javascript from alfresco then you can directly use the parent object bpm_package.
So in your case it would be best to do the following:
var node = bpm_package.children[0]; //or you could check if the
package isn't null
// Then send the node into your
artPDFRename(node); //or you could just add the bpm_package code in
your js file

Cannot Execute Javascript XPath queries on created document

Problem
I'm creating a document with javascript and I'd like to execute XPath queries on this document.
I've tried this in safari/chrome
I've read up on createDocument / xpath searches and it really seems like this code should work
At this point it seems like it may be a webkit bug
My requirements:
I can use innerHTML() to setup the document
I can execute xpath searches w tagnames
The code:
If you copy/paste the following into the webkit inspector, you should be able to repro.
function search(query, root) {
var result = null;
result = document.evaluate(query, root, null, 7,null);
var nodes = [];
var node_count = result.snapshotLength;
for(var i = 0; i < node_count; i++) {
nodes.push(result.snapshotItem(i));
}
return nodes;
}
x = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', 'HTML');
body = x.createElement('body');
body.innerHTML = "<span class='mything'><a></a></span>";
xdoc = x.documentElement; //html tag
xdoc.appendChild(body);
console.log(search(".", xdoc)); // --> [<html>​…​</html>​]
console.log(search("/*", xdoc)); // --> [<html>​…​</html>​]
console.log(search("/html", xdoc)); // --> []
Best Guess
So I can definitely search using XPath, but I cannot search using tagnames. Is there something silly I'm missing about the namespace?
Have you tried:
console.log(search("//html", xdoc));
I'm not familiar with Safari specifically, but the problem might be that Safari is adding another node above HTML or something. If this was the case, the first two queries might be showing you that node plus it's children, which would make it look like they're working properly, while the third query would fail because there wouldn't be a root=>HTML node.
Just a thought.

jQuery won't parse xml with nodes called option

I'm using jQuery to parse some XML, like so:
function enumOptions(xml) {
$(xml).find("animal").each(function(){
alert($(this).text());
});
}
enumOptions("<root><animal>cow</animal><animal>squirrel</animal></root>");
This works great. However if I try and look for nodes called "option" then it doesn't work:
function enumOptions(xml) {
$(xml).find("option").each(function(){
alert($(this).text());
});
}
enumOptions("<root><option>cow</option><option>squirrel</option></root>");
There's no error, just nothing gets alerted, as if the find isn't finding anything. It only does it for nodes called option everything else I tested works ok!
I'm using the current version of jQuery - 1.4.2.
Anyone any idea?
TIA.
bg
Update
jQuery has this method built-in now. You can use
$.parseXML("..")
to construct the XML DOM from a string.
jQuery relies on the HTML DOM using innerHTML to parse the document which can have unreliable results when tag names collide with those in HTML.
Instead, you can use a proper XML parser to first parse the document, and then use jQuery for querying. The method below will parse a valid XML document in a cross-browser fashion:
// http://www.w3schools.com/dom/dom_parser.asp
function parseXML(text) {
var doc;
if(window.DOMParser) {
var parser = new DOMParser();
doc = parser.parseFromString(text, "text/xml");
}
else if(window.ActiveXObject) {
doc = new ActiveXObject("Microsoft.XMLDOM");
doc.async = "false";
doc.loadXML(text);
}
else {
throw new Error("Cannot parse XML");
}
return doc;
}
Once the XML DOM is constructed, jQuery can be used as normal - http://jsfiddle.net/Rz7Uv/
var text = "<root><option>cow</option><option>squirrel</option></root>";
var xml = parseXML(text);
$(xml).find("option"); // selects <option>cow</option>, <option>squirrel</option>
This is probably some special handling for the HTML <option> element, but I can't find that in the source.
On line 4448 of the unminified source for 1.4.2 is the culprit:
// ( div = a div node )
// ( elem = the xml you've passed to it )
div.innerHTML = wrap[1] + elem + wrap[2];
Consider this code:
var d = document.createElement('div');
d.innerHTML = "<foo><option>bar</option><b>blah</b></foo>";
alert(d.innerHTML); // <foo>bar<b>blah</b></foo>
// tested on Firefox 3.6
So, don't ask me why exactly, but it looks like something in the way the DOM handles it, not necessarily jQuery's fault.
Perhaps just use a different node name?

Categories