├── .formatter.exs
├── .gitignore
├── LICENSE
├── README.md
├── lib
├── elixir_xml_to_map.ex
└── xml_to_map
│ ├── naive_map.ex
│ └── nested_map.ex
├── mix.exs
├── mix.lock
└── test
├── elixir_xml_to_map_test.exs
└── test_helper.exs
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc
12 |
13 | # If the VM crashes, it generates a dump, let's ignore it too.
14 | erl_crash.dump
15 |
16 | # Also ignore archive artifacts (built via "mix archive.build").
17 | *.ez
18 | .elixir_ls
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XmlToMap
2 |
3 | [](https://hex.pm/packages/elixir_xml_to_map)
4 | [](https://hexdocs.pm/elixir_xml_to_map/)
5 | [](https://hex.pm/packages/elixir_xml_to_map)
6 | [](https://github.com/homanchou/elixir_xml_to_map/blob/master/LICENSE)
7 | [](https://github.com/homanchou/elixir-xml-to-map/commits/master)
8 |
9 | This library provides two functions to create an Elixir Map data structure from an XML string.
10 |
11 | ### `XmlToMap.naive_map/2`
12 |
13 | Usage:
14 |
15 | ```elixir
16 | XmlToMap.naive_map("123")
17 | ```
18 |
19 | Results in:
20 |
21 | ```elixir
22 | %{"foo" => %{"bar" => "123"}}
23 | ```
24 |
25 | Converts XML string to an Elixir map with strings for keys, not atoms, since atoms are not garbage collected.
26 |
27 | This tool is inspired by Rails `Hash.from_xml()`.
28 |
29 | ----
30 |
31 | I call the function "naive", because there are known short comings and there is some [controversy](https://stackoverflow.com/questions/40650482/how-to-convert-xml-to-a-map-in-elixir) around using a conversion tool like this since XML and Maps are non-isomorphic and there is no standard way to convert all the information from one format to another. The recommended way to pull specific well structured information from XML is to use something like xpath. But if you understand the risks and still prefer to convert the whole XML string to a map then this tool is for you!
32 |
33 | It is currently not able to parse XML namespace. It also can't determine if a child should be a list unless it sees a repeated child. If and only if nodes are repeated at the same level will they become a list.
34 |
35 | ```elixir
36 | # there are two points inside foo, so the value of "point" becomes a list. Had "foo" only contained one point then there would be no list but instead one nested map
37 | XmlToMap.naive_map("1529")
38 |
39 | # => %{"foo" => %{"point" => [%{"x" => "1", "y" => "5"}, %{"x" => "2", "y" => "9"}]}}
40 | ```
41 |
42 | Previously this package did not handle XML node attributes.
43 | The current version takes inspiration from a [go goxml2json package](https://github.com/basgys/goxml2json) and exports attributes in the map while prepending "-" so you know they are attributes.
44 |
45 | Whenever we encounter an XML node with BOTH attributes and children, we wrap "#content" around the node's inner value.
46 |
47 | For example this snippet has a `Height` leaf node with attribute `Units` and a value of `0.50`:
48 |
49 | ```xml
50 |
51 | 0.50
52 |
53 | ```
54 |
55 | This would become this snippet:
56 |
57 | ```json
58 | ...
59 | "ItemDimensions": {
60 | "Height": {
61 | "#content": "0.50",
62 | "-Units": "inches"
63 | }
64 | }
65 | ```
66 |
67 | Empty tags will have a value of nil.
68 |
69 | ### `XmlToMap.nested_map/2`
70 |
71 | This function produces arguably more verbose and less straightforward result, but it preserves
72 | the order of sequences and might be then instantiated back to _XML_ in an isomorphic way.
73 |
74 | In general, it reflects the tree structure of an input _XML_ per se.
75 |
76 | Usage:
77 |
78 | ```elixir
79 | XmlToMap.nested_map("123")
80 | ```
81 |
82 | Results in:
83 |
84 | ```elixir
85 | %{
86 | attributes: [],
87 | name: "foo",
88 | content: %{attributes: [], name: "bar", content: "123"}
89 | }
90 | ```
91 |
92 | The function does not make any assumptions about the content and recursively builds the result.
93 |
94 | ```xml
95 |
96 | 0.50
97 |
98 | ```
99 |
100 | This would become this snippet:
101 |
102 | ```elixir
103 | %{
104 | attributes: [],
105 | name: "ItemDimensions",
106 | content: %{attributes: [{"Units", "inches"}], name: "Height", content: "0.50"}
107 | }
108 | ```
109 |
110 | Which might then be converted to _JSON_ of the same shape.
111 |
112 | To make it less verbose, pass `purge_empty: true` as the second parameter to `XmlToMap.nested_map/2`:
113 |
114 | ```elixir
115 | XmlToMap.nested_map("123", purge_empty: true)
116 | ```
117 |
118 | Results in:
119 |
120 | ```elixir
121 | %{
122 | name: "foo",
123 | content: [
124 | %{attributes: [{"arg", "yes"}], name: "bar", content: "123"},
125 | %{name: "baz"}
126 | ]
127 | }
128 | ```
129 |
130 | -----
131 |
132 | There is a dependency on Erlsom to parse XML then converts the 'simple_form' structure into a map.
133 |
134 | I prefer Erlsom because it is the best documented erlang XML parser and because it mentions that it does not produce new atoms during the scanning.
135 |
136 | See tests for some example usage.
137 |
138 | ----
139 |
140 | ## Installation
141 |
142 | The package can be installed as:
143 |
144 | 1. Add `:elixir_xml_to_map` to your list of dependencies in `mix.exs`:
145 |
146 | ```elixir
147 | def deps do
148 | [{:elixir_xml_to_map, "~> 2.0"}]
149 | end
150 | ```
151 |
152 | 2. Ensure `:elixir_xml_to_map` is started before your application:
153 |
154 | ```elixir
155 | def application do
156 | [extra_applications: [:elixir_xml_to_map]]
157 | end
158 | ```
159 |
160 |
161 | ## License
162 |
163 | Copyright (c) 2016-present, Homan Chou
164 |
165 | Licensed under the Apache License, Version 2.0 (the "License");
166 | you may not use this file except in compliance with the License.
167 | You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
168 |
169 | Unless required by applicable law or agreed to in writing, software
170 | distributed under the License is distributed on an "AS IS" BASIS,
171 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
172 | See the License for the specific language governing permissions and
173 | limitations under the License.
174 |
--------------------------------------------------------------------------------
/lib/elixir_xml_to_map.ex:
--------------------------------------------------------------------------------
1 | defmodule XmlToMap do
2 | @moduledoc """
3 | Simple convenience module for getting a map out of an XML string.
4 | """
5 |
6 | alias XmlToMap.{NaiveMap, NestedMap}
7 |
8 | @doc """
9 | `naive_map/1` utility is inspired by `Rails Hash.from_xml()` but is
10 | "naive" in that it is convenient (requires no setup) but carries the same
11 | drawbacks.
12 |
13 | For example no validation over what should be a collection. If and only if
14 | nodes are repeated at the same level will they become a list. If a node has
15 | attributes we'll prepend a "-" in front of them and merge them into the map
16 | and take the node value and nest that inside "#content" key.
17 | """
18 | @defaults %{namespace_match_fn: &__MODULE__.default_namespace_match_fn/0}
19 |
20 | def naive_map(xml, opts \\ []) do
21 | params = Enum.into(opts, @defaults)
22 | tree = get_generic_data_structure(xml, params)
23 | NaiveMap.parse(tree)
24 | end
25 |
26 | def nested_map(xml, opts \\ []) do
27 | params = Enum.into(opts, @defaults)
28 | {purge_empty, params} = Map.pop(params, :purge_empty, false)
29 | tree = get_generic_data_structure(xml, params)
30 | NestedMap.parse(tree, purge_empty)
31 | end
32 |
33 | # Default function to handle namespace in xml
34 | # This fuction invokes an anonymous function that has this parameters: Name, Namespace, Prefix.
35 | # Usually, the namespace appears at the top of xml file, e.g `xmlns:g="http://base.google.com/ns/1.0"`,
36 | # where `g` is the Prefix and `"http://base.google.com/ns/1.0"` the Namespace,
37 | # the Name is the key tag in xml.
38 | # It should return a term. It is called for each tag and attribute name.
39 | # The result will be used in the output.
40 | # When a namespace and prefix is found, it'll return a term like "#{prefix}:#{name}",
41 | # otherwise it will return only the name.
42 | # The default behavior in :erlsom.simple_form/2 is Name if Namespace == undefined, and a string {Namespace}Name otherwise.
43 | def default_namespace_match_fn do
44 | fn name, namespace, prefix ->
45 | cond do
46 | namespace != [] && prefix != [] -> "#{prefix}:#{name}"
47 | true -> name
48 | end
49 | end
50 | end
51 |
52 | defp get_generic_data_structure(xml, %{namespace_match_fn: namespace_match_fn}) do
53 | {:ok, element, _tail} = :erlsom.simple_form(xml, [{:nameFun, namespace_match_fn.()}])
54 | element
55 | end
56 |
57 | # erlsom simple_form returns a kind of tree:
58 | # Result = {ok, Element, Tail},
59 | # where Element = {Tag, Attributes, Content},
60 | # Tag is a string
61 | # Attributes = [{AttributeName, Value}],
62 | # Content is a list of Elements and/or strings.
63 | defp get_generic_data_structure(xml, _opts) do
64 | {:ok, element, _tail} = :erlsom.simple_form(xml)
65 | element
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/lib/xml_to_map/naive_map.ex:
--------------------------------------------------------------------------------
1 | defmodule XmlToMap.NaiveMap do
2 | @moduledoc """
3 | Module to recursively traverse the output of `erlsom.simple_form/1`
4 | and produce a map.
5 |
6 | `erlsom` uses character lists so this library converts them to
7 | Elixir binary string.
8 |
9 | Attributes, if present, are defined as `tag@attribute_name => attribute_value`.
10 | """
11 |
12 | # if a single list element of a tuple, ignore the list detail and parse it
13 | def parse([{tag, attributes, content}]) do
14 | parse({tag, attributes, content})
15 | end
16 |
17 | # for any other singular value, it's probably meant to be a string
18 | def parse([value]) do
19 | to_string(value) |> String.trim()
20 | end
21 |
22 | # a parse with a tuples is probably the entry point we'll hit first
23 | # if this node has no attributes, our work is easier
24 | def parse({tag, [], content}) do
25 | parsed_content = parse(content)
26 | %{to_string(tag) => parsed_content}
27 | end
28 |
29 | # this is probably the entry point we'll hit first
30 | # Element = {Tag, Attributes, Content},
31 | # Tag is a string
32 | # Attributes = [{AttributeName, Value}],
33 | # Content is a list of Elements and/or strings.
34 | # in this case attributes is not empty
35 | def parse({tag, attributes, content}) do
36 | attributes_map =
37 | Enum.reduce(attributes, %{}, fn {attribute_name, attribute_value}, acc ->
38 | Map.put(acc, "-#{attribute_name}", to_string(attribute_value))
39 | end)
40 |
41 | parsed_content = parse(content)
42 | joined_content = %{"#content" => parsed_content} |> Map.merge(attributes_map)
43 |
44 | %{to_string(tag) => joined_content}
45 | end
46 |
47 | def parse(list) when is_list(list) do
48 | parsed_list = Enum.map(list, &{to_string(elem(&1, 0)), parse(&1)})
49 |
50 | content =
51 | Enum.reduce(parsed_list, %{}, fn {k, v}, acc ->
52 | case Map.get(acc, k) do
53 | nil ->
54 | for({key, value} <- v, into: %{}, do: {key, value})
55 | |> Map.merge(acc)
56 |
57 | [h | t] ->
58 | Map.put(acc, k, [h | t] ++ [v[k]])
59 |
60 | prev ->
61 | Map.put(acc, k, [prev] ++ [v[k]])
62 | end
63 | end)
64 |
65 | case content == %{} do
66 | true -> nil
67 | _ -> content
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/lib/xml_to_map/nested_map.ex:
--------------------------------------------------------------------------------
1 | defmodule XmlToMap.NestedMap do
2 | @moduledoc """
3 | Module to recursively traverse the output of `erlsom.simple_form/1`
4 | and produce a nested map with `name`, `attributes`, and `content` fields.
5 |
6 | `erlsom` uses character lists so this library converts them to
7 | Elixir binary string.
8 | """
9 |
10 | @moduledoc since: "3.1.0"
11 |
12 | @typedoc "Type of a nested map"
13 | @type element :: %{
14 | name: String.t(),
15 | attributes: map() | [{charlist() | String.t(), charlist() | String.t()}],
16 | content: [element()]
17 | }
18 |
19 | # if a single list element of a tuple, ignore the list detail and parse it
20 | def parse([{tag, attributes, content}], purge_empty) do
21 | parse({tag, attributes, content}, purge_empty)
22 | end
23 |
24 | # for any other singular value, it's probably meant to be a string
25 | def parse([value], _purge_empty) do
26 | value |> to_string() |> String.trim()
27 | end
28 |
29 | # a parse with a tuples is probably the entry point we'll hit first
30 | # if this node has no attributes, our work is easier
31 | def parse({tag, [], []}, true) do
32 | %{name: to_string(tag)}
33 | end
34 |
35 | def parse({tag, [], content}, true) do
36 | %{name: to_string(tag), content: parse(content, true)}
37 | end
38 |
39 | def parse({tag, attributes, []}, true) do
40 | %{name: to_string(tag), attributes: fix_attributes(attributes)}
41 | end
42 |
43 | def parse({tag, attributes, content}, purge_empty) do
44 | %{
45 | name: to_string(tag),
46 | attributes: fix_attributes(attributes),
47 | content: parse(content, purge_empty)
48 | }
49 | end
50 |
51 | def parse([], _purge_empty), do: nil
52 |
53 | def parse(list, purge_empty) when is_list(list), do: Enum.map(list, &parse(&1, purge_empty))
54 |
55 | defp fix_attributes(attributes) do
56 | for {k, v} <- attributes do
57 | k = if is_list(k) and List.ascii_printable?(k), do: to_string(k), else: k
58 | v = if is_list(v) and List.ascii_printable?(v), do: to_string(v), else: v
59 | {k, v}
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule XmlToMap.Mixfile do
2 | use Mix.Project
3 |
4 | @source_url "https://github.com/homanchou/elixir-xml-to-map"
5 |
6 | def project do
7 | [
8 | app: :elixir_xml_to_map,
9 | version: "3.1.0",
10 | elixir: "~> 1.9",
11 | build_embedded: Mix.env() == :prod,
12 | start_permanent: Mix.env() == :prod,
13 | description: "A module for converting an XML string to a map",
14 | package: package(),
15 | deps: deps(),
16 | docs: docs()
17 | ]
18 | end
19 |
20 | def package do
21 | [
22 | maintainers: ["Homan Chou"],
23 | licenses: ["Apache-2.0"],
24 | links: %{"GitHub" => @source_url}
25 | ]
26 | end
27 |
28 | def application do
29 | [extra_applications: [:logger]]
30 | end
31 |
32 | defp deps do
33 | [
34 | {:erlsom, "~> 1.4"},
35 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}
36 | ]
37 | end
38 |
39 | defp docs do
40 | [
41 | main: "readme",
42 | source_url: @source_url,
43 | extras: ["README.md"]
44 | ]
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
3 | "erlsom": {:hex, :erlsom, "1.4.1", "53dbacf35adfea6f0714fd0e4a7b0720d495e88c5e24e12c5dc88c7b62bd3e49", [:rebar3], [], "hexpm", "57b777fe2522e342badfa35873b2266c6961e3a9f4d2ac195d761985c40c3247"},
4 | "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
5 | "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
6 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
7 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
8 | }
9 |
--------------------------------------------------------------------------------
/test/elixir_xml_to_map_test.exs:
--------------------------------------------------------------------------------
1 | defmodule XmlToMapTest do
2 | use ExUnit.Case
3 | doctest XmlToMap
4 |
5 | test "make a map (naive)" do
6 | assert XmlToMap.naive_map(sample_xml()) == expectation_naive()
7 | end
8 |
9 | test "combines sibling nodes with the same name into a list (naive)" do
10 | assert XmlToMap.naive_map(amazon_xml()) == amazon_expected_naive()
11 | end
12 |
13 | test "make a map (nested)" do
14 | assert XmlToMap.nested_map(sample_xml()) == expectation_nested()
15 | end
16 |
17 | test "combines sibling nodes with the same name into a list (nested)" do
18 | assert XmlToMap.nested_map(amazon_xml()) == amazon_expected_nested()
19 | end
20 |
21 | test "empty tag => nil" do
22 | xml = """
23 |
24 | 1
25 | Value
26 |
27 |
28 |
29 | """
30 |
31 | assert XmlToMap.naive_map(xml) == %{
32 | "xml" => %{
33 | "id" => "1",
34 | "name" => "Value",
35 | "empty" => nil,
36 | "emptyWithAttrs" => %{
37 | "#content" => nil,
38 | "-id" => "123"
39 | }
40 | }
41 | }
42 |
43 | assert XmlToMap.nested_map(xml) == %{
44 | attributes: [],
45 | content: [
46 | %{attributes: [], name: "id", content: "1"},
47 | %{attributes: [], name: "name", content: "Value"},
48 | %{attributes: [], name: "empty", content: nil},
49 | %{attributes: [{"id", "123"}], name: "emptyWithAttrs", content: nil}
50 | ],
51 | name: "xml"
52 | }
53 |
54 | assert XmlToMap.nested_map(xml, purge_empty: true) == %{
55 | content: [
56 | %{name: "id", content: "1"},
57 | %{name: "name", content: "Value"},
58 | %{name: "empty"},
59 | %{attributes: [{"id", "123"}], name: "emptyWithAttrs"}
60 | ],
61 | name: "xml"
62 | }
63 | end
64 |
65 | test "support for custom namespace_match function" do
66 | assert XmlToMap.naive_map(facebook_xmlns_xml(),
67 | namespace_match_fn: &set_prefix_namespace_fn/0
68 | ) == facebook_custom_function_expected()
69 | end
70 |
71 | test "support for xmlns xml file" do
72 | assert XmlToMap.naive_map(facebook_xmlns_xml()) == facebook_xmlns_xml_expected()
73 | end
74 |
75 | def expectation_naive do
76 | %{
77 | "orders" => %{
78 | "order" => [
79 | %{
80 | "billing_address" => "My address",
81 | "id" => "123",
82 | "items" => %{
83 | "item" => [
84 | %{
85 | "-currency" => "USD",
86 | "#content" => %{
87 | "description" => "Hat",
88 | "price" => "5.99",
89 | "quantity" => "1",
90 | "sku" => %{"-lang" => "en", "#content" => "ABC"}
91 | }
92 | },
93 | %{
94 | "-currency" => "USD",
95 | "#content" => %{
96 | "description" => "Bat",
97 | "price" => "9.99",
98 | "quantity" => "2",
99 | "sku" => "ABC"
100 | }
101 | }
102 | ]
103 | }
104 | },
105 | %{
106 | "billing_address" => "Uncle's House",
107 | "id" => "124",
108 | "items" => %{
109 | "item" => %{
110 | "-currency" => "USD",
111 | "#content" => %{
112 | "description" => "Hat",
113 | "price" => "5.99",
114 | "quantity" => "2",
115 | "sku" => "ABC"
116 | }
117 | }
118 | }
119 | }
120 | ]
121 | }
122 | }
123 | end
124 |
125 | def expectation_nested do
126 | %{
127 | name: "orders",
128 | attributes: [],
129 | content: [
130 | %{
131 | name: "order",
132 | attributes: [],
133 | content: [
134 | %{attributes: [], name: "id", content: "123"},
135 | %{attributes: [], name: "billing_address", content: "My address"},
136 | %{
137 | attributes: [],
138 | name: "items",
139 | content: [
140 | %{
141 | attributes: [{"currency", "USD"}],
142 | name: "item",
143 | content: [
144 | %{attributes: [{"lang", "en"}], name: "sku", content: "ABC"},
145 | %{attributes: [], name: "description", content: "Hat"},
146 | %{attributes: [], name: "price", content: "5.99"},
147 | %{attributes: [], name: "quantity", content: "1"}
148 | ]
149 | },
150 | %{
151 | attributes: [{"currency", "USD"}],
152 | name: "item",
153 | content: [
154 | %{attributes: [], name: "sku", content: "ABC"},
155 | %{attributes: [], name: "description", content: "Bat"},
156 | %{attributes: [], name: "price", content: "9.99"},
157 | %{attributes: [], name: "quantity", content: "2"}
158 | ]
159 | }
160 | ]
161 | }
162 | ]
163 | },
164 | %{
165 | attributes: [],
166 | name: "order",
167 | content: [
168 | %{attributes: [], name: "id", content: "124"},
169 | %{attributes: [], name: "billing_address", content: "Uncle's House"},
170 | %{
171 | attributes: [],
172 | name: "items",
173 | content: %{
174 | attributes: [{"currency", "USD"}],
175 | name: "item",
176 | content: [
177 | %{attributes: [], name: "sku", content: "ABC"},
178 | %{attributes: [], name: "description", content: "Hat"},
179 | %{attributes: [], name: "price", content: "5.99"},
180 | %{attributes: [], name: "quantity", content: "2"}
181 | ]
182 | }
183 | }
184 | ]
185 | }
186 | ]
187 | }
188 | end
189 |
190 | def sample_xml do
191 | """
192 |
193 |
194 | 123
195 | My address
196 |
197 | -
198 | ABC
199 | Hat
200 | 5.99
201 |
202 | 1
203 |
204 |
205 | -
206 | ABC
207 | Bat
208 | 9.99
209 |
210 | 2
211 |
212 |
213 |
214 |
215 |
216 | 124
217 | Uncle's House
218 |
219 | -
220 | ABC
221 | Hat
222 | 5.99
223 | 2
224 |
225 |
226 |
227 |
228 | """
229 | end
230 |
231 | def amazon_expected_naive do
232 | %{
233 | "GetReportRequestListResponse" => %{
234 | "GetReportRequestListResult" => %{
235 | "HasNext" => "true",
236 | "NextToken" =>
237 | "bnKUjUwrpfD2jpZedg0wbVuY6vtoszFEs90MCUIyGQ/PkNXwVrATLSf6YzH8PQiWICyhlLgHd4gqVtOYt5i3YX/y5ZICxITwrMWltwHPross7S2LHmNKmcpVErfopfm7ZgI5YM+bbLFRPCnQrq7eGPqiUs2SoKaRPxuuVZAjoAG5Hd34Twm1igafEPREmauvQPEfQK/OReJ9wNJ/XIY3rAvjRfjTJJa5YKoSylcR8gttj983g7esDr0wZ3V0GwaZstMPcqxOnL//uIo+owquzirF36SWlaJ9J5zSS6le1iIsxqkIMXCWKNSOyeZZ1ics+UXSqjS0c15jmJnjJN2V5uMEDoXRsC9PFEVVZ6joTY2uGFVSjAf2NsFIcEAdr4xQz2Y051TPxxk=",
238 | "ReportRequestInfo" => [
239 | %{
240 | "CompletedDate" => "2016-11-18T20:53:14+00:00",
241 | "EndDate" => "2016-11-18T20:53:00+00:00",
242 | "GeneratedReportId" => "3412841972017123",
243 | "ReportProcessingStatus" => "_DONE_",
244 | "ReportRequestId" => "50920017123",
245 | "ReportType" => "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_",
246 | "Scheduled" => "false",
247 | "StartDate" => "2016-11-18T20:53:00+00:00",
248 | "StartedProcessingDate" => "2016-11-18T20:53:07+00:00",
249 | "SubmittedDate" => "2016-11-18T20:53:00+00:00"
250 | },
251 | %{
252 | "CompletedDate" => "2016-11-18T20:51:57+00:00",
253 | "EndDate" => "2016-11-18T20:51:44+00:00",
254 | "GeneratedReportId" => "3414908503017123",
255 | "ReportProcessingStatus" => "_DONE_",
256 | "ReportRequestId" => "50919017123",
257 | "ReportType" => "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_",
258 | "Scheduled" => "false",
259 | "StartDate" => "2016-11-18T20:51:44+00:00",
260 | "StartedProcessingDate" => "2016-11-18T20:51:49+00:00",
261 | "SubmittedDate" => "2016-11-18T20:51:44+00:00"
262 | },
263 | %{
264 | "CompletedDate" => "2016-11-18T14:54:24+00:00",
265 | "EndDate" => "2016-11-18T14:54:10+00:00",
266 | "GeneratedReportId" => "3410642176017123",
267 | "ReportProcessingStatus" => "_DONE_",
268 | "ReportRequestId" => "50918017123",
269 | "ReportType" => "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_",
270 | "Scheduled" => "false",
271 | "StartDate" => "2016-11-18T14:54:10+00:00",
272 | "StartedProcessingDate" => "2016-11-18T14:54:17+00:00",
273 | "SubmittedDate" => "2016-11-18T14:54:10+00:00"
274 | },
275 | %{
276 | "CompletedDate" => "2016-11-18T14:52:37+00:00",
277 | "EndDate" => "2016-11-18T14:52:26+00:00",
278 | "GeneratedReportId" => "3417419172017123",
279 | "ReportProcessingStatus" => "_DONE_",
280 | "ReportRequestId" => "50917017123",
281 | "ReportType" => "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_",
282 | "Scheduled" => "false",
283 | "StartDate" => "2016-11-18T14:52:26+00:00",
284 | "StartedProcessingDate" => "2016-11-18T14:52:32+00:00",
285 | "SubmittedDate" => "2016-11-18T14:52:26+00:00"
286 | },
287 | %{
288 | "CompletedDate" => "2016-11-18T08:54:01+00:00",
289 | "EndDate" => "2016-11-18T08:53:49+00:00",
290 | "GeneratedReportId" => "3408643280017123",
291 | "ReportProcessingStatus" => "_DONE_",
292 | "ReportRequestId" => "50916017123",
293 | "ReportType" => "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_",
294 | "Scheduled" => "false",
295 | "StartDate" => "2016-11-18T08:53:49+00:00",
296 | "StartedProcessingDate" => "2016-11-18T08:53:54+00:00",
297 | "SubmittedDate" => "2016-11-18T08:53:49+00:00"
298 | },
299 | %{
300 | "CompletedDate" => "2016-11-18T08:51:55+00:00",
301 | "EndDate" => "2016-11-18T08:51:43+00:00",
302 | "GeneratedReportId" => "3410105984017123",
303 | "ReportProcessingStatus" => "_DONE_",
304 | "ReportRequestId" => "50915017123",
305 | "ReportType" => "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_",
306 | "Scheduled" => "false",
307 | "StartDate" => "2016-11-18T08:51:43+00:00",
308 | "StartedProcessingDate" => "2016-11-18T08:51:49+00:00",
309 | "SubmittedDate" => "2016-11-18T08:51:43+00:00"
310 | },
311 | %{
312 | "CompletedDate" => "2016-11-18T02:57:46+00:00",
313 | "EndDate" => "2016-11-18T02:57:34+00:00",
314 | "GeneratedReportId" => "3408556063017123",
315 | "ReportProcessingStatus" => "_DONE_",
316 | "ReportRequestId" => "50914017123",
317 | "ReportType" => "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_",
318 | "Scheduled" => "false",
319 | "StartDate" => "2016-11-18T02:57:34+00:00",
320 | "StartedProcessingDate" => "2016-11-18T02:57:39+00:00",
321 | "SubmittedDate" => "2016-11-18T02:57:34+00:00"
322 | },
323 | %{
324 | "CompletedDate" => "2016-11-18T02:56:12+00:00",
325 | "EndDate" => "2016-11-18T02:55:59+00:00",
326 | "GeneratedReportId" => "3402759511017123",
327 | "ReportProcessingStatus" => "_DONE_",
328 | "ReportRequestId" => "50913017123",
329 | "ReportType" => "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_",
330 | "Scheduled" => "false",
331 | "StartDate" => "2016-11-18T02:55:59+00:00",
332 | "StartedProcessingDate" => "2016-11-18T02:56:05+00:00",
333 | "SubmittedDate" => "2016-11-18T02:55:59+00:00"
334 | },
335 | %{
336 | "CompletedDate" => "2016-11-17T20:56:04+00:00",
337 | "EndDate" => "2016-11-17T20:55:49+00:00",
338 | "GeneratedReportId" => "3399094295017122",
339 | "ReportProcessingStatus" => "_DONE_",
340 | "ReportRequestId" => "50912017122",
341 | "ReportType" => "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_",
342 | "Scheduled" => "false",
343 | "StartDate" => "2016-11-17T20:55:49+00:00",
344 | "StartedProcessingDate" => "2016-11-17T20:55:54+00:00",
345 | "SubmittedDate" => "2016-11-17T20:55:49+00:00"
346 | },
347 | %{
348 | "CompletedDate" => "2016-11-17T20:54:45+00:00",
349 | "EndDate" => "2016-11-17T20:54:33+00:00",
350 | "GeneratedReportId" => "3405850523017122",
351 | "ReportProcessingStatus" => "_DONE_",
352 | "ReportRequestId" => "50911017122",
353 | "ReportType" => "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_",
354 | "Scheduled" => "false",
355 | "StartDate" => "2016-11-17T20:54:33+00:00",
356 | "StartedProcessingDate" => "2016-11-17T20:54:39+00:00",
357 | "SubmittedDate" => "2016-11-17T20:54:33+00:00"
358 | }
359 | ]
360 | },
361 | "ResponseMetadata" => %{"RequestId" => "7509cdb2-0b69-4ca0-89dc-c77f8a747834"}
362 | }
363 | }
364 | end
365 |
366 | def amazon_expected_nested do
367 | %{
368 | attributes: [],
369 | content: [
370 | %{
371 | attributes: [],
372 | name: "GetReportRequestListResult",
373 | content: [
374 | %{attributes: [], name: "HasNext", content: "true"},
375 | %{
376 | attributes: [],
377 | name: "ReportRequestInfo",
378 | content: [
379 | %{
380 | attributes: [],
381 | name: "ReportType",
382 | content: "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_"
383 | },
384 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
385 | %{attributes: [], name: "EndDate", content: "2016-11-18T20:53:00+00:00"},
386 | %{attributes: [], name: "Scheduled", content: "false"},
387 | %{attributes: [], name: "ReportRequestId", content: "50920017123"},
388 | %{
389 | attributes: [],
390 | name: "StartedProcessingDate",
391 | content: "2016-11-18T20:53:07+00:00"
392 | },
393 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T20:53:00+00:00"},
394 | %{attributes: [], name: "StartDate", content: "2016-11-18T20:53:00+00:00"},
395 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T20:53:14+00:00"},
396 | %{attributes: [], name: "GeneratedReportId", content: "3412841972017123"}
397 | ]
398 | },
399 | %{
400 | attributes: [],
401 | name: "ReportRequestInfo",
402 | content: [
403 | %{
404 | attributes: [],
405 | name: "ReportType",
406 | content: "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_"
407 | },
408 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
409 | %{attributes: [], name: "EndDate", content: "2016-11-18T20:51:44+00:00"},
410 | %{attributes: [], name: "Scheduled", content: "false"},
411 | %{attributes: [], name: "ReportRequestId", content: "50919017123"},
412 | %{
413 | attributes: [],
414 | name: "StartedProcessingDate",
415 | content: "2016-11-18T20:51:49+00:00"
416 | },
417 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T20:51:44+00:00"},
418 | %{attributes: [], name: "StartDate", content: "2016-11-18T20:51:44+00:00"},
419 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T20:51:57+00:00"},
420 | %{attributes: [], name: "GeneratedReportId", content: "3414908503017123"}
421 | ]
422 | },
423 | %{
424 | attributes: [],
425 | name: "ReportRequestInfo",
426 | content: [
427 | %{
428 | attributes: [],
429 | name: "ReportType",
430 | content: "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_"
431 | },
432 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
433 | %{attributes: [], name: "EndDate", content: "2016-11-18T14:54:10+00:00"},
434 | %{attributes: [], name: "Scheduled", content: "false"},
435 | %{attributes: [], name: "ReportRequestId", content: "50918017123"},
436 | %{
437 | attributes: [],
438 | name: "StartedProcessingDate",
439 | content: "2016-11-18T14:54:17+00:00"
440 | },
441 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T14:54:10+00:00"},
442 | %{attributes: [], name: "StartDate", content: "2016-11-18T14:54:10+00:00"},
443 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T14:54:24+00:00"},
444 | %{attributes: [], name: "GeneratedReportId", content: "3410642176017123"}
445 | ]
446 | },
447 | %{
448 | attributes: [],
449 | name: "ReportRequestInfo",
450 | content: [
451 | %{
452 | attributes: [],
453 | name: "ReportType",
454 | content: "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_"
455 | },
456 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
457 | %{attributes: [], name: "EndDate", content: "2016-11-18T14:52:26+00:00"},
458 | %{attributes: [], name: "Scheduled", content: "false"},
459 | %{attributes: [], name: "ReportRequestId", content: "50917017123"},
460 | %{
461 | attributes: [],
462 | name: "StartedProcessingDate",
463 | content: "2016-11-18T14:52:32+00:00"
464 | },
465 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T14:52:26+00:00"},
466 | %{attributes: [], name: "StartDate", content: "2016-11-18T14:52:26+00:00"},
467 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T14:52:37+00:00"},
468 | %{attributes: [], name: "GeneratedReportId", content: "3417419172017123"}
469 | ]
470 | },
471 | %{
472 | attributes: [],
473 | name: "ReportRequestInfo",
474 | content: [
475 | %{
476 | attributes: [],
477 | name: "ReportType",
478 | content: "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_"
479 | },
480 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
481 | %{attributes: [], name: "EndDate", content: "2016-11-18T08:53:49+00:00"},
482 | %{attributes: [], name: "Scheduled", content: "false"},
483 | %{attributes: [], name: "ReportRequestId", content: "50916017123"},
484 | %{
485 | attributes: [],
486 | name: "StartedProcessingDate",
487 | content: "2016-11-18T08:53:54+00:00"
488 | },
489 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T08:53:49+00:00"},
490 | %{attributes: [], name: "StartDate", content: "2016-11-18T08:53:49+00:00"},
491 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T08:54:01+00:00"},
492 | %{attributes: [], name: "GeneratedReportId", content: "3408643280017123"}
493 | ]
494 | },
495 | %{
496 | attributes: [],
497 | name: "ReportRequestInfo",
498 | content: [
499 | %{
500 | attributes: [],
501 | name: "ReportType",
502 | content: "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_"
503 | },
504 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
505 | %{attributes: [], name: "EndDate", content: "2016-11-18T08:51:43+00:00"},
506 | %{attributes: [], name: "Scheduled", content: "false"},
507 | %{attributes: [], name: "ReportRequestId", content: "50915017123"},
508 | %{
509 | attributes: [],
510 | name: "StartedProcessingDate",
511 | content: "2016-11-18T08:51:49+00:00"
512 | },
513 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T08:51:43+00:00"},
514 | %{attributes: [], name: "StartDate", content: "2016-11-18T08:51:43+00:00"},
515 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T08:51:55+00:00"},
516 | %{attributes: [], name: "GeneratedReportId", content: "3410105984017123"}
517 | ]
518 | },
519 | %{
520 | attributes: [],
521 | name: "ReportRequestInfo",
522 | content: [
523 | %{
524 | attributes: [],
525 | name: "ReportType",
526 | content: "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_"
527 | },
528 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
529 | %{attributes: [], name: "EndDate", content: "2016-11-18T02:57:34+00:00"},
530 | %{attributes: [], name: "Scheduled", content: "false"},
531 | %{attributes: [], name: "ReportRequestId", content: "50914017123"},
532 | %{
533 | attributes: [],
534 | name: "StartedProcessingDate",
535 | content: "2016-11-18T02:57:39+00:00"
536 | },
537 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T02:57:34+00:00"},
538 | %{attributes: [], name: "StartDate", content: "2016-11-18T02:57:34+00:00"},
539 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T02:57:46+00:00"},
540 | %{attributes: [], name: "GeneratedReportId", content: "3408556063017123"}
541 | ]
542 | },
543 | %{
544 | attributes: [],
545 | name: "ReportRequestInfo",
546 | content: [
547 | %{
548 | attributes: [],
549 | name: "ReportType",
550 | content: "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_"
551 | },
552 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
553 | %{attributes: [], name: "EndDate", content: "2016-11-18T02:55:59+00:00"},
554 | %{attributes: [], name: "Scheduled", content: "false"},
555 | %{attributes: [], name: "ReportRequestId", content: "50913017123"},
556 | %{
557 | attributes: [],
558 | name: "StartedProcessingDate",
559 | content: "2016-11-18T02:56:05+00:00"
560 | },
561 | %{attributes: [], name: "SubmittedDate", content: "2016-11-18T02:55:59+00:00"},
562 | %{attributes: [], name: "StartDate", content: "2016-11-18T02:55:59+00:00"},
563 | %{attributes: [], name: "CompletedDate", content: "2016-11-18T02:56:12+00:00"},
564 | %{attributes: [], name: "GeneratedReportId", content: "3402759511017123"}
565 | ]
566 | },
567 | %{
568 | attributes: [],
569 | name: "ReportRequestInfo",
570 | content: [
571 | %{
572 | attributes: [],
573 | name: "ReportType",
574 | content: "_GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_"
575 | },
576 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
577 | %{attributes: [], name: "EndDate", content: "2016-11-17T20:55:49+00:00"},
578 | %{attributes: [], name: "Scheduled", content: "false"},
579 | %{attributes: [], name: "ReportRequestId", content: "50912017122"},
580 | %{
581 | attributes: [],
582 | name: "StartedProcessingDate",
583 | content: "2016-11-17T20:55:54+00:00"
584 | },
585 | %{attributes: [], name: "SubmittedDate", content: "2016-11-17T20:55:49+00:00"},
586 | %{attributes: [], name: "StartDate", content: "2016-11-17T20:55:49+00:00"},
587 | %{attributes: [], name: "CompletedDate", content: "2016-11-17T20:56:04+00:00"},
588 | %{attributes: [], name: "GeneratedReportId", content: "3399094295017122"}
589 | ]
590 | },
591 | %{
592 | attributes: [],
593 | name: "ReportRequestInfo",
594 | content: [
595 | %{
596 | attributes: [],
597 | name: "ReportType",
598 | content: "_GET_AFN_INVENTORY_DATA_BY_COUNTRY_"
599 | },
600 | %{attributes: [], name: "ReportProcessingStatus", content: "_DONE_"},
601 | %{attributes: [], name: "EndDate", content: "2016-11-17T20:54:33+00:00"},
602 | %{attributes: [], name: "Scheduled", content: "false"},
603 | %{attributes: [], name: "ReportRequestId", content: "50911017122"},
604 | %{
605 | attributes: [],
606 | name: "StartedProcessingDate",
607 | content: "2016-11-17T20:54:39+00:00"
608 | },
609 | %{attributes: [], name: "SubmittedDate", content: "2016-11-17T20:54:33+00:00"},
610 | %{attributes: [], name: "StartDate", content: "2016-11-17T20:54:33+00:00"},
611 | %{attributes: [], name: "CompletedDate", content: "2016-11-17T20:54:45+00:00"},
612 | %{attributes: [], name: "GeneratedReportId", content: "3405850523017122"}
613 | ]
614 | },
615 | %{
616 | attributes: [],
617 | name: "NextToken",
618 | content:
619 | "bnKUjUwrpfD2jpZedg0wbVuY6vtoszFEs90MCUIyGQ/PkNXwVrATLSf6YzH8PQiWICyhlLgHd4gqVtOYt5i3YX/y5ZICxITwrMWltwHPross7S2LHmNKmcpVErfopfm7ZgI5YM+bbLFRPCnQrq7eGPqiUs2SoKaRPxuuVZAjoAG5Hd34Twm1igafEPREmauvQPEfQK/OReJ9wNJ/XIY3rAvjRfjTJJa5YKoSylcR8gttj983g7esDr0wZ3V0GwaZstMPcqxOnL//uIo+owquzirF36SWlaJ9J5zSS6le1iIsxqkIMXCWKNSOyeZZ1ics+UXSqjS0c15jmJnjJN2V5uMEDoXRsC9PFEVVZ6joTY2uGFVSjAf2NsFIcEAdr4xQz2Y051TPxxk="
620 | }
621 | ]
622 | },
623 | %{
624 | attributes: [],
625 | name: "ResponseMetadata",
626 | content: %{
627 | attributes: [],
628 | name: "RequestId",
629 | content: "7509cdb2-0b69-4ca0-89dc-c77f8a747834"
630 | }
631 | }
632 | ],
633 | name: "GetReportRequestListResponse"
634 | }
635 | end
636 |
637 | def set_prefix_namespace_fn do
638 | fn name, namespace, prefix ->
639 | cond do
640 | namespace != [] && prefix != [] -> "#{prefix}_namespace:#{name}"
641 | true -> name
642 | end
643 | end
644 | end
645 |
646 | def facebook_custom_function_expected do
647 | %{
648 | "rss" => %{
649 | "#content" => %{
650 | "channel" => %{
651 | "title" => "Test Store",
652 | "link" => "http://www.example.com",
653 | "description" => "An example item from the feed",
654 | "item" => %{
655 | "g_namespace:id" => "DB_1",
656 | "g_namespace:title" => "Dog Bowl In Blue",
657 | "g_namespace:description" => "Solid plastic Dog Bowl in marine blue color",
658 | "g_namespace:link" => "http://www.example.com/bowls/db-1.html",
659 | "g_namespace:image_link" => "http://images.example.com/DB_1.png",
660 | "g_namespace:brand" => "Example",
661 | "g_namespace:condition" => "new",
662 | "g_namespace:availability" => "in stock",
663 | "g_namespace:price" => "9.99 GBP",
664 | "g_namespace:shipping" => %{
665 | "g_namespace:country" => "UK",
666 | "g_namespace:service" => "Standard",
667 | "g_namespace:price" => "4.95 GBP"
668 | },
669 | "g_namespace:google_product_category" => "Animals > Pet Supplies",
670 | "g_namespace:custom_label_0" => "Made in Waterford, IE"
671 | }
672 | }
673 | },
674 | "-version" => "2.0"
675 | }
676 | }
677 | end
678 |
679 | def facebook_xmlns_xml_expected do
680 | %{
681 | "rss" => %{
682 | "#content" => %{
683 | "channel" => %{
684 | "title" => "Test Store",
685 | "link" => "http://www.example.com",
686 | "description" => "An example item from the feed",
687 | "item" => %{
688 | "g:id" => "DB_1",
689 | "g:title" => "Dog Bowl In Blue",
690 | "g:description" => "Solid plastic Dog Bowl in marine blue color",
691 | "g:link" => "http://www.example.com/bowls/db-1.html",
692 | "g:image_link" => "http://images.example.com/DB_1.png",
693 | "g:brand" => "Example",
694 | "g:condition" => "new",
695 | "g:availability" => "in stock",
696 | "g:price" => "9.99 GBP",
697 | "g:shipping" => %{
698 | "g:country" => "UK",
699 | "g:service" => "Standard",
700 | "g:price" => "4.95 GBP"
701 | },
702 | "g:google_product_category" => "Animals > Pet Supplies",
703 | "g:custom_label_0" => "Made in Waterford, IE"
704 | }
705 | }
706 | },
707 | "-version" => "2.0"
708 | }
709 | }
710 | end
711 |
712 | def facebook_xmlns_xml do
713 | """
714 |
715 |
716 |
717 | Test Store
718 | http://www.example.com
719 | An example item from the feed
720 | -
721 | DB_1
722 | Dog Bowl In Blue
723 | Solid plastic Dog Bowl in marine blue color
724 | http://www.example.com/bowls/db-1.html
725 | http://images.example.com/DB_1.png
726 | Example
727 | new
728 | in stock
729 | 9.99 GBP
730 |
731 | UK
732 | Standard
733 | 4.95 GBP
734 |
735 | Animals > Pet Supplies
736 | Made in Waterford, IE
737 |
738 |
739 |
740 | """
741 | end
742 |
743 | def amazon_xml do
744 | """
745 |
746 |
747 |
748 | true
749 |
750 | _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_
751 | _DONE_
752 | 2016-11-18T20:53:00+00:00
753 | false
754 | 50920017123
755 | 2016-11-18T20:53:07+00:00
756 | 2016-11-18T20:53:00+00:00
757 | 2016-11-18T20:53:00+00:00
758 | 2016-11-18T20:53:14+00:00
759 | 3412841972017123
760 |
761 |
762 | _GET_AFN_INVENTORY_DATA_BY_COUNTRY_
763 | _DONE_
764 | 2016-11-18T20:51:44+00:00
765 | false
766 | 50919017123
767 | 2016-11-18T20:51:49+00:00
768 | 2016-11-18T20:51:44+00:00
769 | 2016-11-18T20:51:44+00:00
770 | 2016-11-18T20:51:57+00:00
771 | 3414908503017123
772 |
773 |
774 | _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_
775 | _DONE_
776 | 2016-11-18T14:54:10+00:00
777 | false
778 | 50918017123
779 | 2016-11-18T14:54:17+00:00
780 | 2016-11-18T14:54:10+00:00
781 | 2016-11-18T14:54:10+00:00
782 | 2016-11-18T14:54:24+00:00
783 | 3410642176017123
784 |
785 |
786 | _GET_AFN_INVENTORY_DATA_BY_COUNTRY_
787 | _DONE_
788 | 2016-11-18T14:52:26+00:00
789 | false
790 | 50917017123
791 | 2016-11-18T14:52:32+00:00
792 | 2016-11-18T14:52:26+00:00
793 | 2016-11-18T14:52:26+00:00
794 | 2016-11-18T14:52:37+00:00
795 | 3417419172017123
796 |
797 |
798 | _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_
799 | _DONE_
800 | 2016-11-18T08:53:49+00:00
801 | false
802 | 50916017123
803 | 2016-11-18T08:53:54+00:00
804 | 2016-11-18T08:53:49+00:00
805 | 2016-11-18T08:53:49+00:00
806 | 2016-11-18T08:54:01+00:00
807 | 3408643280017123
808 |
809 |
810 | _GET_AFN_INVENTORY_DATA_BY_COUNTRY_
811 | _DONE_
812 | 2016-11-18T08:51:43+00:00
813 | false
814 | 50915017123
815 | 2016-11-18T08:51:49+00:00
816 | 2016-11-18T08:51:43+00:00
817 | 2016-11-18T08:51:43+00:00
818 | 2016-11-18T08:51:55+00:00
819 | 3410105984017123
820 |
821 |
822 | _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_
823 | _DONE_
824 | 2016-11-18T02:57:34+00:00
825 | false
826 | 50914017123
827 | 2016-11-18T02:57:39+00:00
828 | 2016-11-18T02:57:34+00:00
829 | 2016-11-18T02:57:34+00:00
830 | 2016-11-18T02:57:46+00:00
831 | 3408556063017123
832 |
833 |
834 | _GET_AFN_INVENTORY_DATA_BY_COUNTRY_
835 | _DONE_
836 | 2016-11-18T02:55:59+00:00
837 | false
838 | 50913017123
839 | 2016-11-18T02:56:05+00:00
840 | 2016-11-18T02:55:59+00:00
841 | 2016-11-18T02:55:59+00:00
842 | 2016-11-18T02:56:12+00:00
843 | 3402759511017123
844 |
845 |
846 | _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_
847 | _DONE_
848 | 2016-11-17T20:55:49+00:00
849 | false
850 | 50912017122
851 | 2016-11-17T20:55:54+00:00
852 | 2016-11-17T20:55:49+00:00
853 | 2016-11-17T20:55:49+00:00
854 | 2016-11-17T20:56:04+00:00
855 | 3399094295017122
856 |
857 |
858 | _GET_AFN_INVENTORY_DATA_BY_COUNTRY_
859 | _DONE_
860 | 2016-11-17T20:54:33+00:00
861 | false
862 | 50911017122
863 | 2016-11-17T20:54:39+00:00
864 | 2016-11-17T20:54:33+00:00
865 | 2016-11-17T20:54:33+00:00
866 | 2016-11-17T20:54:45+00:00
867 | 3405850523017122
868 |
869 | bnKUjUwrpfD2jpZedg0wbVuY6vtoszFEs90MCUIyGQ/PkNXwVrATLSf6YzH8PQiWICyhlLgHd4gqVtOYt5i3YX/y5ZICxITwrMWltwHPross7S2LHmNKmcpVErfopfm7ZgI5YM+bbLFRPCnQrq7eGPqiUs2SoKaRPxuuVZAjoAG5Hd34Twm1igafEPREmauvQPEfQK/OReJ9wNJ/XIY3rAvjRfjTJJa5YKoSylcR8gttj983g7esDr0wZ3V0GwaZstMPcqxOnL//uIo+owquzirF36SWlaJ9J5zSS6le1iIsxqkIMXCWKNSOyeZZ1ics+UXSqjS0c15jmJnjJN2V5uMEDoXRsC9PFEVVZ6joTY2uGFVSjAf2NsFIcEAdr4xQz2Y051TPxxk=
870 |
871 |
872 | 7509cdb2-0b69-4ca0-89dc-c77f8a747834
873 |
874 |
875 | """
876 | end
877 | end
878 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------