XmerlC14n
canonicalizes XML according to the Exclusive XML Canonicalization
specification version 1.0, for use in
XML signatures.
It is a port to Elixir from the xmerl_c14n
Erlang module found in the esaml
project.
Documentation is located at https://hexdocs.pm/xmerl_c14n
XmerlC14n
can be installed by adding xmerl_c14n
to your list of
dependencies in mix.exs
:
def deps do
[
{:xmerl_c14n, "~> 0.1.0"}
]
end
XmerlC14n
operates on xmerl
Erlang records, typically seen as tuples. These
can be parsed directly from the included Erlang modules in Elixir, using
:xmerl_scan.string
, or with a nice wrapper, like
SweetXml.
iex> xml = ~S{<!DOCTYPE doc [<!ATTLIST e9 attr CDATA "default">]>
<doc>
<e1 />
<e2 ></e2>
<e3 name = "elem3" id="elem3" />
<e4 name="elem4" id="elem4" ></e4>
<e5 a:attr="out" b:attr="sorted" attr2="all" attr="I'm"
xmlns:b="http://www.ietf.org"
xmlns:a="http://www.w3.org"
xmlns="http://example.org"/>
<e6 xmlns="" xmlns:a="http://www.w3.org">
<e7 xmlns="http://www.ietf.org">
<e8 xmlns="" xmlns:a="http://www.w3.org">
<e9 xmlns="" xmlns:a="http://www.ietf.org"/>
</e8>
</e7>
</e6>
</doc>
}
iex> {xml_tuples, _} = xml |> to_charlist |> :xmerl_scan.string(namespace_conformant: true, document: true)
{:xmlDocument,
[
{:xmlElement, :doc, :doc, [], {:xmlNamespace, [], []}, [], 1, [],
[
{:xmlText, [doc: 1], 1, [], '\n ', :text},
{:xmlElement, :e1, :e1, [], {:xmlNamespace, [], []}, [doc: 1], 2, [], [],
[], '', :undeclared},
{:xmlText, [doc: 1], 3, [], '\n ', :text},
{:xmlElement, :e2, :e2, [], {:xmlNamespace, [], []}, [doc: 1], 4, [], [],
[], :undefined, :undeclared},
{:xmlText, [doc: 1], 5, [], '\n ', :text},
{:xmlElement, :e3, :e3, [], {:xmlNamespace, [], []}, [doc: 1], 6,
[
{:xmlAttribute, :name, :name, [], {:xmlNamespace, [], []},
[e3: 6, doc: 1], 1, [], 'elem3', false},
{:xmlAttribute, :id, :id, [], {:xmlNamespace, [], []}, [e3: 6, doc: 1],
2, [], 'elem3', false}
], [], [], :undefined, :undeclared},
{:xmlText, [doc: 1], 7, [], '\n ', :text},
{:xmlElement, :e4, :e4, [], {:xmlNamespace, [], []}, [doc: 1], 8,
[
{:xmlAttribute, :name, :name, [], {:xmlNamespace, [], []},
[e4: 8, doc: 1], 1, [], 'elem4', false},
{:xmlAttribute, :id, :id, [], {:xmlNamespace, [], []}, [e4: 8, doc: 1],
2, [], 'elem4', false}
], [], [], :undefined, :undeclared},
{:xmlText, [doc: 1], 9, [], '\n ', :text},
{:xmlElement, :e5, {:"http://example.org", :e5}, [],
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]}, [doc: 1],
10,
[
{:xmlAttribute, :"a:attr", {:"http://www.w3.org", :attr},
{'a', 'attr'},
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 1, [], 'out', false},
{:xmlAttribute, :"b:attr", {:"http://www.ietf.org", :attr},
{'b', 'attr'},
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 2, [], 'sorted', false},
{:xmlAttribute, :attr2, :attr2, [],
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 3, [], 'all', false},
{:xmlAttribute, :attr, :attr, [],
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 4, [], 'I\'m', false},
{:xmlAttribute, :"xmlns:b", {'xmlns', 'b'}, {'xmlns', 'b'},
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 5, [], 'http://www.ietf.org', false},
{:xmlAttribute, :"xmlns:a", {'xmlns', 'a'}, {'xmlns', 'a'},
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 6, [], 'http://www.w3.org', false},
{:xmlAttribute, :xmlns, :xmlns, [],
{:xmlNamespace, :"http://example.org",
[{'b', :"http://www.ietf.org"}, {'a', :"http://www.w3.org"}]},
[e5: 10, doc: 1], 7, [], 'http://example.org', false}
], [], [], :undefined, :undeclared},
{:xmlText, [doc: 1], 11, [], '\n ', :text},
{:xmlElement, :e6, {:"", :e6}, [],
{:xmlNamespace, :"", [{'a', :"http://www.w3.org"}]}, [doc: 1], 12,
[
{:xmlAttribute, :xmlns, :xmlns, [],
{:xmlNamespace, :"", [{'a', :"http://www.w3.org"}]}, [e6: 12, doc: 1],
1, [], [], false},
{:xmlAttribute, :"xmlns:a", {'xmlns', 'a'}, {'xmlns', 'a'},
{:xmlNamespace, :"", [{'a', :"http://www.w3.org"}]}, [e6: 12, doc: 1],
2, [], 'http://www.w3.org', false}
],
[
{:xmlText, [e6: 12, doc: 1], 1, [], '\n ', :text},
{:xmlElement, :e7, {:"http://www.ietf.org", :e7}, [],
{:xmlNamespace, :"http://www.ietf.org", [{'a', :"http://www.w3.org"}]},
[e6: 12, doc: 1], 2,
[
{:xmlAttribute, :xmlns, :xmlns, [], {:xmlNamespace, ...}, [...],
...}
],
[
{:xmlText, [e7: 2, e6: 12, doc: 1], 1, [], '\n ', ...},
{:xmlElement, :e8, {:"", ...}, [], ...},
{:xmlText, [e7: 2, ...], 3, ...}
], [], :undefined, :undeclared},
{:xmlText, [e6: 12, doc: 1], 3, [], '\n ', :text}
], [], :undefined, :undeclared},
{:xmlText, [doc: 1], 13, [], '\n', :text}
], [], '', :undeclared}
]}
iex> xml_tuples |> XmerlC14n.canonicalize!() |> IO.puts()
<doc>
<e1></e1>
<e2></e2>
<e3 id="elem3" name="elem3"></e3>
<e4 id="elem4" name="elem4"></e4>
<e5 xmlns="http://example.org" xmlns:a="http://www.w3.org" xmlns:b="http://www.ietf.org" attr="I'm" attr2="all" b:attr="sorted" a:attr="out"></e5>
<e6>
<e7 xmlns="http://www.ietf.org">
<e8 xmlns="">
<e9></e9>
</e8>
</e7>
</e6>
</doc>
If using SweetXml, you neither have to convert your XML to a charlist, nor destructure it from a tuple.
iex> xml |> SweetXml.parse(namespace_conformant: true, document: true) |> XmerlC14n.canonicalize!() |> IO.puts()
<doc>
<e1></e1>
<e2></e2>
<e3 id="elem3" name="elem3"></e3>
<e4 id="elem4" name="elem4"></e4>
<e5 xmlns="http://example.org" xmlns:a="http://www.w3.org" xmlns:b="http://www.ietf.org" attr="I'm" attr2="all" b:attr="sorted" a:attr="out"></e5>
<e6>
<e7 xmlns="http://www.ietf.org">
<e8 xmlns="">
<e9></e9>
</e8>
</e7>
</e6>
</doc>
For more examples see https://hexdocs.pm/xmerl_c14n/XmerlC14n.html#canonicalize/1-examples