Saltar al contenido

Escapar simultáneamente de comillas simples y dobles en Xpath

Si encuentras algún detalle que no entiendes puedes dejarlo en la sección de comentarios y trataremos de ayudarte lo mas rápido que podamos.

Solución:

porque estas usando string manipulación para construir su expresión XPath, es su responsabilidad que la expresión sea XPath válida. Esta expresión:

//*[contains(.,concat('Fat"',"her's son"))]

Selecciona:

Fat"her's son

Prueba aquí

Sería un mejor enfoque usar un XPath string variable, pero parece que R no tiene una API para eso, incluso usando libxml.

Él key aquí se da cuenta de que con xml2 puede volver a escribir en el html analizado con caracteres de escape html. Esta función hará el truco. Es más largo de lo necesario porque he incluido comentarios y alguna lógica de verificación/conversión de tipos.

contains_text <- function(node_set, find_this)

  # Ensure we have a nodeset
  if(all(class(node_set) == c("xml_document", "xml_node")))
    node_set %<>% xml_children()

  if(class(node_set) != "xml_nodeset")
    stop("contains_text requires an xml_nodeset or xml_document.")

  # Get all leaf nodes
  node_set %<>% xml_nodes(xpath = "//*[not(*)]")

  # HTML escape the target string
  find_this %<>% gsub(""", """, .)

  # Extract, HTML escape and replace the nodes
  lapply(node_set, function(node) xml_text(node) %<>% gsub(""", """, .))

  # Now we can define the xpath and extract our target nodes
  xpath <- paste0("//*[contains(text(), "", find_this, "")]")
  new_nodes <- html_nodes(node_set, xpath = xpath)

  # Since the underlying xml_document is passed by pointer internally,
  # we should unescape any text to leave it unaltered
  xml_text(node_set) %<>% gsub(""", """, .)
  return(new_nodes)

Ahora:

library(rvest)
library(xml2)

html %>% xml2::read_html() %>% contains_text(target)
#> xml_nodeset (1)
#> [1] 
Fat"her's son
html %>% xml2::read_html() %>% contains_text(target) %>% xml_text() #> [1] "Fat"her's son"

APÉNDICE

Este es un método alternativo, que es una implementación del método sugerido por @Alejandro pero permite objetivos arbitrarios. Tiene el mérito de dejar intacto el documento xml y es un poco más rápido que el método anterior, pero implica el tipo de string análisis que se supone que debe evitar una biblioteca xml. Funciona tomando el objetivo, dividiéndolo después de cada " y 'luego encierra cada fragmento en el tipo de cita opuesto al que contiene antes de pegarlos todos nuevamente con comas e insertarlos en un XPath concatenate función.

library(stringr)

safe_xpath <- function(target)
(&apo;)", target, invert = TRUE)
  contain_quotes <- grep(""", target)
  contain_apo    <- grep("&apo;", target)

  if(length(safe_pieces) > 0) 
      target[safe_pieces] <- paste0(""", target[safe_pieces], """)

  if(length(contain_quotes) > 0)
  
    target[contain_quotes] <- paste0("'", target[contain_quotes], "'")
    target[contain_quotes] <- gsub(""", """, target[contain_quotes])
  

  if(length(contain_apo) > 0)
  
    target[contain_apo] <- paste0(""", target[contain_apo], """)
    target[contain_apo] <- gsub("&apo;", "'", target[contain_apo])
  

  fragment <- paste0(target, collapse = ",")
  return(paste0("//*[contains(text(),concat(", fragment, "))]"))

Ahora podemos generar un xpath válido como este:

safe_xpath(target)
#> [1] "//*[contains(text(),concat('Fat"',"her'","s son"))]"

así que eso

html %>% xml2::read_html() %>% html_nodes(xpath = safe_xpath(target))
#> xml_nodeset (1)
#> [1] 
Fat"her's son

usar quote() para consulta xpath

library(XML)

solo una comilla dentro string

target1 <- "Father's son"
doc1 <- XML::newHTMLDoc()
newXMLNode("div", 1, parent = getNodeSet(doc1, "//body"), doc = doc1)
newXMLNode("div", target1, parent = getNodeSet(doc1, "//body"), doc = doc1)
xpath_query1 <- paste0('//*[ contains(text(), ', '"', target1, '"', ')]')
getNodeSet(doc1, xpath_query1)

comillas simples y dobles dentro string

target2 <- "Fat"her's son"
doc2 <- XML::newHTMLDoc()
newXMLNode("div", 1, parent = getNodeSet(doc2, "//body"), doc = doc2)
newXMLNode("div", target2, parent = getNodeSet(doc2, "//body"), doc = doc2)
xpath_query2 <- quote('//body/*[contains(.,concat('Fat"',"her's son"))]')
getNodeSet(doc2, xpath_query2)

Producción:

getNodeSet(doc1, xpath_query1)
# [[1]]
# 
Father's son
# # attr(,"class") # [1] "XMLNodeSet" getNodeSet(doc2, xpath_query2) # [[1]] #
Fat"her's son
# # attr(,"class") # [1] "XMLNodeSet"

Valoraciones y reseñas

Si entiendes que ha sido de utilidad este artículo, agradeceríamos que lo compartas con el resto juniors de esta forma contrubuyes a difundir esta información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *