Identify css selector string vs XPath string - javascript

I'm working on a small querying module (in js) for html and I want to provide a generic query(selector) function supporting both, css selectors and XPath selectors as string argument.
Regardless of how each kind of selection is done, my problem here is how to identify whether a given string is an xpath or a css selector. We can assume that the function would be something like this:
function query(selector){
selectorKind = identifySelectorKind(selector); // I want to know how to code this particular function
if(selectorKind==="css") return queryCss(selector);
if(selectorKind==="xPath") return queryXPath(selector); //Assume both functions exists and work
}
My first approach (given my limited knowledge of xPath queries) was to identify the query kind by checking if the first character is / (here I am assuming all relevant xPath queries begin with /)
So, identifySelectorKind would go a bit like this:
function identifySelectorKind(selector){
if (selector[0] === "/") return "xPath";
else return "css";
}
Note that I don't need to validate neither css nor xpath selectors, I only need an unambiguous way to differentiate them. Would this logic be enough? (in other words, all xPath selectors begin with / and no css selector begins the same way?), if not, is there a better way or some considerations I may want to know?

You can't necessarily. For example, * is a valid xpath and a valid css selector, but it matches a different set of elements in each.

If you're absolutely sure your XPath selector will always begin with /, then yes, it's fine. Note that an XPath selector doesn't have to begin with a /, but if yours always selects from the root, then it's fine.

Related

javascript matches against css string [duplicate]

