XML decoder module sharing the spirit of Json.Decode
.
Using XmlParser
as its parser component.
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 ] )
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.
They are constructed by functions such as single
or list
,
then used in conjunction with path
.
See path
for examples.
Represents error on decode execution.
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 -> XmlParser.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 ] )
string : Decoder String
Decodes an XmlParser.Node
into String
.
XmlParser.Text
, extracts its value.XmlParser.Element
AND contains nothing, treat it as "empty text".XmlParser.Element
AND contains a single XmlParser.Text
child,
extracts its value.If you want to extract values from node attribute, use stringAttr
and variants.
run string "<root>string</root>"
--> Ok "string"
run string "<root></root>"
--> Ok ""
run string "<root><nested>string</nested></root>"
--> Err "Node: <root><nested>string</nested></root>\nThe node is not a simple text node."
int : Decoder Basics.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 "Node: <root>value</root>\ncould not convert string 'value' to an Int"
float : Decoder Basics.Float
Decodes to Float
.
run float "<root>1.0</root>"
--> Ok 1.0
run float "<root>value</root>"
--> Err "Node: <root>value</root>\ncould not convert string 'value' to a Float"
bool : Decoder Basics.Bool
Decodes to Bool
.
In Xml Schema Definition (XSD), valid lexical representation of boolean values are,
We follow this specification, case-sensitively.
run bool "<root>true</root>"
--> Ok True
run bool "<root>value</root>"
--> Err "Node: <root>value</root>\nNot a valid boolean value."
node : Decoder XmlParser.Node
Decodes an XmlParser.Node
into itself.
See XmlParser
to further decompose the node.
import XmlParser exposing (Node(..))
run (path ["path", "to", "target"] (list node)) """<root><path><to><target><child></child><child></child></target><target></target></to></path></root>"""
--> Ok [Element "target" [] [Element "child" [] [],Element "child" [] []],Element "target" [] []]
run (path ["path", "to", "target"] (single node)) """<root><path><to><target><child></child><child></child></target><target></target></to></path></root>"""
--> Err ("Path: /path/to/target\nNode: <to><target><child></child><child></child></target><target></target></to>\nMultiple nodes found.")
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 "Node: <root></root>\nAttribute 'attr' not found."
intAttr : String -> Decoder Basics.Int
Decodes an attribute value into Int
.
run (intAttr "attr") "<root attr='1'></root>"
--> Ok 1
run (intAttr "attr") "<root attr='value'></root>"
--> Err "Node: <root attr=\"value\"></root>\ncould not convert string 'value' to an Int"
floatAttr : String -> Decoder Basics.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 "Node: <root attr=\"value\"></root>\ncould not convert string 'value' to a Float"
boolAttr : String -> Decoder Basics.Bool
Decodes an attribute value into Bool
.
run (boolAttr "attr") "<root attr='true'></root>"
--> Ok True
run (boolAttr "attr") "<root attr='value'></root>"
--> Err "Node: <root attr=\"value\"></root>\nNot a valid boolean value."
single : Decoder a -> ListDecoder a
Composes ListDecoder
that results in a singular value.
It fails if:
Examples:
run (path [ "tag" ] (single string)) "<root><tag>string</tag></root>"
--> Ok "string"
run (path [ "tag" ] (single string)) "<root></root>"
--> Err "Path: /tag\nNode: <root></root>\nNode not found."
run (path [ "tag" ] (single string)) "<root><tag>string1</tag><tag>string2</tag></root>"
--> Err "Path: /tag\nNode: <root><tag>string1</tag><tag>string2</tag></root>\nMultiple nodes found."
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 "Path: /tag\nNode: <tag>nonInt</tag>\ncould not convert string 'nonInt' to an Int"
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 ]
index : Basics.Int -> Decoder a -> ListDecoder a
Similar to Json.Decode.index
, decode from list of nodes at some index.
Useful for "narrowing down" your search paths.
Fails if there are not enough nodes.
run (path [ "tag" ] (index 1 int)) "<root><tag>0</tag><tag>1</tag></root>"
--> Ok 1
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.
oneOf : List (Decoder a) -> Decoder a
Try a list of decoders.
Fails if all given decoders failed, or no decoders are given.
run (oneOf [ int, succeed 0 ]) "<root>nonInt</root>"
--> Ok 0
run (oneOf [ int ]) "<root>nonInt</root>"
--> Err "All decoders failed:\n 1) Node: <root>nonInt</root>\n could not convert string 'nonInt' to an Int"
andThen : (a -> Decoder b) -> Decoder a -> Decoder b
Generates a decoder that depends on previous value.
with : Decoder a -> (a -> Decoder b) -> Decoder b
Flipped andThen
.
Why do we want this? It allows us to write decoders in Cotinuation-Passing Style (CPS)!
run
(
with (path ["string"] (single string)) <| \s ->
with (path ["int"] (single int)) <| \i ->
succeed ( s, i )
)
"<root><string>string</string><int>1</int></root>"
--> Ok ("string", 1)
This style is being discussed in Elm community, and sometimes it makes sense to adopt one, especially when you are trying to decode complex data.
https://discourse.elm-lang.org/t/experimental-json-decoding-api/2121
mapN
family, so you can easily add/remove fields (similar pros to pipeline style)<type>
tagThis style is not yet formatted nicely by elm-format
as of writing this,
but the proposal is already filed!
https://github.com/avh4/elm-format/issues/568
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" )
map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value
map5 : (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))
)
"<root><string>string</string><int>1</int></root>"
--> Ok ("string", 1)
withDefault : a -> Decoder a -> Decoder a
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)
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.Node
s.
Then they must be decoded using special decoder parts: ListDecoder
.
In the above example, single
and list
are ListDecoder
generators.
It collects ALL nodes matching the path in breadth-first manner.
If you need to collect nodes only from a specific intermediate node that fulfills some conditions,
(e.g. path [ "a", "b" ]
, but only in 3rd "<a>...</a>"
)
you need to split the path and explicitly define a Decoder that narrows down the path.
Be careful not to accidentally include nodes from not-targeted ancestors.
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 "Path: /tag/nested\nNode: <nested>string1</nested>\ncould not convert string 'string1' to an Int"
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 optionalPath
.
errorToString : Error -> String
Convert Error
to a formatted string.