ymtszw/elm-xml-decode/2.0.0

NOTE: the latest version is 3.2.2

Xml.Decode

XML decoder module sharing the spirit of Json.Decode.

Using XmlParser as its parser component.

Basic Example

Examples in this package are doc-tested.

exampleDecoder : Decoder ( String, List Int )
exampleDecoder =
    map2 Tuple.pair
        (path [ "string", "value" ] (single string))
        (path [ "int", "values" ] (list int))


run exampleDecoder
    """
    <root>
        <string>
            <value>SomeString</value>
        </string>
        <int>
            <values>1</values>
            <values>2</values>
        </int>
    </root>
    """
--> Ok ( "SomeString", [ 1, 2 ] )

Types

type Decoder a

A function that knows how to decode an XML node into Elm value.

A function that knows how to decode list of XML nodes into Elm value.

Used in conjunction with "query" functions such as path.

type Error
= SimpleError Problem
| DetailedError (List String) Node Problem

Represents error on decode execution.

DetailedError will tell you:

  • where the error happened
  • what kind of node actually was there
type Problem
= NodeNotFound
| AttributeNotFound String
| Duplicate
| Unparsable String

Possible problems that can happen during decoding.

Decode Executor

run : Decoder a -> String -> Result String a

Synonym of decodeString.

decodeString : Decoder a -> String -> Result String a

Parses an XML string and decodes into Elm value.

Parsing will be done by XmlParser.parse

This function returns error in String. Should you need to separately handle Parser.Error and Xml.Decode.Error, explicitly use XmlParser.parse and decodeXml.

decodeXml : Decoder a -> Xml -> Result Error a

Decodes an XmlParser.Xml value into other type of Elm value.

It discards Document Type Definitoin (DTD) and Processing Instruction in XML, only cares about root XML node.

import XmlParser exposing (Xml, Node(..))


exampleDecoder : Decoder ( String, List Int )
exampleDecoder =
    map2 Tuple.pair
        (path [ "string", "value" ] (single string))
        (path [ "int", "values" ] (list int))


decodeXml exampleDecoder <|
    Xml [] Nothing <|
        Element "root" []
            [ Element "string" []
                [ Element "value" [] [ Text "SomeString" ]
                ]
            , Element "int" []
                [ Element "values" [] [ Text "1" ]
                , Element "values" [] [ Text "2" ]
                ]
            ]
--> Ok ( "SomeString", [ 1, 2 ] )

Decoders

string : Decoder String

Decodes an XmlParser.Node into String.

  • If the node is XmlParser.Text, extracts its value.
  • If the node is XmlParser.Element AND contains nothing, treat it as "empty text".
  • If the node is XmlParser.Element AND contains a single XmlParser.Text child, extracts its value.
  • Otherwise fails.

If you want to extract values from node attribute, use stringAttr and variants.

run string "string" --> Ok "string"

run string ""
--> Ok ""


run string "string"
--> Err "The node is not a simple text node. At: /, Node: string"
int : Decoder Int

Similar to string, but also tries to convert String to Int.

run int "<root>1</root>"
--> Ok 1


run int "<root>value</root>"
--> Err "could not convert string 'value' to an Int At: /, Node: <root>value</root>"
float : Decoder Float

Decodes to Float.

run float "<root>1.0</root>"
--> Ok 1.0


run float "<root>value</root>"
--> Err "could not convert string 'value' to a Float At: /, Node: <root>value</root>"
bool : Decoder Bool

Decodes to Bool.

In Xml Schema Definition (XSD), valid lexical representation of boolean values are,

  • 'true'
  • 'false'
  • '1'
  • '0'

We follow this specification, case-sensitively.

run bool "true" --> Ok True

run bool "value"
--> Err "Not a valid boolean value. At: /, Node: value"

Attribute Decoders

stringAttr : String -> Decoder String

Decodes an attribute value of XmlParser.Node into String.

Fails if the node does not have specified attribute.

run (stringAttr "attr") "<root attr='value'></root>"
--> Ok "value"


run (stringAttr "attr") "<root></root>"
--> Err "Attribute 'attr' not found. At: /, Node: <root></root>"
intAttr : String -> Decoder Int

