An open API service indexing awesome lists of open source software.

https://github.com/typeable/xml-isogen

TemplateHaskell generators for XML-isomorphic data types, instances for parsing and rendering. A convenient DSL to define types.
https://github.com/typeable/xml-isogen

Last synced: 10 months ago
JSON representation

TemplateHaskell generators for XML-isomorphic data types, instances for parsing and rendering. A convenient DSL to define types.

Awesome Lists containing this project

README

          

# xml-isogen

[![Build Status](https://travis-ci.org/typeable/xml-isogen.svg?branch=master)](https://travis-ci.org/typeable/xml-isogen)

TemplateHaskell generators for XML-isomorphic data types, including
instances for parsing and rendering. A convenient DSL to define those
types.

Essentially it's a haskell DSL which allows its users to generate XML parsers and generators for haskell datatypes.
See also [xsd-isogen](https://github.com/typeable/xsd-isogen)

## Tutorial

Lets go through series of examples. First things first:

```haskell
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module Tutorial
where

import Prelude hiding ((^))
import Control.DeepSeq -- from deepseq
import Data.Text
import Data.THGen.XML -- from xml-isogen
import Text.XML.Writer -- from xml-conduit-writer
import Text.XML.DOM.Parser -- from dom-parser
```

### Records

Lets say we want to parse and/or generate an XML file of the following form:

```XML


John


john@example.com

```

Person has a name and an email, and email could be omitted. With `xml-isogen` it's
enough to write the following definition:

```haskell
"Person" =:= record ParserAndGenerator
! "name" [t|Text|]
? "email" [t|Text|]
```

At this point you can load the module into `ghci` and check what was generated for us
so far:

```
*Tutorial> :browse
data XmlPerson
= XmlPerson {_xpName :: !Text, _xpEmail :: !(Maybe Text)}
xpEmail ::
lens-4.18.1:Control.Lens.Type.Lens' XmlPerson (Maybe Text)
xpName :: lens-4.18.1:Control.Lens.Type.Lens' XmlPerson Text
```

We have a data type `XmlPerson` with two fields and two lenses. Note that the
fields have a prefix built of an underscore and all upper case characters
and digits of the type name.

Lets take a closer look at `XmlPerson`:

```
*Tutorial> :i XmlPerson
data XmlPerson
= XmlPerson {_xpName :: !Text, _xpEmail :: !(Maybe Text)}
-- Defined at Tutorial.hs:13:1
instance Show XmlPerson -- Defined at Tutorial.hs:13:1
instance Eq XmlPerson -- Defined at Tutorial.hs:13:1
instance FromDom XmlPerson -- Defined at Tutorial.hs:13:1
instance ToXML XmlPerson -- Defined at Tutorial.hs:13:1

```

We have
[FromDom](https://hackage.haskell.org/package/dom-parser-3.1.0/docs/Text-XML-DOM-Parser-FromDom.html#t:FromDom)
and
[ToXML](https://hackage.haskell.org/package/xml-conduit-writer-0.1.1.2/docs/Text-XML-Writer.html#t:ToXML)
instance generated for us. That's because
we instructed `xml-isogen` to generate them using the `ParserAndGenerator` noun. You can specify
also `Parser` or `Generator` if you want only one of them.

The `_xpEmail` field is optional; that's because we prefixed it with `?` modifier.
Here is the list of possible modifiers that affect types:

Modifier | Description | Generated Type
--- | --- | ---
`!` | required | `a`
`?` | optional | `Maybe a`
`*` | repeated | `[a]`
`+` | nonempty | `NonEmpty a`

### Supported types

Fields in a record may have any types as long as they are instances of `Eq`, `Show`,
`NFData`, `FromDom` (for the parser) and `ToXml` (for the generator). Remember
though that TemplateHaskell requires types to be available before they are used
in a splice.

You can omit field types altogether, in that case the type will be assumed to be a
capitalized field name with an `Xml` prefix.
It's your responsibility to make sure that type exists.
Example:

```haskell
newtype XmlEmail = XmlEmail Text
deriving (Eq, Show, NFData, ToXML)

instance FromDom XmlEmail where
fromDom = XmlEmail <$> fromDom

"Example1" =:= record Parser
! "email" -- will have type XmlEmail
```

### Enumerations

Often XML element can contain only limited number of possible values. Lets define
a type `Status` that can have only values `Active`, `Pending` or `Deleted`:

```haskell
"Status" =:= enum ParserAndGenerator
& "Active"
& "Pending"
& "Deleted"
```

This definition will generate the following type for us:

```haskell
data XmlStatus
= XmlStatusActive
| XmlStatusPending
| XmlStatusDeleted
| UnknownXmlStatus !String
```

It has all the necessary instances, so you can use it as a type for a field.

### Append content

Sometimes the XML you are dealing with contains a mix of elements and immediate
content. Something like the following:

```XML


I am

totally

weird

```

You can model this with an "append content" modifier `^`. It will instruct
`xml-isogen` to append content of the field as it is, without wrapping it
into an XML element. For our case it may look like this:

```haskell
"Example2" =:= record ParserAndGenerator
! "field1" [t|Text|]
^ "mixed" [t|Text|]
! "field2" [t|Text|]
```

After parsing the XML above, we'll get the following:

```haskell
XmlExample2 {_xe2Field1 = "I am", _xe2Mixed = "totally", _xe2Field2 = "weird"}
```

### Attributes

`xml-isogen` also supports XML attributes using `!%` and `?%` modifiers:

```haskell
"Example3" =:= record ParserAndGenerator
! "field1" [t|Text|]
!% "attribute1" [t|Text|]
?% "attribute2" [t|Text|]

"Body" =:= record ParserAndGenerator
! "root" [t|XmlExample3|]
```

The following two types will be generated:

```haskell
data XmlExample3
= XmlExample3 {_xe3Field1 :: !Text,
_xe3Attribute1 :: !Text,
_xe3Attribute2 :: !(Maybe Text)}

newtype XmlBody = XmlBody {_xbRoot :: XmlExample3}
```

The `_xe3Attribute2` is optional because we used `?%` modifier. Attributes will be
attached to parent XML element. Here is an example of the generated XML file:

```XML


hello

```

Note that attributes are attached to the parent XML element, that's why we needed
`XmlBody` type here.

And here is what you get after parsing the XML:

```haskell
XmlExample3 {_xe3Field1 = "hello", _xe3Attribute1 = "world", _xe3Attribute2 = Nothing}
```

### Namespaces

Often XSD schema requires XML elements to be qualified with a namespace. To instruct
`xml-isogen` to qualify fields, specify namespace is a curly brackets:

```haskell
"Example4" =:= record ParserAndGenerator
! "field1" [t|Text|]
! "{http://example.com/1}field2" [t|Text|]
! "{http://example.com/2}field3" [t|Text|]
```

Here is the generated XML:

```XML

hello

world

!

```

### Nillable types

Sometimes optional element in XML are encoded using `nil="true"` attribute instead of
omitting the element. (The `nil` attribute comes from `http://www.w3.org/2001/XMLSchema-instance` namespace). With `xml-isogen` you handle it using the `Nillable` type:

```haskell
"Example5" =:= record ParserAndGenerator
! "field" [t|Nillable Text|]
```

If the field contains the value `Nothing`, like this

```haskell
XmlExample5 { _xe5Field = Nillable Nothing}
```

then the following XML will be generated:

```XML

```

## Development

To start working with `xml-isogen` using nix use:

```
nix-shell --packages cabal2nix --run "cabal2nix ." > default.nix
nix-shell
cabal v2-build
```