Excel - 使用 FILTERXML 从字符串中提取子字符串

Posted

技术标签:

【中文标题】Excel - 使用 FILTERXML 从字符串中提取子字符串【英文标题】:Excel - Extract substring(s) from string using FILTERXML 【发布时间】:2020-09-02 08:50:57 【问题描述】:

背景

最近我一直在尝试更熟悉将分隔字符串更改为 XML 以使用 Excel 的 FILTERXML 解析并检索那些感兴趣的子字符串的概念。请注意,此功能来自 Excel 2013,不适用于 Excel for Mac 或 Excel Online。

对于带分隔符的字符串,我的意思是使用空格作为分隔符或任何其他可用于在字符串中定义子字符串的字符组合的普通句子中的任何内容。例如让我们想象一下:

ABC|123|DEF|456|XY-1A|ZY-2F|XY-3F|XY-4f|xyz|123

问题

所以,很多人都知道如何获取 nth 元素(例如:=TRIM(MID(SUBSTITUTE(A1,"|",REPT(" ",LEN(A1))),3*LEN(A1)+1,LEN(A1))) 来检索 456)。或者与LEN()MID()FIND() 和所有这些结构的其他组合,我们如何使用FILTERXML 来使用更具体的标准来提取关注的子字符串并清理完整的字符串?例如如何检索:

按位置排列的元素 数字或非数字元素 单独包含子字符串的元素 以子字符串开头或结尾的元素 大写或小写的元素 包含数字的元素 唯一值 ...

【问题讨论】:

【参考方案1】:

Excel 的FILTERXML 使用XPATH 1.0,不幸的是,这意味着它不像我们希望的那样多样化。此外,Excel 似乎 允许返回重做的节点值,并且只允许您按出现的顺序选择节点。但是,我们仍然可以使用相当多的功能。更多相关信息可以在here找到。

该函数有两个参数:=FILTERXML(<A string in valid XML format>,<A string in valid XPATH format>)

假设单元格A1 包含字符串:ABC|123|DEF|456|XY-1A|ZY-2F|XY-3F|XY-4f|xyz|123。为了创建一个有效的 XML 字符串,我们使用 SUBSTITUTE 将分隔符更改为有效的结束标签和开始标签结构。因此,要为给定示例获取有效的 XML 结构,我们可以这样做:

"<t><s>"&SUBSTITUTE(A1,"|","</s><s>")&"</s></t>"

出于可读性原因,我将使用单词 <XML> 作为 占位符 来引用上述构造。您将在下面找到不同有用的XPATH 函数以过滤节点:


1) 所有元素:

=FILTERXML(<XML>,"//s")

返回:ABC123DEF456XY-1AZY-2FXY-3FXY-4fxyz 和 @98765434 个节点)


2) 按位置排列的元素:

=FILTERXML(<XML>,"//s[position()=4]")

或者:

=FILTERXML(<XML>,"//s[4]")

返回:456(索引 4 上的节点)

=FILTERXML(<XML>,"//s[position()<4]")

返回:ABC123DEF(索引

=FILTERXML(<XML>,"//s[position()=2 or position()>5]")

返回:123ZY-2FXY-3FXY-4fxyz123(索引 2 或 > 5 上的节点)

=FILTERXML(<XML>,"//s[last()]")

返回:123(最后一个索引上的节点)

=FILTERXML(<XML>,"//s[position() mod 2 = 1]")

返回:ABCDEFXY-1AXY-3Fxyz(奇数节点)

=FILTERXML(<XML>,"//s[position() mod 2 = 0]")

返回:123456ZF-2FXY-4f123(偶数节点)


3)(非)数字元素:

=FILTERXML(<XML>,"//s[number()=.]")

或者:

=FILTERXML(<XML>,"//s[.*0=0]")

返回:123456123(数字节点)

=FILTERXML(<XML>,"//s[not(number()=.)]")

或者:

=FILTERXML(<XML>,"//s[.*0!=0)]")

返回:ABCDEFXY-1AZY-2FXY-3FXY-4fxyz(非数字节点)


4) (不)包含的元素:

=FILTERXML(<XML>,"//s[contains(., 'Y')]")

返回:XY-1AZY-2FXY-3FXY-4f(包含“Y”,注意XPATH 区分大小写,不包括xyz

=FILTERXML(<XML>,"//s[not(contains(., 'Y'))]")

返回:ABC123DEF456xyz123(不包含'Y',注意XPATH区分大小写,包括xyz )


5) 以(不)开头或/和结尾的元素:

=FILTERXML(<XML>,"//s[starts-with(., 'XY')]")

返回:XY-1AXY-3FXY-4f(以 'XY' 开头)

=FILTERXML(<XML>,"//s[not(starts-with(., 'XY'))]")

返回:ABC123DEF456ZY-2Fxyz123(不要以 'XY' 开头)

