|
(* Well at least it works now *) |
|
|
|
module Types |
|
|
|
open System |
|
open System.IO |
|
open System.Collections |
|
open System.Collections.Generic |
|
open System.Reflection |
|
open System.Resources |
|
open System.Linq |
|
open System.Xml |
|
open System.Xml.Serialization |
|
open System.Xml.Schema |
|
open System.CodeDom |
|
open System.CodeDom.Compiler |
|
open System.Collections.Specialized |
|
open System.Collections.ObjectModel |
|
open System.Diagnostics |
|
open System.Threading |
|
open FSharp.Compiler.CodeDom |
|
|
|
// side-effect operators |
|
let (!!) f = f |> ignore |
|
let (>>!) a b = Console.WriteLine(a, b.ToString()) |> ignore |
|
|
|
// predicates |
|
let inline notNil value = not (obj.ReferenceEquals(value, null)) |
|
|
|
/// ComplexType XmlSchemaElement |
|
type XmlSchemaComplexType with |
|
|
|
member x.attributes = |
|
seq { |
|
for a in x.Attributes do |
|
yield a :?> XmlSchemaAttribute |
|
} |
|
|
|
member x.attributeUses = |
|
seq { |
|
for au in x.AttributeUses do |
|
yield au |
|
} |
|
|
|
|
|
/// File system and input/output operations |
|
type Fsio = |
|
|
|
static member slurp (inFile:string) = |
|
use rdr = new StreamReader(inFile) |
|
rdr.ReadToEnd() |
|
|
|
/// Spits a string of content to the named output file path. |
|
static member spit outFile content = |
|
IO.File.WriteAllText(outFile, content) |
|
|
|
/// Returns the assemblies equivalent of present working directory |
|
static member pwd = Environment.CurrentDirectory |
|
|
|
/// Takes a file path (non-root) and prefixes the current assembly working directory. |
|
static member joinPwd path = IO.Path.Combine(Fsio.pwd, path) |
|
|
|
//++ Augmentations |
|
|
|
//+ XmlSchema(s) |
|
|
|
type XmlSchema with |
|
|
|
member x.namespaces = x.Namespaces.ToArray() |
|
|
|
static member init (txt:string) = |
|
let h = new ValidationEventHandler(fun _ -> failwith "invalid schema") |
|
use rdr = new XmlTextReader(new StringReader(txt)) |
|
XmlSchema.Read(rdr, h) |
|
|
|
static member load (path:string) = |
|
XmlSchema.init (Fsio.slurp path) |
|
|
|
member x.seqSchemaTypes = |
|
seq { |
|
for st in x.SchemaTypes.Values do |
|
yield st :?> XmlSchemaType |
|
} |
|
|
|
member x.seqElements = |
|
seq { |
|
for se in x.Elements.Values do |
|
yield se :?> XmlSchemaElement |
|
} |
|
|
|
member x.seqAttributes = |
|
seq { |
|
for sa in x.Attributes.Values do |
|
yield sa :?> XmlSchemaAttribute |
|
} |
|
|
|
member x.seqItems = |
|
seq { |
|
for si in x.Items do |
|
yield si |
|
} |
|
|
|
member x.print = |
|
use ms : MemoryStream = new MemoryStream(1024) |
|
x.Write(ms) // write schema to memory stream |
|
(!!) (ms.Seek(Int64.Parse("0"), SeekOrigin.Begin)) |
|
use rdr : StreamReader = new StreamReader(ms) |
|
rdr.ReadToEnd() |
|
|
|
member x.spit msg = |
|
Fsio.spit (Fsio.joinPwd msg + ".xsd") x.print |
|
|
|
member x.itemProps = |
|
x.seqItems |> Seq.map (fun z -> z.GetType().GetProperties()) |
|
|
|
member x.itemNames = x.seqItems |> Seq.map (fun item -> item.ToString()) |
|
|
|
member x.filter pred = x.seqItems |> Seq.filter pred |
|
|
|
type XmlSchemaSet with |
|
|
|
/// Returns a pristine new XmlSchemaSet instance from a static method call, bind value as needed. |
|
/// Automatically resolves include/import namespace URL's and schemas (e.g. basisschema.xsd) |
|
static member init : XmlSchemaSet = |
|
let this = new XmlSchemaSet() |
|
this.XmlResolver <- new XmlUrlResolver() |
|
this |
|
|
|
static member create (xs:XmlSchema) = |
|
let this = XmlSchemaSet.init // delegate |
|
(!!) (this.Add(xs)) |
|
this |
|
|
|
static member load (msg:string) = |
|
let this = new XmlSchemaSet() |
|
let path = Fsio.joinPwd msg + ".xsd" |
|
(!!) (this.Add("", path)) |
|
this |
|
|
|
/// Returns a XmlSchemaSet instance with the provided sequence schemas added to it. Uses static |
|
/// method call to init and itself is a static member method. Result is a option maybe value (Some/None) |
|
static member initially schemas : XmlSchemaSet option = |
|
let this = XmlSchemaSet.init |
|
schemas |> Seq.iter (fun (x : XmlSchema) -> !!(this.Add(x))) |
|
this.Compile() |
|
if this.IsCompiled then Some this |
|
else None |
|
|
|
/// Returns a list with all schemas from the schema set yielded as values from the collection. |
|
member this.schemas = |
|
[ for xs in this.Schemas() do |
|
yield xs :?> XmlSchema ] |
|
|
|
/// Predicate returns true if the schema set does not contain any schemas in it. |
|
member this.isEmpty = this.schemas |> List.isEmpty |
|
|
|
/// Returns a fully configured and initialized XmlReaderSettings object set on the current instance. |
|
member this.validationSettings = |
|
let rdrSettings = |
|
XmlReaderSettings |
|
(Schemas = this, ValidationType = ValidationType.Schema, |
|
ValidationFlags = (XmlSchemaValidationFlags.ProcessIdentityConstraints |
|
||| XmlSchemaValidationFlags.ReportValidationWarnings)) |
|
rdrSettings.ValidationEventHandler.Add(fun args -> |
|
match args.Severity with |
|
| XmlSeverityType.Warning -> printfn "%A" args.Message |
|
| _ -> printfn "%A" (args.Exception.ToString())) |
|
rdrSettings |
|
|
|
/// Static method which takes a list of one or more schemas and a file to validate. Returns generic |
|
/// deserialized results. |
|
static member deserialize (schemas : XmlSchema list) (file : string) func = |
|
try |
|
let this = (XmlSchemaSet.initially schemas |> Option.get) |
|
let conf = this.validationSettings |
|
use input = new StreamReader(file) |
|
use reader = XmlReader.Create(input, conf) |
|
let serializer = XmlSerializer(typeof<'T>) |
|
serializer.Deserialize(reader) :?> 'T |> func |
|
with |
|
| :? XmlException as xe -> (>>!) xe.Message "XML Parse Error" |
|
| :? InvalidOperationException as ioe -> |
|
(>>!) ioe.InnerException.Message "XML Serialization Error" |
|
| e -> (>>!) e.Message "Other Error" |
|
|
|
//+ CodeDom |
|
|
|
/// Augmentation of CodeAttributeDeclaration |
|
type CodeAttributeDeclaration with |
|
/// Adds a predicate to code attribute decls. which returns true when a XmlTypeAttribute |
|
member x.IsXmlTypeAttribute = |
|
x.Name = "System.Xml.Serialization.XmlTypeAttribute" |
|
|
|
/// Augmentation of CodeTypeDeclaration |
|
type CodeTypeDeclaration with |
|
|
|
/// Returns a seq of all custom attributes in the code type declaration |
|
member x.customAttrs = |
|
seq { |
|
for ca in x.CustomAttributes do |
|
yield ca |
|
} |
|
|
|
/// Returns a array of only those custom attributes which are of type XmlTypeAttribute |
|
static member xmlTypeAttrs z = |
|
z |> Seq.filter (fun (cad:CodeAttributeDeclaration) -> |
|
cad.IsXmlTypeAttribute) |
|
|> List.ofSeq // ffs keep this in mind! PRODUCE A NEW VALUE!!!! |
|
|
|
/// Triggers the side effect of clearing custom attributes and then re-adding those selected |
|
/// XmlCodeAttributes which have been selected (filtered) by means of predicate in XmlTypeAttr seq |
|
member x.xmlTypeAttrsOnly = |
|
let xtattr = CodeTypeDeclaration.xmlTypeAttrs x.customAttrs |
|
x.CustomAttributes.Clear() |
|
xtattr |> Seq.iter (fun item -> !!(x.CustomAttributes.Add(item))) |
|
xtattr |
|
|
|
/// Augmentation of CodeNamespace type |
|
type CodeNamespace with |
|
|
|
/// Initializes a new instance of a CodeNamespace with given namespace string. |
|
static member init ns : CodeNamespace = new CodeNamespace(ns) |
|
|
|
/// Creates a XmlCodeExporter set to this instance of a CodeNamespace class |
|
member x.exporter : XmlCodeExporter = new XmlCodeExporter(x) |
|
|
|
/// Returns a seq of all code types in the current code namespace |
|
member x.seqCodeTypes = |
|
seq { |
|
for ct in x.Types do |
|
yield ct |
|
} |
|
|
|
/// Returns a seq of all code namespace imports in the current code namespace |
|
member x.seqCodeNsImports = |
|
seq { |
|
for ci in x.Imports do |
|
yield ci |
|
} |
|
|
|
/// Returns a seq of all code comments in the current code namespace |
|
member x.seqCodeComments = |
|
seq { |
|
for cc in x.Comments do |
|
yield cc |
|
} |
|
|
|
/// Augmentation of FSharpCodeProvider |
|
type FSharpCodeProvider with |
|
|
|
/// Static constructor (initializer) member method which takes a CodeNamespace to validate identifiers |
|
/// and finally produces a pristine FSharpCodeProvider value each call |
|
static member init ns : FSharpCodeProvider = |
|
CodeGenerator.ValidateIdentifiers(ns) |
|
new FSharp.Compiler.CodeDom.FSharpCodeProvider() |
|
|
|
(* |
|
- XmlSchemas on steroids. Augmentation is a powerful concept. A few words: do note that in F# we're not |
|
- allowed to define during augmentation (the type + `with' denotes it) any mutable fields e.g. let mutable foo = "bar" |
|
- often you may find playing down here to take the following forms: |
|
- * static constructors returning a pristine instance of some object |
|
- * side-effects mutating the CURRENT schema instance |
|
- * side-effects called once in other instances, either passed as arguments |
|
- * or destructured as tuppled arguments and partially (pattern matched) bound to local variables |
|
Once a certain object instance is not longer required, or when we know how to retrieve that instance or |
|
when we can call parents/children instead, it is often discarded (foo, _, _) = |
|
Since most operations required for ones own Xsd.exe go by XmlSchemas, monkeypatching it with some |
|
auxiliary static and instance member methods and values makes it heaps easier to work with the intermediate |
|
moving parts. |
|
|
|
|
|
*) |
|
type XmlSchemas with |
|
|
|
/// Augments the same pattern of static methods as XmlSchemaSet. Init will return a single instance. |
|
static member init : XmlSchemas = new XmlSchemas() |
|
|
|
/// Takes a set of schemas and adds them to the collection, validating in the meanwhile. Then compiles |
|
/// and returns the results. |
|
static member initially schemas : XmlSchemas = |
|
let this = XmlSchemas.init |
|
let hndl = |
|
new ValidationEventHandler(fun _ -> |
|
failwith "XmlSchema not compiled") |
|
schemas |> Seq.iter (fun (x : XmlSchema) -> !!(this.Add(x))) |
|
this.Compile(hndl, true) |
|
this |
|
|
|
/// There is no easy way to retrieve all schemas once inside a (serializer) XmlSchemas collection, |
|
/// however, here we defined a inner recursive function which moves the cursor in the collection |
|
/// as long as needed, returning the value on each successive seed fed to the fn. |
|
/// recursive fn was whacked so this works at least,... took me 1.5 days |
|
member x.schemas : XmlSchema list = |
|
let xse = new XmlSchemaEnumerator(x) |
|
(!!) (xse.MoveNext()) |
|
let a = xse.Current |
|
(!!) (xse.MoveNext()) |
|
let b = xse.Current |
|
[a; b] |
|
|
|
/// Takes a function and applies it to the schemas instance mapping over the member property value |
|
member private x.applyToSchemas fn = x.schemas |> List.map fn |
|
|
|
/// Xml schema types flat sequence |
|
member x.schemasTypes = |
|
x.applyToSchemas (fun (xsd : XmlSchema) -> xsd.seqSchemaTypes) |
|
|> Seq.concat // iron it out (flatten) |
|
|
|
/// Xml schema elements flat sequence |
|
member x.schemasElements = |
|
x.applyToSchemas (fun (xsd : XmlSchema) -> xsd.seqElements) |
|
|> Seq.concat |
|
|
|
/// Instances of popular xml schema to code dom conversions |
|
member x.instances ns : CodeNamespace * XmlSchemaImporter * XmlCodeExporter = |
|
ns |> CodeNamespace.init |> (fun cns -> cns, new XmlSchemaImporter(x), cns.exporter) |
|
|
|
/// Imports type mappings, creates a code namespace |
|
member x.import : CodeNamespace * XmlSchemaImporter * XmlCodeExporter = |
|
let nsname = "ZorgMatch.Domain.Wlz" |
|
|
|
let (cns, importer, exporter) = x.instances nsname |
|
|
|
|
|
// mapped schema types imported and stored |
|
let types : XmlTypeMapping list = |
|
x.schemasTypes |
|
|> Seq.toList |
|
|> List.map |
|
(fun (ct : XmlSchemaType) -> |
|
importer.ImportSchemaType(ct.QualifiedName) |
|
) |
|
|
|
// elements import and type mapping stored as value |
|
let elems : XmlTypeMapping list = |
|
x.schemasElements |
|
|> Seq.toList |
|
|> List.map |
|
(fun (ce : XmlSchemaElement) -> |
|
importer.ImportTypeMapping(ce.QualifiedName)) |
|
|
|
// cons lists and export mappings (side-effect) |
|
(types @ elems) |> List.iter (fun cm -> exporter.ExportTypeMapping(cm)) |
|
|
|
// we can just return all 'old' instances since the iterations mutation those specific objects |
|
(cns, importer, exporter) |
|
|
|
/// Performs side-effects of populating the instance schemas contained xml elements, types and attributes |
|
/// to the code dom equivallent objects and returns a triple of code ns, attributes and types. |
|
member x.codeDeclarations = |
|
let ns, _, _ = x.import |
|
let codeTypes = ns.seqCodeTypes |
|
let xattrs = |
|
codeTypes |
|
|> Seq.map |
|
(fun (ct : CodeTypeDeclaration) -> |
|
ct.xmlTypeAttrsOnly) // oops, was missing the -Only (which has side-effects) |
|
|> Seq.concat |> Seq.toList |
|
(ns, xattrs, codeTypes |> Seq.toList) |
|
|
|
/// Code provider instance for FSharp generation from Xsd. Contains a tupple including code namespace. |
|
member x.codeProvider : CodeNamespace * FSharpCodeProvider = |
|
let ns, _, _ = x.codeDeclarations |
|
ns, FSharpCodeProvider.init ns |
|
|
|
/// Dumps generated F# code to the console. |
|
member x.dumpCode = |
|
let ns, codeProvider = x.codeProvider |
|
use writer : StringWriter = new StringWriter() |
|
codeProvider.GenerateCodeFromNamespace |
|
(ns, writer, new CodeGeneratorOptions()) |
|
writer.GetStringBuilder().ToString() |
|
|
|
/// Writes the generated F# code to file. |
|
member x.writeCode (path : string) (name : string) = |
|
let ns, codeProvider = x.codeProvider |
|
use writer : StringWriter = new StringWriter() |
|
/// Side effect, mutating the writer object to later retrieve the string builder. The namespace |
|
/// contains the mutated code types and attributes generated from taking in Xml types and values. |
|
codeProvider.GenerateCodeFromNamespace |
|
(ns, writer, new CodeGeneratorOptions()) |
|
let outFile = IO.Path.Combine(path, (name.ToString() + ".fs")) |
|
Fsio.spit outFile (writer.GetStringBuilder().ToString()) |
|
if IO.File.Exists(outFile) then ("Created " + outFile) |
|
else ("Error, failed to locate generated file at " + outFile) |
|
|
|
|
|
//++ Custom types |
|
|
|
/// Load Xml schemas from a compiled/generated .resources file made |
|
/// from a .resx with all messages embedded |
|
type SchemaResources = |
|
|
|
/// Loads a .resources file and returns a map of all schemas inside. |
|
static member load dom = |
|
let env = Environment.CurrentDirectory |
|
let frm = String.Format("{0}.resources", dom.ToString()) |
|
let res = IO.Path.Combine(env, frm) |
|
let filename = |
|
match IO.File.Exists(res) with |
|
| true -> res |
|
| false -> failwith ("Resource file not found in " + res) |
|
let stream = File.Open(filename, IO.FileMode.Open) |
|
use rdr : ResourceReader = new Resources.ResourceReader(stream) |
|
seq { for x in rdr do yield x :?> DictionaryEntry } |
|
|> Seq.map (fun de -> de.Key.ToString(), de.Value.ToString()) |
|
|> Map.ofSeq |
|
|
|
/// Sets-up XmlSchema for a message and has a XmlSchemaSet add and compile them. |
|
// static member setup = |
|
// XmlSchema.init >> XmlSchemaSet.slurp |
|
|
|
/// Takes a domain e.g. iWlz10 and a message key e.g. IO31 and returns the schema resources. |
|
static member init dom msg = |
|
let text = (SchemaResources.load dom).[msg] |
|
let xs = new XmlSchemas() |
|
let a = XmlSchema.load (Fsio.joinPwd "basisschema.xsd") |
|
let b = text |> XmlSchema.init |
|
(!!) (xs.Add(a)) |
|
(!!) (xs.Add(b)) |
|
let hndl = new ValidationEventHandler(fun _ -> failwith "XmlSchema not compiled") |
|
xs.Compile(hndl, true) |
|
xs |
|
|
|
|
|
|
|
|
|
|
|
// printfn "%A" text |
|
// let schemas = setup.schemas |> XmlSchemas.initially |
|
// schemas |
|
|
|
|
|
// (Fsio.joinPwd msg + ".xsd") |
|
// o.spit msg // persist to working directory since we might need a physical file after all |