简体   繁体   中英

How can I recursively get all XElement children for an XmlProvider

I'm trying to build a dynamic type/class builder for C# using F#, from the following XML

<config target="string">
    <protocol>string</protocol>
    <about_path>string</about_path>
    <about_content>
        <name_path>string</name_path>
        <id_path>string</id_path>
        <version_path>string</version_path>
    </about_content>
</config>

Using the code below I can parse the sample just fine

module XmlParser =
    open FSharp.Data
    open System.Globalization
    open FSharp.Data.Runtime.BaseTypes
    open System.Xml.Linq

    [<Literal>]
    let targetSchema = "<config target=\"string\">
                            <protocol>string</protocol>
                            <about_path>string</about_path>
                            <about_content>
                                <name_path>string</name_path>
                                <id_path>string</id_path>
                                <version_path>string</version_path>
                            </about_content>
                        </config>"

    type Configuration = XmlProvider<targetSchema> 

The problem now is that I can't get my head around retrieving the inner parts of the about_content tag.

After parsing the actual xml using

let parsedValue = Configuration.Parse(xmlIn)

I've tried to get my head around the recursion handling in F# but am stuck at the non-working code that looks like this ( e would be parsedValue.XElement )

let rec flatten ( e : System.Xml.Linq.XElement) (out:List<string>) = 
    if e.HasElements 
    then for inner in e.Elements -> flatten(inner)
    else e.Name.LocalName

What I would need is a hint on how to gather the e.Name.LocalName values into a sequence/List as a result of the recursion. I could also live with having a list of XElement s at the end.

The function flatten needs to return a sequence, not a single thing.

For elements with subelements, you need to call flatten for each, then concat all results:

e.Elements() |> Seq.map flatten |> Seq.concat

(note that XElement.Elements is a method, not a property; therefore, you need to add () to call it)

For a single element, just return its name wrapped in a single-element sequence:

Seq.singleton e.Name.LocalName

Putting it all together:

let rec flatten (e : System.Xml.Linq.XElement) = 
    if e.HasElements 
    then e.Elements() |> Seq.map flatten |> Seq.concat
    else Seq.singleton e.Name.LocalName

(also note that I have removed your out parameter, which, I assume, was meant to be not a parameter, but an attempt to declare the function's return type; it can be omitted; for reference, function return type in F# is declared after the function's signature with a colon, eg let f (x:int) : int = x + 5 )


If you prefer a more imperative-looking style, you can use the seq computation expression. yield will yield a single element, while yield! will have the effect of yielding each element of another sequence:

let rec flatten (e : System.Xml.Linq.XElement) = 
    seq {
        if e.HasElements then 
            for i in e.Elements() do 
                yield! flatten i
        else 
            yield e.Name.LocalName
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM