Анализ XML файла с помощью F # linq для XML

F # новичок

У меня есть 2 файла XML в 2 папках c:\root\a\file.xml и c:\root\b\file.xml

Они имеют одинаковую структуру:

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

Мне нужно выбрать файл, свойство node с именем firstName имеет значение Jane.

В F # (возможно, используя System.Xml.Linq) Я попробовал несколько решений, но пока не работал. Кто желает помочь?

Ответы

Ответ 1

Было бы полезно, если бы вы могли показать какой-то код, который вы попробовали, - тогда кто-то может объяснить, в чем проблема, поэтому вы можете узнать больше, чем когда кто-то просто пишет код, который работает. В любом случае вам нужно будет ссылаться на некоторые сборки с помощью System.Xml.Linq и сначала открыть пространство имен. В F # interactive вы можете записать его так (в проекте F # просто используйте диалоговое окно "Добавить ссылку" ):

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

При использовании XLinq в F # вам нужна простая функция утилиты для преобразования строк в объект XName (который представляет имя элемента/атрибута). Существует неявное преобразование в С#, но это, к сожалению, не работает в F #.

let xn s = XName.Get(s)

Затем вы можете загрузить свой XML-документ с помощью класса XDocument и использовать метод 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).

EDIT: Другой способ написать это - использовать выражения последовательности, а затем просто взять первый элемент (это не обрабатывает случай, когда элемент не найден):

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

Ответ 2

Вам не нужно использовать 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")

Ответ 3

Кроме того, вы можете смешать решение 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"

Ответ 4

Мне нравится этот подход лучше

#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()