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 ] )Represents error on decode execution.
Synonym of decodeString.
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.
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 ] )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 "string" --> Ok "string"
run string ""
--> Ok ""
run string "string"
--> Err "Node: string\nThe node is not a simple text node."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"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"Decodes to Bool.
In Xml Schema Definition (XSD), valid lexical representation of boolean values are,
We follow this specification, case-sensitively.
run bool "true" --> Ok True
run bool "value"
--> Err "Node: value\nNot a valid boolean value."Decodes an XmlParser.Node into itself.
See [XmlParser][xp] 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.")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."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"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"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."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."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"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 ]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 1mapN series are backed by Result.mapN series, thus it only supports up to map5.
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"Generates a decoder that depends on previous value.
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!
Transform a decoder.
run (map String.length string) "<root>string</root>"
--> Ok 6Generates 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" )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 0Generates 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 NothingGenerates 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)
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.
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.
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 ] )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"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 NothingIf you want to apply default value when the node is missing, use optionalPath.
Convert Error to a formatted string.
A function that knows how to decode an XML node into Elm value.