Customizations
For some schemas the generator may produce something not correct (for example ignoring identity constraints). Or maybe the generated values simply look ugly to you (you may want meaningful words instead of random gibberish). There are a few options to customize the generated output:
Simply change the output
Obviously one option is to change the produced output by using XSLT or by any other means.
For example since the output is an XElement
, the System.Xml.Linq
API is already well suited for this task:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The same in C# is:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The above example is just to get the idea. If you don't like imperative code
you can map the element to another one, but for little fixes the
mutability allowed in System.Xml.Linq
may be just fine.
Providing your own generators
Custom generators are of course a superior alternative.
To keep things simple, customizations are allowed only for elements and for global complex types. The only way to customize attributes and values of simple types, is to provide custom generators for the enclosing elements or global complex types. Hopefully this is an acceptable limitation for most practical purposes.
FsCheck provides excellent documentation about creating custom generators, both in F# and in C#.
Anyway in AntaniXml there is also an option to provide only a mapping function, so the default
generators are still used but the generated elements are then transformed with a custom function.
Custom generators are expected to produce XElement
instances.
In case of complex types the name of the generated element actually is not important because it
will be replaced by the proper element name required in each context.
Customizing a global complex type
The qualified name of the complex type is used as an identifier to pick up a custom generator for all the elements explicitly defined to be of such a type. Hence only global types can be customized since anonymous types lack a name to be used as an identifier. Also in case of an element whose anonymous type is derived from a global one for which a customization is provided, the custom generator is not picked up.
Customizing elements
Custom generators for elements instead may have a broader reach. When a custom generator is provided for elements with a given qualified name, that generator is used for all elements with such a name, regardless of their type. So, in case the schema is designed in Russian doll style, custom generators for elements are a better bet. Just beware of (nasty) schemas where two distinct element definitions describe elements with the exact same name but different content models. In this case a custom generator may not be used because it would apply to both element definitions.
Let's give a few concrete examples. In the following we'll be using this schema
(assuming a string variable xsdText
holds it):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
First we show a customization for a complex type:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The equivalent C# code is the following:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The main thing to notice is the CustomGenerators
type and its ForComplexType
method accepting a mapping
for transforming in uppercase the value of the id
attribute for all the elements of type barType
.
This example is about customizing the default generator with a custom mapping.
Later we also show an example of replacing a custom generator with one created from scratch.
The Arbitrary
instance created embeds a generator, and Gen.sample
is the FsCheck method to create
samples, specifying a size (5 in the example but let's ignore the concept of size for now)
and the number of samples to create.
The element generated may look like this:
1: 2: 3: 4: |
|
Yes, text is gibberish (but for testing purposes usually it's a good idea to probe a system
with strange data), anyway you see the value of attribute id
is all upper case thanks to
the customization.
In the next example instead we are creating a generator from scratch using the FsCheck combinators. Starting from a set of fixed string values, a generator ranging over such a set is built and then mapped to another one that wraps the random string values in xml elements.
1: 2: 3: 4: 5: 6: 7: 8: |
|
Here's the C# version of the same example:
1: 2: 3: 4: 5: 6: 7: |
|
Now baz
is no more gibberish because we took full control of the random generation:
1: 2: 3: 4: 5: 6: 7: 8: |
|
FsCheck provides many more combinators to compositionally create many kinds of generators.
Full name: Customizations.xsdText
Full name: Customizations.samples
namespace System.Xml.Schema
--------------------
type Schema =
new : xmlSchemaSet:XmlSchemaSet -> Schema
member Arbitrary : elementName:XmlQualifiedName -> Arbitrary<XElement>
member Arbitrary : elementName:XmlQualifiedName * customizations:CustomGenerators -> Arbitrary<XElement>
member Generator : elementName:XmlQualifiedName -> IXmlElementGenerator
member IsValid : element:XElement -> bool
member Validate : element:XElement -> ValidationResult
member Validate : element:string -> ValidationResult
member GlobalElements : IEnumerable<XmlQualifiedName>
static member CreateFromText : schemaText:string -> Schema
static member CreateFromUri : schemaUri:string -> Schema
Full name: AntaniXml.Schema
--------------------
new : xmlSchemaSet:Schema.XmlSchemaSet -> Schema
type XmlQualifiedName =
new : unit -> XmlQualifiedName + 2 overloads
member Equals : other:obj -> bool
member GetHashCode : unit -> int
member IsEmpty : bool
member Name : string
member Namespace : string
member ToString : unit -> string
static val Empty : XmlQualifiedName
static member ToString : name:string * ns:string -> string
Full name: System.Xml.XmlQualifiedName
--------------------
XmlQualifiedName() : unit
XmlQualifiedName(name: string) : unit
XmlQualifiedName(name: string, ns: string) : unit
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.mapi
XContainer.Descendants(name: XName) : System.Collections.Generic.IEnumerable<XElement>
member Equals : obj:obj -> bool
member GetHashCode : unit -> int
member LocalName : string
member Namespace : XNamespace
member NamespaceName : string
member ToString : unit -> string
static member Get : expandedName:string -> XName + 1 overload
Full name: System.Xml.Linq.XName
XName.Get(localName: string, namespaceName: string) : XName
System.Int32.ToString(provider: System.IFormatProvider) : string
System.Int32.ToString(format: string) : string
System.Int32.ToString(format: string, provider: System.IFormatProvider) : string
Full name: Microsoft.FSharp.Collections.Seq.iter
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Customizations.foo
Full name: Customizations.barType
Full name: Customizations.toUpper
type XElement =
inherit XContainer
new : name:XName -> XElement + 4 overloads
member AncestorsAndSelf : unit -> IEnumerable<XElement> + 1 overload
member Attribute : name:XName -> XAttribute
member Attributes : unit -> IEnumerable<XAttribute> + 1 overload
member DescendantNodesAndSelf : unit -> IEnumerable<XNode>
member DescendantsAndSelf : unit -> IEnumerable<XElement> + 1 overload
member FirstAttribute : XAttribute
member GetDefaultNamespace : unit -> XNamespace
member GetNamespaceOfPrefix : prefix:string -> XNamespace
member GetPrefixOfNamespace : ns:XNamespace -> string
...
Full name: System.Xml.Linq.XElement
--------------------
XElement(name: XName) : unit
XElement(other: XElement) : unit
XElement(other: XStreamingElement) : unit
XElement(name: XName, content: obj) : unit
XElement(name: XName, [<System.ParamArray>] content: obj []) : unit
System.String.ToUpper(culture: System.Globalization.CultureInfo) : string
Full name: Customizations.cust
type CustomGenerators =
new : unit -> CustomGenerators
member ForComplexType : complexTypeName:XmlQualifiedName * mapping:Func<XElement,XElement> -> CustomGenerators
member ForComplexType : complexTypeName:XmlQualifiedName * generator:Gen<XElement> -> CustomGenerators
member ForElement : elementName:XmlQualifiedName * mapping:Func<XElement,XElement> -> CustomGenerators
member ForElement : elementName:XmlQualifiedName * generator:Gen<XElement> -> CustomGenerators
member private ToMaps : unit -> Maps
Full name: AntaniXml.CustomGenerators
--------------------
new : unit -> CustomGenerators
Full name: Microsoft.FSharp.Core.Operators.id
Full name: Customizations.baz
Full name: Customizations.abcGen
Full name: Customizations.cus