F# XML Type Provider Common Elements

2019-05-09 22:46发布

问题:

XSD specifications for XML files can share common elements. If I have several XML files that share a common element, is there a way to extract the common element without repeating the code for each XML file type?

For example: There are a number of XML files defined via XSD, with a common description element, but different content structures elsewhere. The description has subelements with things like author, date, etc. When I create a type provider for each of the XML files, the types are different, so if I only want to extract the description section out of each, the code has to be copy pasted for each of the types.

XML file 1:

<root>
  <description >
     <author> Me </author>     
  </description>
  <element > Data </element>
  <otherelement> Data </otherelement>
</root>

XML file 2:

<root2>
  <description >
     <author> Me </author>     
  </description>
  <elem > Data </elem>
  <diffelem> Data </diffelem >
</root2>

Would require the code to be something like:

type File1 = XmlProvider<""".\file1.xml""">
type File2 = XmlProvider<""".\file2.xml""">

let descript1 = 
    File1.GetSample().description.author   
let descript2 = 
    File2.GetSample().description.author    //duplicated code

Simple in this case, but gets more involved with a longer description and more xml file types.

Is there a way around this? Can a type provider be created for a subset of an XML file and only extract those parts so the code can be more reusable?

回答1:

The XML type provider works the best in the case when your inputs are fairly regular. So, if you need to process multiple different schemas, it might be better to use the standard XML tools (like XDocument).

That said, you can use a few tricks that make your scenario nicer. First, you can specify a list of samples. To do that, just create a XML file that has some (any) root and both of your samples:

<?xml version="1.0" encoding="utf-8"?>
<samples>
  <root>
    <description><author> Me </author></description>
    <element > Data </element>
    <otherelement> Data </otherelement>
  </root>
  <root2>
    <description ><author> Me </author></description>
    <elem > Data </elem>
    <diffelem> Data </diffelem >
  </root2>
</samples>

Now you can create XML type provider and tell it that your sample file is a list (SampleIsList=true) and that it should use global resolution (meaning that all elements named description will be treated as values of the same type):

type X = XmlProvider<"C:/temp/sample1.xml", SampleIsList=true, Global=true>

Now, you have differently named roots, which makes the situation trickier, but you can now write code that gets the <description> element from one or the other root:

let i = X.Load("...")

let description = 
  match i.Root, i.Root2 with
  | Some r1, _ -> r1.Description
  | _, Some r2 -> r2.Description
  | _ -> failwith "Missing"

This gives you a description node with author sub node that you can get for both of your documents:

description.Author