使用F# Linq to XML解析XML文件

14

F# 新手

我有两个 XML 文件在两个文件夹中,分别是 c:\root\a\file.xmlc:\root\b\file.xml

它们的结构完全相同:

<parent>
   <property name="firstName">Jane</property>
   <property name="lastName">...</property>
   <property name="dateOfBirth">...</property>>
</parent>

我需要选择属性节点名称为firstName且值为Jane的文件。

在F#中(可能使用System.Xml.Linq),我尝试了几种解决方案,但目前都没有成功。有人愿意帮忙吗?

4个回答

40

如果您能展示一些尝试过的代码,将会非常有帮助 - 这样某人就可以解释问题所在,这样您就可以学到比仅仅发布可用代码更多的知识。无论如何,您都需要引用一些包含 System.Xml.Linq 的程序集,并首先打开该命名空间。在 F# 交互式环境中,您可以像这样编写(在 F# 项目中,只需使用添加引用对话框即可):

#r "System.Core.dll"
#r "System.Xml.Linq.dll"
open System.Xml.Linq

在使用F#中的XLinq时,你需要一个简单的实用函数来将字符串转换为XName对象(表示元素/属性名称)。虽然C#中有隐式转换,但遗憾的是它在F#中不起作用。

let xn s = XName.Get(s)

您可以使用XDocument类加载XML文档,并使用Element方法获取单个“父”元素。然后,您可以调用Elements以获取所有嵌套的“属性”元素:

let xd = XDocument.Load("file.xml")
let props = xd.Element(xn "parent").Elements(xn "property")

现在,您可以搜索元素以找到具有指定属性值的一个元素。例如,使用Seq.tryFind(这还允许您处理未找到元素的情况):

let nameOpt = props |> Seq.tryFind (fun xe -> 
  xe.Attribute(xn "name").Value = "firstName")

现在,变量nameOpt的类型是option<XElement>,因此您可以对其进行模式匹配以查看是否找到该元素(例如Some(el)),或者未找到该元素(None)。

编辑:另一种编写方式是使用序列表达式,然后只取第一个元素(这不处理未找到元素的情况):

let nameEl = 
  seq { for el in xd.Element(xn "parent").Elements(xn "property") do
          if xe.Attribute(xn "name").Value = "firstName" then yield xe }
  |> Seq.head

1
现在我感觉很蠢!我以为使用f#“query {}”表达式和linq的重点是可以从数据源中抽象出来。所以,我认为不可能的事情是,没有办法做一个标准的“query { for foo in xmlBar do ... }”这样的东西吗? - BitTickler
请注意,如果尚未引用System.Xml,则需要引用它。 - byteit101

13

你不需要使用 LINQ。下面是一种实现方式:

open System.Xml.XPath

let getName (filename:string) =
  let xpath = XPathDocument(filename).CreateNavigator()
  let node = xpath.SelectSingleNode(@"parent/property[@name='firstName']")
  node.Value

let files = [@"c:\root\a\file.xml"; @"c:\root\b\file.xml"]
let fileForJane = files |> List.find (fun file -> getName file = "Jane")

6
此外,您可以将 kvb 的解决方案与 (?) 操作符混合使用:
let (?) (fname: string) (nodeName: string) : string =
  let xpath = XPathDocument(fname).CreateNavigator()
  let node = xpath.SelectSingleNode(@"parent/property[@name='"^nodeName^"']")
  node.Value;;

val ( ? ) : string -> string -> string

> "i:/a.xml"?firstName;;
val it : string = "Jane"

不理解。我看不出这是符号和运算符参考中的哪个运算符。你能提供参考吗? - dudeNumber4

2

我更喜欢这种方法

#r "System.Core.dll"
#r "System.Xml.Linq.dll"
open System.Xml.Linq

let xn s = XName.Get(s)

let xd = XDocument.Load("file.xml")
let fnp = xd.Descendants(xn "property")
        .Where(fun (p : XElement) -> p.Attribute(xn "name").Value = "firstName")
        .Single()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接