Decodes an attribute value into Int.

run (intAttr "attr") "<root attr='1'></root>"
--> Ok 1


run (intAttr "attr") "<root attr='value'></root>"
--> Err "could not convert string 'value' to an Int At: /, Node: <root attr=\"value\"></root>"
floatAttr : String -> Decoder Float

Decodes an attribute value into Float.

run (floatAttr "attr") "<root attr='1.5'></root>"
--> Ok 1.5


run (floatAttr "attr") "<root attr='value'></root>"
--> Err "could not convert string 'value' to a Float At: /, Node: <root attr=\"value\"></root>"
boolAttr : String -> Decoder Bool

Decodes an attribute value into Bool.

run (boolAttr "attr") "<root attr='true'></root>"
--> Ok True


run (boolAttr "attr") "<root attr='value'></root>"
--> Err "Not a valid boolean value. At: /, Node: <root attr=\"value\"></root>"

List Decoders

Composes ListDecoder that results in a singular value.

It fails if:

  • there are multiple nodes, or,
  • there are no nodes.

Examples:

run (path [ "tag" ] (single string)) "<root><tag>string</tag></root>"
--> Ok "string"


run (path [ "tag" ] (single string)) "<root></root>"
--> Err "Node not found. At: /tag, Node: <root></root>"


run (path [ "tag" ] (single string)) "<root><tag>string1</tag><tag>string2</tag></root>"
--> Err "Multiple nodes found. At: /tag, Node: <root><tag>string1</tag><tag>string2</tag></root>"
list : Decoder a -> ListDecoder (List a)

Composes ListDecoder that results in a list of values.

This ListDecoder fails if any incoming items cannot be decoded.

run (path [ "tag" ] (list string)) "<root><tag>string1</tag><tag>string2</tag></root>"
--> Ok [ "string1", "string2" ]


run (path [ "tag" ] (list int)) "<root><tag>1</tag><tag>nonInt</tag></root>"
--> Err "could not convert string 'nonInt' to an Int At: /tag, Node: <tag>nonInt</tag>"
leakyList : Decoder a -> ListDecoder (List a)

Variation of list, which ignores items that cannot be decoded.

run (path [ "tag" ] (leakyList int)) "<root><tag>1</tag><tag>nonINt</tag></root>"
--> Ok [ 1 ]

Decoder Utilities

mapN series are backed by Result.mapN series, thus it only supports up to map5.

succeed : a -> Decoder a

Decoder that always succeed with the given value.

fail : String -> Decoder a

Decoder that always fail with the given message.

andThen : (a -> Decoder b) -> Decoder a -> Decoder b

Generates a decoder that depends on previous value.

map : (a -> value) -> Decoder a -> Decoder value

Transform a decoder.

run (map String.length string) "<root>string</root>"
--> Ok 6
map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value

Generates a decoder that combines results from two decoders.

It can be used for generating a decoder for a data type that takes two inputs. Also this is used as a building block of decoder composition helpers.

