convertire - C'è un modo per generare un file.xsd per i tipi F#?




xsd to xml (3)

Stiamo cercando di sfruttare F # per i nostri progetti in futuro e vorremmo essere in grado di generare automaticamente schemi .xsd da tipi F #.

La ricerca sul Web restituisce molte risposte per generare tipi da .xsd, ma non viceversa.

Qualcuno sa di un modo per farlo?


Mi avvicinerei a questo usando 'typeclasses'. Esempio rapido (in REPL). Supponiamo di avere un tipo Person , come il type Person = { id : int64; name : string} type Person = { id : int64; name : string} . Poi:

> ("id", Xsd.int64, "name", Xsd.string)
  |> Xsd.record2 "Person"
  |> Xsd.root
  |> Xsd.to_string;;
val it : string =
  "<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:complexType name="Person">
  <xsd:sequence>
    <xsd:element name="id" type="long"/>
    <xsd:element name="name" type="string"/>
  </xsd:sequence>
</xsd:complexType>
</xsd:schema>"

Questo funziona mettendo poche funzioni di convertitore per ogni tipo nel modulo Xsd e anche per combinazioni di tipi, ad esempio somma e tipi di prodotto. Questo dovrebbe coprire la maggior parte delle esigenze. Che aspetto potrebbe Xsd modulo Xsd :

(* xsd.fsi *)

/// Just a string marked by the type of data whose XSD it holds.
/// Implementation is private so that callers can't create any XSD
/// they like.
type 'a t

/// Gives us the string representation of the XSD.
val to_string : 'a t -> string

/// Wraps an XSD encoding inside the <xsd:schema> tag pair.
val root : 'a t -> 'a t

// Primitive types.

val int : int t
val int64 : int64 t
val string : string t

/// Encode a two-field record's type (name and fields along with their
/// types) as XSD.
val record2 : string -> string * 'a1 t * string * 'a2 t -> 'a t

(* xsd.fs *)

type 'a t = { to_string : string }

let to_string xsd = xsd.to_string
let root xsd =
  { to_string =
      sprintf "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
  %s
</xsd:schema>" xsd.to_string }

let int = { to_string = "integer" }
let int64 = { to_string = "long" }
let string = { to_string = "string" }

/// Helper for record fields.
let element name typ =
  sprintf "<xsd:element name=\"%s\" type=\"%s\"/>" name typ

let record2 name (field1, xsd1, field2, xsd2) =
  { to_string =
      sprintf
        "<xsd:complexType name=\"%s\">
  <xsd:sequence>
    %s
    %s
  </xsd:sequence>
</xsd:complexType>"
        name
        (element field1 xsd1.to_string)
        (element field2 xsd2.to_string) }

A dire il vero, questa è una tecnica sconosciuta rispetto all'uso del riflesso di runtime. Ma è anche più sicuro di tipo e ti dà un controllo più fine sulla codifica. Probabilmente non hai bisogno di implementare tutto XSD: ti servono solo le parti effettivamente utilizzate dai tuoi tipi.


Potrei sbagliarmi, ma non vedo come ciò possa essere realizzato in un modo che lo renderebbe più pratico dell'utilizzo di un provider di tipo F # basato su XSD, se ce n'è uno che funzioni sufficientemente bene. Ma poi, non sono sicuro che ce ne sia uno.

Prova il provider di tipi FSharp.Data.Xsd. È possibile specificare XSD direttamente nell'origine come stringa o facendo riferimento a un file XSD esterno all'origine. È possibile che non possa creare alcun XSD che si possa desiderare.

Il problema, penso, è che i tipi di F # da soli non sono un modo pratico per specificare come dovrebbe essere l'XSD, a meno che non si faccia un compromesso che forse non si è pronti a fare.

  • Vuoi creare alcuni tipi specifici in F # per controllare la mappatura? Non penso che dover usare tali tipi sarebbe "sfruttando F #".

  • Useresti attributi di codice o altri metadati? In tal caso, non sta migliorando l'XSD invece dei tipi F #?

  • Creeresti semplicemente alcune regole che implicano una mappatura one-to-one? Potrebbe funzionare, ma potrebbe non produrre l'XSD e l'XML che desideri. Potrebbe diventare troppo prolisso.

Dovresti generare l'XSD. Se invece si utilizza un provider di tipi per generare i tipi F # da XSD, i tipi generati sono immediatamente disponibili. Non è molto più pratico e piacevole?


Se vuoi generare XSD di qualsiasi tipo direttamente dal tuo codice, dai un'occhiata a questo script di F #. Genera un XSD di tipo di record F #. Script utilizza tre assembly .NET: System.Runtime.Serialization.dll , System.Runtime.Serialization.Xml , System.Xml .

#r "System.Runtime.Serialization.dll"
#r "System.Runtime.Serialization.Xml.dll"
#r "System.Xml.dll"

open System
open System.IO
open System.Linq
open System.Text
open System.Text.RegularExpressions
open System.Xml
open System.Runtime.Serialization

type [<DataContract>] CommitInfo = { 
  [<field: DataMember(Name="id") >]
  id: string
  [<field: DataMember(Name="date") >]
  date: DateTime 
  [<field: DataMember(Name="issueUrl") >]
  issueUrl: string
  [<field: DataMember(Name="issueId") >]
  issueId: int
  [<field: DataMember(Name="message") >]
  message: string
  [<field: DataMember(Name="url") >]
  url: string 
}

let getXmlWriter (stream: Stream) = 
    //let utf8noBOM = new UTF8Encoding(false)
    let settings = new XmlWriterSettings()
    settings.Indent <- true
    settings.Encoding <- Encoding.UTF8  
    //settings.OmitXmlDeclaration <- true
    XmlWriter.Create(stream, settings)

let streamToString (stream: Stream) = 
    stream.Position <- int64 0
    use sr = new StreamReader(stream)
    sr.ReadToEnd()

let getResultFromStream (streamWriter: Stream -> unit) = 
    use stream = new MemoryStream ()
    streamWriter stream
    streamToString stream

let exporter = XsdDataContractExporter()
exporter.Export(typeof<CommitInfo array>)
let schemas = exporter.Schemas.Schemas().Cast<Schema.XmlSchema>() |> Array.ofSeq
let schema = schemas.[1]

fun s -> s |> getXmlWriter |> schema.Write
|> getResultFromStream 
|> printfn "%s"