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.