=FILTERXML(<XML>,"//s[substring(., string-length(.) - string-length('F') +1) = 'F']")

返回:DEFZY-2FXY-3F(以'F'结尾,注意XPATH 1.0不支持ends-with

=FILTERXML(<XML>,"//s[not(substring(., string-length(.) - string-length('F') +1) = 'F')]")

返回:ABC123456XY-1AXY-4fxyz123(不要以“F”结尾)

=FILTERXML(<XML>,"//s[starts-with(., 'X') and substring(., string-length(.) - string-length('A') +1) = 'A']")

返回:XY-1A(以'X'开始,以'A'结束)


6) 大写或小写的元素:

=FILTERXML(<XML>,"//s[translate(.,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')=.]")

返回:ABC123DEF456XY-1AZY-2FXY-3F123(大写节点)

=FILTERXML(<XML>,"//s[translate(.,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')=.]")

返回:123456xyz123(小写节点)

注意:不幸的是,XPATH 1.0 不支持upper-case() 也不支持lower-case(),因此上述是一种解决方法。如果需要,添加特殊字符。


7) (不)包含任何数字的元素:

=FILTERXML(<XML>,"//s[translate(.,'1234567890','')!=.]")

返回:123456XY-1AZY-2FXY-3FXY-4f123(包含任何数字)

=FILTERXML(<XML>,"//s[translate(.,'1234567890','')=.]")

返回:ABCDEFxyz(不包含任何数字)

=FILTERXML(<XML>,"//s[translate(.,'1234567890','')!=. and .*0!=0]")

返回:XY-1AZY-2FXY-3FXY-4f(包含数字,但不是单独的数字)


8) 独特元素或重复元素:

=FILTERXML(<XML>,"//s[preceding::*=.]")

返回:123(重复节点)

=FILTERXML(<XML>,"//s[not(preceding::*=.)]")

返回:ABC123DEF456XY-1AZY-2FXY-3FXY-4fxyz(唯一节点)

=FILTERXML(<XML>,"//s[not(following::*=. or preceding::*=.)]")

返回:ABCDEF456XY-1AZY-2FXY-3FXY-4f(没有相似兄弟的节点)


9) 一定长度的元素:

=FILTERXML(<XML>,"//s[string-length()=5]")

返回:XY-1AZY-2FXY-3FXY-4f(5 个字符长)

=FILTERXML(<XML>,"//s[string-length()<4]")

返回:ABC123DEF456xyz123(少于 4 个字符)


10) 基于前/后的元素:

=FILTERXML(<XML>,"//s[preceding::*[1]='456']")

返回:XY-1A(前一个节点等于'456')

=FILTERXML(<XML>,"//s[starts-with(preceding::*[1],'XY')]")

返回:ZY-2FXY-4fxyz(上​​一个节点以 'XY' 开头)

=FILTERXML(<XML>,"//s[following::*[1]='123']")

返回:ABCxyz(下一个节点等于 '123')

=FILTERXML(<XML>,"//s[contains(following::*[1],'1')]")

返回:ABC456xyz(后面的节点包含“1”)

=FILTERXML(<XML>,"//s[preceding::*='ABC' and following::*='XY-3F']")

或者:

=FILTERXML(<XML>,"//s[.='ABC']/following::s[following::s='XY-3F']")    

返回:123DEF456XY-1AZY-2F('ABC' 和 'XY-3F' 之间的所有内容)


11) 基于子字符串的元素:

=FILTERXML(<XML>,"//s[substring-after(., '-') = '3F']")

返回:XY-3F(连字符后以“3F”结尾的节点)

=FILTERXML(<XML>,"//s[contains(substring-after(., '-') , 'F')]")

返回:ZY-2FXY-3F(连字符后包含“F”的节点)

=FILTERXML(<XML>,"//s[substring-before(., '-') = 'ZY']")

返回:ZY-2F(连字符前以“ZY”开头的节点)

=FILTERXML(<XML>,"//s[contains(substring-before(., '-'), 'Y')]")

返回:XY-1AZY-2FXY-3FXY-4f(连字符前包含“Y”的节点)


12) 基于串联的元素:

=FILTERXML(<XML>,"//s[concat(., '|', following::*[1])='ZY-2F|XY-3F']")

返回:ZY-2F(与 '|' 连接时的节点,并且后面的兄弟节点等于 'ZY-2F|XY-3F')

=FILTERXML(<XML>,"//s[contains(concat(., preceding::*[2]), 'FA')]")

返回:DEF(与左侧的兄弟两个索引连接时的节点包含'FA')


13) 空与非空:

=FILTERXML(<XML>,"//s[count(node())>0]")

或者:

=FILTERXML(<XML>,"//s[node()]")

返回:ABC123DEF456XY-1AZY-2FXY-3FXY-4fxyz 和 @987654544 个节点不为空)

