在 Xpath 中同时转义双引号和单引号
Posted
技术标签:
【中文标题】在 Xpath 中同时转义双引号和单引号【英文标题】:Simultaneously escape double and single quotes in Xpath 【发布时间】:2020-04-09 09:31:34 【问题描述】:类似于How to deal with single quote in xpath,我想转义单引号。不同的是,我不能排除双引号也可能出现在目标字符串中的可能性。
目标:
使用 Xpath(在 R 中)同时转义双引号和单引号。目标元素应用作变量,而不是像现有答案之一那样进行硬编码。 (应该是一个变量,因为我事先不知道内容,它可能有单引号、双引号或两者都有)。
作品:
library(rvest)
library(magrittr)
html <- "<div>1</div><div>Father's son</div>"
target <- "Father's son"
html %>% xml2::read_html() %>% html_nodes(xpath = paste0("//*[contains(text(), \"", target,"\")]"))
xml_nodeset (1)
[1] <div>Father's son</div>
不起作用:
html <- "<div>1</div><div>Fat\"her's son</div>"
target <- "Fat\"her's son"
html %>% xml2::read_html() %>% html_nodes(xpath = paste0("//*[contains(text(), \"", target,"\")]"))
xml_nodeset (0)
Warning message:
In xpath_search(x$node, x$doc, xpath = xpath, nsMap = ns, num_results = Inf) :
Invalid expression [1207]
更新
非常欢迎我尝试“翻译为 R”的非 R 答案。
【问题讨论】:
我的意思是*问题。 【参考方案1】:这里的关键是意识到,使用 xml2,您可以使用 html 转义字符写回已解析的 html。这个函数可以解决问题。它比实际需要的要长,因为我包含了 cmets 和一些类型检查/转换逻辑。
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)
现在:
library(rvest)
library(xml2)
html %>% xml2::read_html() %>% contains_text(target)
#> xml_nodeset (1)
#> [1] <div>Fat"her's son</div>
html %>% xml2::read_html() %>% contains_text(target) %>% xml_text()
#> [1] "Fat\"her's son"
附录
这是一种替代方法,它是@Alejandro 建议的方法的实现,但允许任意目标。它具有保持 xml 文档不变的优点,并且比上述方法快一点,但涉及到 xml 库应该防止的那种字符串解析。它的工作原理是获取目标,在每个 "
和 '
之后将其拆分,然后将每个片段括在与其包含的相反类型的引号中,然后用逗号将它们全部粘贴在一起并将它们插入到 XPath @987654325 @函数。
library(stringr)
safe_xpath <- function(target)
target %<>%
str_replace_all("\"", ""&break;") %>%
str_replace_all("'", "&apo;&break;") %>%
str_split("&break;") %>%
unlist()
safe_pieces <- grep("(")|(&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, "))]"))
现在我们可以像这样生成一个有效的 xpath:
safe_xpath(target)
#> [1] "//*[contains(text(),concat('Fat\"',\"her'\",\"s son\"))]"
这样
html %>% xml2::read_html() %>% html_nodes(xpath = safe_xpath(target))
#> xml_nodeset (1)
#> [1] <div>Fat"her's son</div>
【讨论】:
哇,这真是太好了。学到了很多,谢谢!我已经接受了答案,所以赏金将被自动分配,同时答案可以收集更多当之无愧的赞成票,...如果它符合您的兴趣。否则我也可以直接分配赏金。再次感谢! 此方法涉及更改基础文档,而不是编写正确的 XPath 表达式。 @Alejandro 我知道你的意思,但请记住,xml 在此函数结束之前返回到其初始状态,因此有关实现的这一事实对用户是隐藏的。我们不在多线程环境中,这种实现可能会出现问题。我还编写了一个函数(类似于您建议的方法),它可以分段构建 xpath,但在我看来它不太优雅。如果您打算这样做,将 html 解析为单个字符串几乎一样容易。如果ThankGuys 感兴趣,我可以将其包含在我的答案中。 如果不需要太多努力,我肯定会感兴趣。但也公平地说,我的规范没有对基础文档的(临时/持久)更改做出任何限制,因此问题得到了充分回答。事实上,我发现在基础文档中使用 xml2 进行临时更改的想法是一个非常聪明的想法。但我会牢记亚历杭德罗的暗示! 现在,答案的第二部分确实涵盖了将字符串注入嵌入式语言的常用方法:使用宿主语言清理字符串。【参考方案2】:我将cat
函数添加到html_nodes()
函数调用中的目标。似乎可以处理这两种情况。 cat()
也有打印转义文本的副作用。
library(rvest)
library(magrittr)
html <- "<div>1</div><div>Father's son</div>"
target <- "Father's son"
html %>% xml2::read_html() %>% html_nodes(xpath = paste0("//*[contains(text(), \"",cat(target),"\")]"))
#> Father's son
#> xml_nodeset (4)
#> [1] <html><body>\n<div>1</div>\n<div>Father's son</div>\n</body></html>
#> [2] <body>\n<div>1</div>\n<div>Father's son</div>\n</body>
#> [3] <div>1</div>\n
#> [4] <div>Father's son</div>
html <- "<div>1</div><div>Father said \"Hello!\"</div>"
target <- 'Father said "Hello!"'
html %>% xml2::read_html() %>% html_nodes(xpath = paste0("//*[contains(text(), \"",cat(target),"\")]"))
#> Father said "Hello!"
#> xml_nodeset (4)
#> [1] <html><body>\n<div>1</div>\n<div>Father said "Hello!"</div>\n</body> ...
#> [2] <body>\n<div>1</div>\n<div>Father said "Hello!"</div>\n</body>
#> [3] <div>1</div>\n
#> [4] <div>Father said "Hello!"</div>
【讨论】:
感谢您的回答。似乎创建了 4 个节点的输出而不是一个。所以所有节点都被选中。我认为 cat 中的 xpath-part 只是被省略了,至少它看起来像如果你将它保存到一个变量。 再次感谢您的帮助。我接受了艾伦的回答,因为它非常详细,涵盖了很多场景。我希望没问题。【参考方案3】:使用quote()
进行xpath 查询
library(XML)
字符串内只有单引号
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)
字符串中的单引号和双引号
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)
输出:
getNodeSet(doc1, xpath_query1)
# [[1]]
# <div>Father's son</div>
#
# attr(,"class")
# [1] "XMLNodeSet"
getNodeSet(doc2, xpath_query2)
# [[1]]
# <div>Fat"her's son</div>
#
# attr(,"class")
# [1] "XMLNodeSet"
【讨论】:
谢谢你已经有很大的帮助了。也许我没有足够好地指定它。我需要动态插入target
。所以一些事情:xpath_query2 <- quote(paste0('//body/*[contains(.,concat(', target,'))]'))
- (这个示例代码 obv。失败) - 但这样的事情可能吗?
是的,有可能。请注意 xpath 查询中的想法 - 2:单引号在双引号内,双引号在单引号内。然后使用 xpath 函数将它们连接起来。您可以通过识别字符串中的单引号和双引号来动态创建 xpath 查询并适当地处理它。你只需编写一个通用函数来实现这个想法。希望这会有所帮助。
据我所知,xpath 查询的问题是它不喜欢转义双引号。当您尝试在 xpath 查询中转义双引号时,您总是会遇到错误。
再次感谢您的帮助。正如我写给其他人一样:我接受了艾伦的回答,因为它非常详细并且涵盖了很多场景。我希望没问题。【参考方案4】:
因为您使用字符串操作来构建您的 XPath 表达式,所以您有责任确保该表达式是有效的 XPath。这个表达式:
//*[contains(.,concat('Fat"',"her's son"))]
选择:
<div>Fat"her's son</div>
在here中测试
使用 XPath 字符串变量会是更好的方法,但看起来 R 没有用于此的 API,即使使用 libxml。
【讨论】:
再次感谢您的帮助。我接受了艾伦的回答,因为它非常详细,涵盖了很多场景。我希望没问题。 @ThanksGuys 没问题。但这个答案本质上是错误的。您应该编写一个语法正确的 XPath 表达式,就像您需要一个语法正确的 R 程序一样。为此,您需要一个辅助 R 函数,该函数使不带引号的字符串保持不变,或者如果字符串包含单引号或包含双引号,则使用逆函数,或者当字符串同时具有单引号和双引号时,将该函数递归地应用于由引号字符标记的部分.以上是关于在 Xpath 中同时转义双引号和单引号的主要内容,如果未能解决你的问题,请参考以下文章
SQL双引号和单引号同时存在的时候该如何转义呢? 比如这一句