run (map2 Tuple.pair string string) "<root>string</root>"
--> Ok ( "string", "string" )
(a -> b -> c -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder value
(a -> b -> c -> d -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder value
(a -> b -> c -> d -> e -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder value
andMap : Decoder a -> Decoder (a -> b) -> Decoder b

Equivalent to Json.Decode.Extra.andMap, allows writing XML decoders in sequential style.

run (succeed Tuple.pair |> andMap (path ["string"] (single string)) |> andMap (path ["int"] (single int)) ) "string1" --> Ok ("string", 1)

Generates a decoder that results in the default value on failure.

run (withDefault 0 int) "<root>1</root>"
--> Ok 1


run (withDefault 0 int) "<root>Non Int value</root>"
--> Ok 0
maybe : Decoder a -> Decoder (Maybe a)

Generates a decoder that results in a Maybe value.

If the given decoder resulted in Err, it succeeds with Nothing. Otherwise (in cases of Ok,) it succeeds with Just value.

run (maybe int) "<root>1</root>"
--> Ok (Just 1)


run (maybe int) "<root>Non Int value</root>"
--> Ok Nothing
lazy : (() -> Decoder a) -> Decoder a

Generates a lazy decoder.

Similar to Json.Decode.lazy.

someRecordDecoder : Decoder SomeRecord
someRecordDecoder =
    map2 SomeRecord
        (path [ "path", "to", "string", "value" ] (single string))
        (path [ "path", "to", "list", "of", "someRecord" ] (list (lazy (\_ -> someRecordDecoder))))

With this, you can avoid "bad-recursion" error on compilation which happens when you define nested part of the above decoder as (list someRecordDecoder)

Node Locater

path : List String -> ListDecoder a -> Decoder a

Generates a Decoder that applies a ListDecoder at specified XML path.

Typical usage:

someRecordDecoder : Decoder SomeRecord
someRecordDecoder =
    map2 SomeRecord
        (path [ "string", "value" ] (single string))
        (path [ "int", "values" ] (list int))

Due to the nature of XML, you cannot distinguish a particular tag or tags hold whether "singular value" or "list of values", from the structure of XML document itself. This is opposite to JSON, where there can be only one field of a paticular key in a level, and its quantization is obvious from the shape of the value.

For this reason, this function always produce list of XmlParser.Nodes. Then they must be decoded using special decoder parts: ListDecoder. In the above example, single and list are ListDecoder generators.

If you want to perform complex resolution of multiple matches from path, you can implement your own ListDecoders.

Note that in the path, you must "start" at the root scope. For instance, to work with an XML document like:

<Root> <Path> <Target> Value </Target> </Path> </Root>

You should specify:

path [ "Path", "Target" ] (single string)

Basic usages:

run (path [ "tag", "nested" ] (single string)) "<root><tag><nested>string1</nested></tag></root>"
--> Ok "string1"


run (path [ "tag", "nested" ] (list string)) "<root><tag><nested>string1</nested><nested>string2</nested></tag></root>"
--> Ok [ "string1", "string2" ]


run (path [ "tag", "nested" ] (single (stringAttr "value"))) "<root><tag><nested value='attr1'></nested></tag></root>"
--> Ok "attr1"

Decoders will report errors with path at which error happened as well as nearest node:

run (path [ "tag", "nested" ] (single int)) "<root><tag><nested>string1</nested></tag></root>"
--> Err "could not convert string 'string1' to an Int At: /tag/nested, Node: <nested>string1</nested>"

Pipeline APIs

Allow writing Decoders in Pipeline-style, just like Json.Decode.Pipeline.

requiredPath : List String -> ListDecoder a -> Decoder (a -> b) -> Decoder b

Decodes value at required XML path.

pipelineDecoder : Decoder ( String, List Int )
pipelineDecoder =
    succeed Tuple.pair
        |> requiredPath [ "path", "to", "string", "value" ] (single string)
        |> requiredPath [ "path", "to", "int", "values" ] (list int)


run pipelineDecoder
    """
    <root>
        <path>
            <to>
                <string>
                    <value>SomeString</value>
                </string>
                <int>
                    <values>1</values>
                    <values>2</values>
                </int>
            </to>
        </path>
    </root>
    """
--> Ok ( "SomeString", [ 1, 2 ] )
optionalPath : List String -> ListDecoder a -> a -> Decoder (a -> b) -> Decoder b

Tries to decode value at optional XML path. Uses default value if the node is missing.

decoderWithDefault : Decoder String
decoderWithDefault =
    succeed identity
        |> optionalPath [ "optional", "path" ] (single string) "default"


run decoderWithDefault "<root><optional><path>string</path></optional></root>"
--> Ok "string"


run decoderWithDefault "<root></root>"
--> Ok "default"
possiblePath : List String -> ListDecoder a -> Decoder (Maybe a -> b) -> Decoder b

Decodes value at possible XML path into Maybe value.

maybeDecoder : Decoder (Maybe String)
maybeDecoder =
    succeed identity
        |> possiblePath [ "possible", "path" ] (single string)


run maybeDecoder "<root><possible><path>string</path></possible></root>"
--> Ok (Just "string")


run maybeDecoder "<root></root>"
--> Ok Nothing

If you want to apply default value when the node is missing, use optionalWith.

Error Utility

errorToString : Error -> String

Convert Error to a formatted string.