Given:
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<a xlink:href="url"></a>
</svg>
</body>
Is it possible to use the HTML DOM's .querySelector() or .querySelectorAll() to select the link inside the SVG by the contents of its xlink:href attribute?
This works:
document.querySelector('a') // <a xlink:href="url"/>
These don't:
document.querySelector('[href="url"]') // null
document.querySelector('[xlink:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\\:href="url"]') // null
Is there a way of writing that attribute selector to make it 'see' the xlink:href?
Query selector can handle namespaces, but it gets tricky because
The syntax for specifying namespaces in CSS selectors is different from html;
The querySelector API doesn't have any method for assigning a namespace prefix (like xlink) to an actual namespace (like "http://www.w3.org/1999/xlink").
On the first point, the relevant part of the CSS specs allows you to specify no namespace (the default), a specific namespace, or any namespace:
#namespace foo "http://www.example.com";
[foo|att=val] { color: blue }
[*|att] { color: yellow }
[|att] { color: green }
[att] { color: green }
The first rule will match only elements with the attribute att in the "http://www.example.com" namespace with the value "val".
The second rule will match only elements with the attribute att regardless of the namespace of the attribute (including no namespace).
The last two rules are equivalent and will match only elements with the attribute att where the attribute is not in a namespace.
See this fiddle, paying attention to the fill styles (default, hover, and active):
https://jsfiddle.net/eg43L/
The Selectors API adopts the CSS selector syntax, but has no equivalent to the #namespace rule for defining a namespace. As a result, selectors with namespaces are not valid but the wildcard namespace token is valid:
If the group of selectors include namespace prefixes that need to be resolved, the implementation must raise a SYNTAX_ERR exception ([DOM-LEVEL-3-CORE], section 1.4).
This specification does not provide support for resolving arbitrary namespace prefixes. However, support for a namespace prefix resolution mechanism may be considered for inclusion in a future version of this specification.
A namespace prefix needs to be resolved if the namespace component is neither empty (e.g. |div), representing the null namespace, or an asterisk (e.g. *|div), representing any namespace. Since the asterisk or empty namespace prefix do not need to be resolved, implementations that support the namespace syntax in Selectors must support these.
(bold added)
Check out the fiddle again, this time paying attention to the console output. The command document.querySelector('[*|href="#url"]') returns the element you want.
One final warning: MDN tells me that IE8- do not support CSS namespaces, so this might not work for them.
Update 2015-01-31:
As #Netsi1964 pointed out in the comments, this doesn't work for custom namespaced attributes in HTML 5 documents, since HTML doesn't support XML namespaces. (It would work in a stand-alone SVG or other XML document including XHTML.)
When the HTML5 parser encounters an attribute like data:myAttribute="value" it treats that as a single string for the attribute name, including the :. To make things more confusing, it auto-lowercases the string.
To get querySelector to select these attributes, you have to include the data: as part of the attribute string. However, since the : has special meaning in CSS selectors, you need to escape it with a \ character. And since you need the \ to get passed through as part of the selector, you need to escape it in your JavaScript.
The successful call therefore looks like:
document.querySelector('[data\\:myattribute="value"]');
To make things a little more logical, I would recommend using all lower-case for your attribute names, since the HTML 5 parser will convert them anyway. Blink/Webkit browser will auto-lowercase selectors you pass querySelector, but that's actually a very problematic bug (in means you can never select SVG elements with mixed-case tag names).
But does the same solution work for xlink:href? No! The HTML 5 parser recognizes xlink:href in SVG markup, and correctly parses it as a namespaced attribute.
Here's the updated fiddle with additional tests. Again, look at the console output to see the results. Tested in Chrome 40, Firefox 35, and IE 11; the only difference in behavior is that Chrome matches the mixed-case selector.
[*|href] will match both html href and svg xlink:href, then use :not([href]) to exclude html href.
document.querySelectorAll('[*|href]:not([href])')
tested in chrome
Unfortunately not.
querySelector doesn't handle XML namespaces, so there is no easy way to do this that way. You can however use an XPath query.
var result = document.evaluate(
// Search for all nodes with an href attribute in the xlink namespace.
'//*[#xlink:href="url"]',
document,
function(prefix){
return {
xlink: "http://www.w3.org/1999/xlink"
}[prefix] || null;
},
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
var element = result.iterateNext();
If you need full cross-browser support, such as for IE, which does not have a document.evaluate, you can polyfill it with wicked-good-xpath.
Of course, depending on your usage, it may be easier to do this (which I think will work on IE):
var element = Array.prototype.filter.call(document.querySelectorAll('a'),
function(el){
return el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') === 'url';
})[0] || null;

JQuery: What's the difference between referencing an element using #[objectId] or [id=objectId]

Can anybody tell me what's the difference between referencing an element using #[objectId] or [id=objectId]?
The first one is very fast, as jQuery internally uses getElementById when it recognizes the pattern (using a regular expression).
The second one asks jQuery to iterate over all objects having an id. It's very slow. jQuery doesn't even stop iterating when it find one match in that case.
The only legitimate reason to use a [id... selector is when you don't just search by an exact id, for example you might want to search all elements whose id starts with "something" using $('[id^=something]').
Assuming you have a valid HTML (no reused id) and a valid id, you can still have problems with $('#'+someId) (for example when your id contains a quote, or anything that breaks Sizzle's pattern recognition system). In that case, use $(document.getElementById(someId)).
Following your comment : Yes, a "#" in an ID makes it impossible for Sizzle (jQuery's selector engine) to understand your selector. Sizzle uses the following regex :
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
and /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/.test('#som#thing') returns false.

JQuery multiple attributes in selection

I stumbled upon this form of selector. Notice the quotes, its two attributes.
$('#item1','#item2')
It seems to return only first element, which is different from $('#item1, #item2') result. I couldn't find any documentation on what exactly this does. Can somebody explain this or link to documentation with examples please
It's called context, and it's the same as find(), so this:
$('#item1','#item2')
would equal :
$('#item2').find('#item1');
in other words, it searched inside #item2 for an element with the ID #item1
To select both elements with ID's #item1 and #item2, you would do:
$('#item1, #item2')
notice the difference in quotes.
Selector in Jquery $(param) supports single string parameter and then it split parameter string and then do work for selecting element..
$('#item1','#item2') //treat first one param
$('#item1,#item2') //treat one param and splits passed string and will select both
You can specify any number of selectors to combine into a single result.
This multiple expression combinator is an efficient way to select disparate elements.
multiple-selector
multiple-selector-2
var list = $("div,p,span").map(function () {
return this.tagName;
}).get().join(", ");
$("b").append(document.createTextNode(list));

Filtering elements out of a jQuery selector

I have a page that selects all the elements in a form and serializes them like this:
var filter = 'form :not([name^=ww],[id$=IDF] *,.tools *)';
var serialized = $(filter).serialize();
This works, unless the form gets around 600+ elements. Then the user gets s javascript error saying that the script is running slow and may make their browsers unresponsive. It then gives them the option to stop running the script.
I have tried running the filters separately, I have tried using .not on the selectors, then serializing them, but I run into one of two problems. Either it runs faster without the error, but also does not filter the elements, or it does filter the elements and gives me the slow script error.
Any ideas?
With 600+ elements this is going to be dead slow. You need to offer Sizzle (jQuery's selector engine) some opportunities for optimisation.
First, consider the fact that jQuery can use the natively-supported querySelectorAll method (in modern browsers) if your selector complies with the CSS3 spec (or at least to the extent of what's currently supported in browsers).
With your case, that would mean passing only one simple selector to :not instead of 3 (1 simple, 2 complex).
form :not([name^=ww])
That would be quite fast... although you're not being kind to browsers that don't support querySelectorAll.
Look at your selector and think about how much Sizzle has to do with each element. First it needs to get ALL elements within the page (you're not pre-qualifying the :not selector with a tag/class/id). Then, on each element it does the following:
(assume that it exits if a result of a check is false)
Check that the parent has an ancestor with the nodeName.toLowerCase() of form.
Check that it does not have a name attribute starting with ww (basic indexOf operation).
Check that it does not have an ancestor with an id attribute ending in IDF. (expensive operation)
Check that it does not have an ancestor with a class attribute containing tools.
The last two operations are slow.
It may be best to manually construct a filter function, like so:
var jq = $([1]);
$('form :input').filter(function(){
// Re-order conditions so that
// most likely to fail is at the top!
jq[0] = this; // faster than constructing a new jQ obj
return (
!jq.closest('[id$=IDF]')[0]
// this can be improved. Maybe pre-qualify
// attribute selector with a tag name
&& !jq.closest('.tools')[0]
&& this.name.indexOf('ww') !== 0
);
});
Note: that function is untested. Hopefully you get the idea...
Could you maybe just serialize the whole form and do your filtering on the backend? Also, why-oh-why is the form growing to 600+ fields?
use the :input selector to only select applicable elements..

What does $$ mean in Javascript?

I am looking at some javascript code and it has this in a function:
$$('.CssClass').each(function(x) { .... } )
I get that the intent is to apply the anonymous function to each element with a class of CssClass, but I can't work what the $$ refers to ... and can't google for $$!
Update: thanks for the hints. The javascript comes from the iPhone look-alike library: jPint which includes the prototypejs library, and does define $$ as:
function $$() {
return Selector.findChildElements(document, $A(arguments));
}
Probably this prototype function:
$$(cssRule...) -> [HTMLElement...]
Takes an arbitrary number of CSS
selectors (strings) and returns a
document-order array of extended DOM
elements that match any of them.
http://www.prototypejs.org/api/utility#method-$$
$ is an ordinary symbol character, thus "$", "$$", "$$$" are ordinary variables.
the meaning of $ depends upon the libraries that are in use; in jQuery the $-function creates a jquery object from a css selector, e.g. $("DIV") is a collection of all DIVs in the current document.
Are you looking at a library such as mootools by chance? This is used as a short-hand to certain types of objects by accessing the DOM. They do things like $('myElement') to access page elements for example.
$ is a valid function name in javascript. So something defines a function $$ that takes a string looking for some class called .CssClass and returns a object where you call each on.
I know that jQuery defines a function called $ at least that does similar things.
Any chance you are looking at a MooTools script?
http://www.consideropen.com/blog/2008/08/30-days-of-mootools-12-tutorials-day-2-selectors/ (now owned by a domain grabber)
"The $$ lets you quickly select multiple elements and places them into an array (a type of list that lets you manipulate, retrieve, and reorder the list in all sorts of ways). You can select elements by name (such as div, a, img) or an ID, and you can even mix and match."
Most likely a shorthand function name that handles the DOM accessing of the specified arguments, whether tag name or object id.
As per above, you're likely in MooTools or jQuery.
In the browser's console, it is another way to write querySelectorAll().
Simply selects all the elements on the web page that you need and puts them in an array.
Practical examples:
Select all the elements and set an outline guide for debugging layouts [source]:
$$('*').map((A,B)=>A.style.outline=`1px solid hsl(${B*B},99%,50%`)
Print the image addresses for all the images on a webpage [source]
$$('img').forEach(img => console.log(img.src))

Categories

Resources