Asked  7 Months ago    Answers:  5   Viewed   30 times

I want to select just a class on its own called .date

For some reason, I cannot get this to work. If anyone knows what is wrong with my code, it would be much appreciated.

@$doc = new DOMDocument();
@$doc->loadHTML($html);
$xml = simplexml_import_dom($doc); // just to make xpath more simple
$images = $xml->xpath('//[@class="date"]');                             
foreach ($images as $img)
{
    echo  $img." ";
}

 Answers

45

I want to write the canonical answer to this question because the answer above has a problem.

Our problem

The CSS selector:

.foo

will select any element that has the class foo.

How do you do this in XPath?

Although XPath is more powerful than CSS, XPath doesn't have a native equivalent of a CSS class selector. However, there is a solution.

The right way to do it

The equivalent selector in XPath is:

//*[contains(concat(" ", normalize-space(@class), " "), " foo ")]

The function normalize-space strips leading and trailing whitespace (and also replaces sequences of whitespace characters by a single space).

(In a more general sense) this is also the equivalent of the CSS selector:

*[class~="foo"]

which will match any element whose class attribute value is a list of whitespace-separated values, one of which is exactly equal to foo.

A couple of obvious, but wrong ways to do it

The XPath selector:

//*[@class="foo"]

doesn't work! because it won't match an element that has more than one class, for example

<div class="foo bar">

It also won't match if there is any extra whitespace around the class name:

<div class="  foo ">

The 'improved' XPath selector

//*[contains(@class, "foo")]

doesn't work either! because it wrongly matches elements with the class foobar, for example

<div class="foobar">

Credit goes to this fella, who was the earliest published solution to this problem that I found on the web: http://dubinko.info/blog/2007/10/01/simple-parsing-of-space-seprated-attributes-in-xpathxslt/

Wednesday, March 31, 2021
 
KingCrunch
answered 7 Months ago
10

You wrote that you wanted the length of the result set of the following query:

$queryResult = $xpathvar->query('//item/title');

I assume that $xpathvar here is of type DOMXPath. If so, it has a length property as described here. Instead of using foreach, simply use:

$length = $xpathvar->query('//item/title')->length;

Now I want to pick the text node values for //channel/item/title

Which you can get with the expression //channel/item/title/text().

and href value for //channel/item/description/table/tr/td[1]/a[1] (with a text node value = "[link]")

Your expression here selects any tr, the first td under that, then the first a. But the first a does not have a value of "[link]" in your source. If you want that, though, you can use:

//channel/item/description/table/tr/td[1]/a[1]/@href

but it looks like you rather want:

//channel/item/description/table/tr/td/a[. = "[link]"][1]/@href

which finds the first a element in the tree that has the value (text node) that is "[link]".

Above in 2nd case, I am looking for the value of 2nd a (with a text node value = "[link]"), inside 2nd td inside tr, table, description, item, channel.

Not sure if this was a separate question or meant to explain the previous one. Regardless, the answer the same as in the previous one, unless you explicitly want to search for 2nd a etc (i.e., search by position), in which case you can use numeric predicates.


Note: you start most of your expressions with //expr, which essentially means: search the whole tree at any depth for the expression expr. This is potentially expensive and if all you need is a (relative) root node for which you know the starting point or expression, it is better, and far more performant, to use a direct path. In your case, you can replace //channel for /*/channel (because it is the first under the root element).

Saturday, May 29, 2021
 
TheLovelySausage
answered 5 Months ago
56

This selector should work but will be more efficient if you replace it with your suited markup:

//*[contains(@class, 'Test')]

Or, since we know the sought element is a div:

//div[contains(@class, 'Test')]

But since this will also match cases like class="Testvalue" or class="newTest", @Tomalak's version provided in the comments is better:

//div[contains(concat(' ', @class, ' '), ' Test ')]

If you wished to be really certain that it will match correctly, you could also use the normalize-space function to clean up stray whitespace characters around the class name (as mentioned by @Terry):

//div[contains(concat(' ', normalize-space(@class), ' '), ' Test ')]

Note that in all these versions, the * should best be replaced by whatever element name you actually wish to match, unless you wish to search each and every element in the document for the given condition.

Tuesday, June 1, 2021
 
wavyGravy
answered 5 Months ago
99

Is jquery an option? Hopefully so, because this does what you want:

http://api.jquery.com/toggle/

$(".class").toggle();
or
$(".class").show();  $(".class").hide();
Thursday, August 5, 2021
 
o_flyer
answered 3 Months ago
92

Yes, you're on the right track with XPath -- it's ideal for selecting parts of an XML document.

For example, for this XML,

<r>
   <h2>Title A</h2>
   <div>Some Content</div>
   <div>More Content</div>
   <h2>Title B</h2>
</r>

this XPath,

//div[preceding-sibling::h2 = 'Title A' and following-sibling::h2 = 'Title B']

will select this content,

<div>Some Content</div>
<div>More Content</div>

between the two h2 titles, as requested.


Update to address OP's self-answer:

For this new XML example,

<div>
    <h2><span>Summary</span></h2>
    <p>Paragraph</p>
    <ul>
        <li>List1</li>
        <li>List2</li>
        <li>List3</li>
    </ul>
    <p>Paragraph</p>

    <h2><span>Location</span></h2>
    <p>Paragraph</p>
</div>

the XPath I provided above can easily be adapted,

//*[preceding-sibling::h2 = 'Summary' and following-sibling::h2 = 'Location']

to select this XML,

<p>Paragraph</p>  
<ul>
   <li>List1</li>
   <li>List2</li>
   <li>List3</li>
</ul>    
<p>Paragraph</p>

as requested.

Monday, August 30, 2021
 
d8aninja
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :