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

Jvd*_*vdV 26 xml arrays excel xpath excel-formula

背景

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

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

ABC|123|DEF|456|XY-1A|ZY-2F|XY-3F|XY-4f|xyz|123
Run Code Online (Sandbox Code Playgroud)

因此,很多人都知道如何获取第n 个元素(例如:=TRIM(MID(SUBSTITUTE(A1,"|",REPT(" ",LEN(A1))),3*LEN(A1)+1,LEN(A1)))要检索456)。或其他combinationes有LEN()MID()FIND()和所有这些结构,我们如何使用FILTERXML使用更具体的标准来关注的提取子和清理满弦?例如,如何检索:

  • 按位置排列的元素
  • 数字或非数字元素
  • 自身包含子字符串的元素
  • 以子字符串开头或结尾的元素
  • 大写或小写的元素
  • 持有数字的元素
  • 独特的价值
  • ...

Jvd*_*vdV 33

不幸的是,Excel 的FILTERXML用途XPATH 1.0意味着它不像我们希望的那样多样化。此外,Excel中似乎没有允许返回重做节点值并专门让你在出场顺序选择节点。但是,我们仍然可以使用相当多的功能。可以在此处找到更多相关信息。

该函数有两个参数: =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")
Run Code Online (Sandbox Code Playgroud)

返回:ABC123DEF456XY-1AZY-2FXY-3FXY-4fxyz123(所有节点)


2) 元素按位置:

=FILTERXML(<XML>,"//s[position()=4]")
Run Code Online (Sandbox Code Playgroud)

或者:

=FILTERXML(<XML>,"//s[4]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[position()<4]")
Run Code Online (Sandbox Code Playgroud)

返回:ABC,123DEF(索引 < 4 上的节点)

=FILTERXML(<XML>,"//s[position()=2 or position()>5]")
Run Code Online (Sandbox Code Playgroud)

返回:123ZY-2FXY-3FXY-4fxyz123(在指数2或> 5个节点)

=FILTERXML(<XML>,"//s[last()]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[position() mod 2 = 1]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[position() mod 2 = 0]")
Run Code Online (Sandbox Code Playgroud)

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


3)(非)数字元素:

=FILTERXML(<XML>,"//s[number()=.]")
Run Code Online (Sandbox Code Playgroud)

或者:

=FILTERXML(<XML>,"//s[.*0=0]")
Run Code Online (Sandbox Code Playgroud)

返回:123456、 和123(数字节点)

=FILTERXML(<XML>,"//s[not(number()=.)]")
Run Code Online (Sandbox Code Playgroud)

或者:

=FILTERXML(<XML>,"//s[.*0!=0)]")
Run Code Online (Sandbox Code Playgroud)

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


4) 包含(不)的元素:

=FILTERXML(<XML>,"//s[contains(., 'Y')]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[not(contains(., 'Y'))]")
Run Code Online (Sandbox Code Playgroud)

返回:ABC, 123, DEF, 456, xyzand 123(不包含'Y',注意XPATH区分大小写,包括xyz


5) 以(不)开始或/和结束的元素:

=FILTERXML(<XML>,"//s[starts-with(., 'XY')]")
Run Code Online (Sandbox Code Playgroud)

返回:XY-1A,XY-3FXY-4f(以“XY”开头)

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

返回:ABC123DEF456ZY-2Fxyz123(不要与'XY'开始)

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

返回:DEF, ZY-2Fand XY-3F(以'F'结尾,通知XPATH 1.0不支持ends-with

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

返回:ABC, 123, 456, XY-1A, XY-4f, xyzand 123(不要以'F'结尾)

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

返回:(XY-1A以“X”开头,以“A”结尾)


6) 大写或小写的元素:

=FILTERXML(<XML>,"//s[translate(.,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')=.]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[translate(.,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')=.]")
Run Code Online (Sandbox Code Playgroud)

返回:123456xyz123(小写节点)

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


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

=FILTERXML(<XML>,"//s[translate(.,'1234567890','')!=.]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[translate(.,'1234567890','')=.]")
Run Code Online (Sandbox Code Playgroud)

返回:ABC,DEFxyz(不包含任何数字)

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

返回:XY-1A, ZY-2F,XY-3FXY-4f(保存数字但不单独保存数字)


8)独特的元素或重复:

=FILTERXML(<XML>,"//s[preceding::*=.]")
Run Code Online (Sandbox Code Playgroud)

返回:(123重复节点)

=FILTERXML(<XML>,"//s[not(preceding::*=.)]")
Run Code Online (Sandbox Code Playgroud)

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

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

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


9) 一定长度的元素:

=FILTERXML(<XML>,"//s[string-length()=5]")
Run Code Online (Sandbox Code Playgroud)

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

=FILTERXML(<XML>,"//s[string-length()<4]")
Run Code Online (Sandbox Code Playgroud)

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


10) 基于前/后的元素:

=FILTERXML(<XML>,"//s[preceding::*[1]='456']")
Run Code Online (Sandbox Code Playgroud)

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

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

返回:ZY-2FXY-4f、 和xyz(前一个节点以 'XY' 开头)

=FILTERXML(<XML>,"//s[following::*[1]='123']")
Run Code Online (Sandbox Code Playgroud)

返回:ABC, 和xyz(后面的节点等于 '123')

=FILTERXML(<XML>,"//s[contains(following::*[1],'1')]")
Run Code Online (Sandbox Code Playgroud)

返回:ABC456、 和xyz(后续节点包含“1”)

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

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


11) 基于子串的元素:

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

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

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

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

=FILTERXML(<XML>,"//s[substring-before(., '-') = 'ZY']")
Run Code Online (Sandbox Code Playgroud)

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

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

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


12) 基于串联的元素:

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

返回:(ZY-2F节点与“|”连接且其后的兄弟节点等于“ZY-2F|XY-3F”)

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

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


13)空与非空:

=FILTERXML(<XML>,"//s[count(node())>0]")
Run Code Online (Sandbox Code Playgroud)

或者:

=FILTERXML(<XML>,"//s[node()]")
Run Code Online (Sandbox Code Playgroud)

返回:ABC123DEF456XY-1AZY-2FXY-3FXY-4fxyz123(所有非空节点)

=FILTERXML(<XML>,"//s[count(node())=0]")
Run Code Online (Sandbox Code Playgroud)

或者:

=FILTERXML(<XML>,"//s[not(node())]")
Run Code Online (Sandbox Code Playgroud)

返回:无(所有节点都是空的)


现在很明显,上面是XPATH 1.0功能可能性的演示,您可以获得上述和更多的各种组合!我试图涵盖最常用的字符串函数。如果您有遗漏,请随时发表评论。

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

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

  • 仅供参考,您可能对通过“FilterXML”将数字字符串更改为唯一数字的排序数组的一种棘手方法感兴趣,该方法通过一些解释(以及指向上述帖子的链接:-)进行了丰富,位于[将数字划分为唯一排序的数组]数字](/sf/ask/4447864991/)-@JvdV (2认同)
  • @Harun24HR,是的,您可以使用[逻辑运算符](https://www.w3schools.com/xml/xpath_operators.asp),例如“less then”。您可以在上面的示例中直接应用例如:`=FILTERXML(&lt;XML&gt;,"//s[.&lt;200]")`,这将返回两个“123”节点。 (2认同)
  • @JvdV 效果很好!老实说,我从您的这篇文章中了解了“FILTERXML()”。衷心感谢您。 (2认同)
  • 很好的解释@JvdV,但我不太理解。值得注意的是,根据截至 2022 年 11 月 15 日的 [FILTERXML](https://support.microsoft.com/en-us/office/filterxml-function-4df72efc-11ec-4951-86f5-c1374812f5b7) 文档 * 不是适用于 Excel 网页版 和 Excel for Mac* (2认同)

Jvd*_*vdV 7

这篇文章的目的是为了展示我们如何SPLIT()使用FILTERXML()但不使用 VBA来制作我们自己的可重用函数。尽管目前处于 BETA 阶段,但LAMBDA()我们正在使用此功能,我们可以创建自己的功能。让我用第一个例子来解释这一点:

在此处输入图片说明

中的公式B1很简单=SPLIT(A1,"|",""),它按出现顺序溢出分隔的文本值。然而SPLIT(),我们LAMBDA()在“名称管理器”中创建的函数的名称。

=LAMBDA(txt,del,xpath,FILTERXML("<t><s>"&SUBSTITUTE(txt,del,"</s><s>")&"</s></t>","//s"&xpath))
Run Code Online (Sandbox Code Playgroud)

如您所见,该函数有 4 个参数:

  • txt - 对我们源值的引用。
  • del - 我们要使用的分隔符。
  • xpath - 如果需要,放置 xpath 表达式以应用一些过滤器。
  • FILTERXML("<t><s>"&SUBSTITUTE(txt,del,"</s><s>")&"</s></t>","//s"&xpath)

第 4 个参数是调用所有 3 个前面的参数以创建与主要文章中介绍的相同的构造的地方。现在,由于 MS 没有,我们SPLIT()使用三个参数创建了自己的函数。

主要文章集中在SUBSTITUTE()特定分隔符的 ,在我们的示例中是管道符号。但是如果你有几个分隔符呢?您需要多个嵌套SUBSTITUTE()函数,对吗?如果我们也可以在我们的SPLIT()函数中实现它,那不是很好吗?这就是LAMBDA()让我个人感到兴奋的地方,因为如果我们提供一条出路,我们就可以递归调用它,否则它将无限循环!

让我用第二个例子来演示:

在此处输入图片说明

中的公式B1又是:

=SPLIT(A1,"|&#","")
Run Code Online (Sandbox Code Playgroud)

这次最大的不同是我们在第二个参数的字符串中有几个分隔符。它仍然有效......疯狂!但是在我们中发生的事情LAMBDA()是我使它递归:

=LAMBDA(txt,del,xpath,IF(del="",FILTERXML("<t><s>"&txt&"</s></t>","//s"&xpath),SPLIT(SUBSTITUTE(txt,LEFT(del),"</s><s>"),RIGHT(del,LEN(del)-1),xpath)))
Run Code Online (Sandbox Code Playgroud)

的主要部分LAMBDA()仍然存在,但我嵌套了一个IF(),并在其中一遍又一遍地IF()调用相同的SPLIT()函数,直到所有分隔符都被替换。

我们可以将其进一步扩展,不仅可以按单个字符分割,还可以按整个单词分割。在这种情况下,我们将需要一个数组来循环。我在下面的例子中尝试过:

在此处输入图片说明

公式B1=SPLIT(A1,C1:C2,""),因此我们的字符串变成了单元格引用。请注意,在我的情况下,我这样做了,因此您需要对单个值进行硬编码或引用单个单元格,或者需要对垂直数组或垂直范围引用进行硬编码。这对我们LAMBDA()虽然有影响:

=LAMBDA(txt,del,xpath,IF(COUNTA(del)=1,FILTERXML("<t><s>"&SUBSTITUTE(txt,@del,"</s><s>")&"</s></t>","//s"&xpath),SPLIT(SUBSTITUTE(txt,@del,"</s><s>"),INDEX(del,SEQUENCE(COUNTA(del)-1,,2)),xpath)))
Run Code Online (Sandbox Code Playgroud)

很长,但不要忘记我们现在可以将它用作整个工作簿中的一个函数。

我们现在已经SPLIT()用三个参数创建了我们自己的函数:

=SPLIT(<StringToBeSplited>,<YourDelimiters>,<OptionalXpath>)
Run Code Online (Sandbox Code Playgroud)

享受!