2017-09-07 6 views
0

Je HTML comme ceci:Comment trouver du texte à travers les limites de balises HTML (avec des pointeurs XPath comme résultat)?

<div>Lorem ipsum <b>dolor sit</b> amet.</div> 

Comment puis-je trouver une correspondance à base de texte brut pour ma chaîne de recherche dans ce ipsum dolor HTML? J'ai besoin des pointeurs de nœud XPath de début et de fin pour la correspondance, plus les index de caractères pour pointer à l'intérieur de ces nœuds de début et de fin. J'utilise Nokogiri pour travailler avec le DOM, mais toute solution pour Ruby est bonne.

Difficulté:

  • Je ne peux pas node.traverse {|node| … } par les DOM et faire une recherche de texte brut chaque fois qu'un nœud de texte vient à travers, parce que ma chaîne de recherche peut franchir les frontières d'étiquette.

  • Je ne peux pas faire une recherche en texte brut après la conversion du HTML en texte brut, car j'ai besoin des index XPath comme résultat.

je pourrais mettre en œuvre moi-même avec traversal arbre de base, mais auparavant, je me demande s'il y a une fonction Nokogiri ou astuce pour le faire plus confortablement.

Répondre

0

En fin de compte, nous avons utilisé le code comme suit. Il est illustré pour l'exemple donné dans la question, mais fonctionne également dans le cas générique de l'imbrication de balises HTML de profondeur arbitraire. (Qui est ce dont nous avons besoin.)

En outre, nous l'avons implémenté d'une manière qui peut ignorer les espaces (> 2) excédentaires dans une rangée. C'est pourquoi nous devons rechercher la fin du match et ne pouvons pas simplement utiliser la longueur de la chaîne de recherche/devis et le début de la position de correspondance: le nombre de caractères d'espaces dans la chaîne de recherche et la recherche peuvent différer.

 
doc = Nokogiri::HTML.fragment("<div>Lorem ipsum <b>dolor sit</b> amet.</div>") 
quote = 'ipsum dolor' 


# Find search string in document text, "plain text in plain text". 

quote_query = 
    quote.split(/[[:space:]]+/).map { |w| Regexp.quote(w) }.join('[[:space:]]+') 
start_index = doc.text.index(/#{quote_query}/i) 
end_index = start_index+doc.text[/#{quote_query}/i].size 


# Find XPath values and character indexes for start and stop of search match. 
# For that, walk through all text nodes and count characters until reaching 
# the start and end positions of the search match. 

start_xpath, start_offset, end_xpath, end_offset = nil 
i = 0 

doc.xpath('.//text() | text()').each do |x| 
 offset = 0 
 x.text.split('').each do 
   if i == start_index 
     e = x.previous 
     sum = 0 
     while e 
       sum+= e.text.size 
       e = e.previous 
     end 
     start_xpath = x.path.gsub(/^\?/, '').gsub(
     /#{Regexp.quote('/text()')}.*$/, '' 
    ) 
     start_offset = offset+sum 
   elsif i+1 == end_index 
     e = x.previous 
     sum = 0 
     while e 
       sum+= e.text.size 
       e = e.previous 
     end 
     end_xpath = x.path.gsub(/^\?/, '').gsub(
     /#{Regexp.quote('/text()')}.*$/, '' 
    ) 
     end_offset = offset+1+sum 
   end 
   offset+=1 
   i+=1 
 end 
end 

À ce stade, nous pouvons récupérer les valeurs XPath souhaitées pour le début et la fin du match de recherche (et en plus, des décalages de caractères indiquant le caractère exact intérieur de l'élément désigné XPath pour le démarrage et l'arrêt le match de recherche). Nous obtenons:

puts start_xpath 
    /div 
puts start_offset 
    6 
puts end_xpath 
    /div/b 
puts end_offset 
    5 
1

Vous pouvez faire quelque chose comme:

doc.search('div').find{|div| div.text[/ipsum dolor/]}