Selecionando uma classe css com xpath

87

Quero selecionar apenas uma classe própria chamada .date

Por algum motivo, não consigo fazer isso funcionar. Se alguém souber o que há de errado com meu código, ficaria muito grato.

@$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." ";
}
Teddy13
fonte
2
e que tal um pedaço de html? (Prefira nos mostrar a saída simpleXml de asXML (), pois é mais próximo de xpath)
SergeS
se houver várias aulas que você precisa fazercontains(@class, 'date')
Gordon
A resposta de @Gordon é perigosa, se o atributo de classe for "datetime", ele também corresponderia. A resposta do usuário716736 é mais completa.
Niels Bom,

Respostas:

242

Quero escrever a resposta canônica a esta pergunta porque a resposta acima tem um problema.

Nosso problema

O seletor CSS :

.foo

irá selecionar qualquer elemento que tenha a classe foo .

Como você faz isso no XPath?

Embora XPath seja mais poderoso que CSS, XPath não tem um equivalente nativo de um seletor de classe CSS . No entanto, existe uma solução.

A maneira certa de fazer isso

O seletor equivalente no XPath é:

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

A função normalize-space remove os espaços em branco à esquerda e à direita (e também substitui as sequências de caracteres de espaço em branco por um único espaço).

(Em um sentido mais geral), isso também é equivalente ao seletor CSS:

*[class~="foo"]

que corresponderá a qualquer elemento cujo valor de atributo de classe seja uma lista de valores separados por espaços em branco, um dos quais é exatamente igual a foo .

Algumas maneiras óbvias, mas erradas de fazer isso

O seletor XPath:

//*[@class="foo"]

não funciona! porque não vai corresponder a um elemento que tem mais de uma classe, por exemplo

<div class="foo bar">

Também não corresponderá se houver algum espaço em branco extra ao redor do nome da classe:

<div class="  foo ">

O seletor XPath 'aprimorado'

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

também não funciona! porque corresponde erroneamente a elementos com a classe foobar , por exemplo

<div class="foobar">

O crédito vai para este sujeito, que foi a primeira solução publicada para esse problema que encontrei na web: http://dubinko.info/blog/2007/10/01/simple-parsing-of-space-seprated-attributes- in-xpathxslt /

user716736
fonte
Qual é a necessidade de normalizar o espaço?
Freek
"a resposta acima" provavelmente se refere a MrGlass.
LarsH
Isso é possível <div class="foo\tbar">? Quero dizer, nomes de classes separados por uma guia.
Frozen Flame
1
mas <div class = "group-conditions" /> e <div class = "condition" /> são iguais para $ x ('// div [contains (concat ("", normalize-space (@class), " ")," condição ")] ')
Memke,
1
@ testerjoe2 você tentou //*[contains(concat(" ", normalize-space(@class), " "), " foo ")]?
Niels Bom,
11

//[@class="date"] não é um xpath válido.

Experimente //*[@class="date"], ou se você souber que é uma imagem,//img[@class="date"]

MrGlass
fonte
7

XPath 3.1 introduz uma função contém-token e, portanto, finalmente resolve isso 'oficialmente'. Ele é projetado para oferecer suporte a aulas .

Exemplo:

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

Esta função garante que o espaço em branco (não apenas (U + 0020)) seja tratado corretamente, funciona no caso de repetição do nome da classe e geralmente cobre os casos extremos.


Nota: A partir de hoje (2016-12-13), XPath 3.1 tem o status de Recomendação Candidata .

Robin Pokorny
fonte
Não funciona no último cromo de hoje. Até que funcione, como contornar a limitação de que // * [contém (@class, "foo")] também selecionará qualquer classe que contenha foo, como foobar, fooz etc.
MasterJoe
1

HTML permite nomes de elementos e atributos que não diferenciam maiúsculas de minúsculas e, em seguida, class é uma lista de nomes de classes separados por espaço. Aqui vamos nós para uma imgtag e o classnome date:

//*['IMG' = translate(name(.), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')]/@*['CLASS' = translate(name(.), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') and contains(concat(' ', normalize-space(.), ' '), concat(' ', 'date', ' '))]

Veja também: Seletor CSS para conversão XPath

hakre
fonte
1

CUIDADO COM OS SINAIS DE MENOS NO MODELO !!! Se você estiver consultando "my-ownclass" no DOM:

<ul class="my-ownclass"><li>...</li></ul>
<ul class="someother"><li>...</li></ul>
<ul><li>...</li></ul>

$finder = new DomXPath($dom);
$nodes = $finder->query(".//ul[contains(@class, 'my-ownclass')]"); // This will NOT behave as expected! This will strangely match all the <ul> elements in DOM.
$nodes = $finder->query(".//ul[contains(@class, 'ownclass')]"); // This will match the element.
Vlado
fonte