=FILTERXML(<XML>,"//s[count(node())=0]")

或者:

=FILTERXML(<XML>,"//s[not(node())]")

返回:无(所有节点为空)


现在显然以上是XPATH 1.0 函数的可能性演示,您可以获得以上和更多组合的整个范围!我试图涵盖最常用的字符串函数。如果您有任何遗漏,请随时发表评论。

虽然这个问题本身相当广泛,但我希望就如何使用FILTERXML 来处理手头的查询提供一些总体指导。该公式返回要以任何其他方式使用的节点数组。很多时候我会在TEXTJOIN()INDEX() 中使用它。但我想其他选项将是新的 DA 函数来溢出结果。

请注意,在通过 FILTERXML() 解析字符串时,与号字符 (&) 和左尖括号 (not。它们将分别需要替换为&amp;amp;&amp;lt;。另一种选择是使用它们的数字 ISO/IEC 10646 字符 code 分别为 &amp;#38;&amp;#60;。解析后,该函数将以文字形式将这些字符返回给您。不用说,用分号分割字符串变得很棘手。

【讨论】:

仅供参考您可能对通过FilterXML 将数字字符串更改为唯一数字的排序数组的一种棘手方法感兴趣,并通过一些解释丰富了(以及上述帖子的链接:- ) Divide numbers into unique sorted digits - @JvdV @Harun24HR,是的,你可以使用logical operators,比如“less then”。您可以直接应用例如:=FILTERXML(&lt;XML&gt;,"//s[.&lt;200]") 在上面的示例中,这将返回两个“123”-nodes。 @JvdV 很好用!老实说,我从你的这篇文章中了解到FILTERXML()。谢谢你。 这是一个很好的参考 - 很好,完成了,@JvdV。 我今天早上看到了那篇文章(并为你添加了 + 号)。我自己通过 VBA 使用过这个函数一两次,虽然很少。 @T.M.【参考方案2】:

臭名昭著的缺少 SPLIT() 函数

这篇文章是为了更深入地展示我们如何在不使用 VBA 的情况下使用 FILTERXML() 制作我们自己的可重用 SPLIT() 函数。尽管目前处于 BETA 阶段,LAMBDA() 正在向我们走来,使用此功能我们可以创建自己的自定义功能。让我举个例子来解释一下:

C1 中的公式只是=SPLIT(A1,B1:B3,""),它会按出现的顺序溢出分隔的文本值。但是SPLIT() 是我们在“名称管理器”中创建的LAMBDA() 函数的名称:

=LAMBDA(txt,del,xpath,FILTERXML("<t><s>"&REDUCE(txt,del,LAMBDA(a,b,SUBSTITUTE(a,b,"</s><s>")))&"</s></t>","//s"&xpath))

如你所见,函数有4个参数:

txt - 对我们源值的引用。 del - 我们想要的任意数量的分隔符。书面或参考。 xpath - 如果需要,放置一个 xpath 表达式以应用一些过滤器。例如:"[.*0=0]" 仅返回数字子字符串。 FILTERXML("&lt;t&gt;&lt;s&gt;"&amp;REDUCE(txt,del,LAMBDA(c,d,SUBSTITUTE(c,d,"&lt;/s&gt;&lt;s&gt;")))&amp;"&lt;/s&gt;&lt;/t&gt;","//s"&amp;xpath)

第 4 个参数是调用所有 3 个先前参数以创建与主帖子中介绍的相同结构的位置。现在,由于 MS 不想给我们自己,我们用三个参数创建了自己的 SPLIT() 函数。

主帖集中在特定分隔符的SUBSTITUTE();在给定的示例中,管道符号。但是,如果您有多个分隔符怎么办?您需要多个嵌套的 SUBSTITUTE() 函数,对吗?如果我们也可以在 SPLIT() 函数中实现它,那不是很好吗?这就是LAMBDA() 让我个人感到兴奋的地方,因为我们可以递归地调用它,MS 甚至想到了一个与 lambda 相关的函数来解决问题; REDUCE()。此函数现在将递归处理所有分隔符,直到所有分隔符都通过嵌套的LAMBDA()!

我们现在已经创建了我们自己的SPLIT() 函数,其中包含三个参数:

=SPLIT(<StringToBeSplited>,<YourDelimiters>,<OptionalXpath>)

我们现在可以在我们的整个工作簿中使用它作为一个函数。享受吧!

【讨论】:

以上是关于Excel - 使用 FILTERXML 从字符串中提取子字符串的主要内容,如果未能解决你的问题,请参考以下文章

excel 如何将单元格里面的英文翻译中文到指定的单元格 [转载]

Excel从如何右开始提取字符

从 Excel 工作表中查找并替换所有文本文件中的字符串

如何有效地从大型 Excel 文档中检索所有字符串

无法从 excel 列中读取字符串值

从excel读取(utf-8)字符串与odbc