├── .github
└── workflows
│ ├── ci.yml
│ └── hexpm-release.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── Makefile
├── README.md
├── c_src
├── fxml.c
└── fxml_stream.c
├── configure
├── configure.ac
├── include
├── fxml.hrl
└── fxml_gen.hrl
├── rebar.config
├── rebar.config.script
├── spec
├── README.md
└── fxmlrpc_codec.spec
├── src
├── fast_xml.app.src
├── fast_xml.erl
├── fxml.erl
├── fxml_gen.erl
├── fxml_gen_pt.erl
├── fxml_stream.erl
├── fxml_sup.erl
├── fxmlrpc.erl
├── fxmlrpc_codec.erl
└── fxmlrpc_codec_external.erl
├── test
└── fxml_test.erl
└── vars.config.in
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 |
7 | tests:
8 | name: Tests
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | otp: ['20', '25', '26', '27', '28']
13 | runs-on: ubuntu-24.04
14 | container:
15 | image: public.ecr.aws/docker/library/erlang:${{ matrix.otp }}
16 | steps:
17 | - uses: actions/checkout@v4
18 | - run: ./configure --enable-gcov
19 | - run: make
20 | - run: rebar3 compile
21 | - run: rebar3 xref
22 | - run: rebar3 dialyzer
23 | - run: rebar3 eunit -v
24 | if: matrix.otp >= 26
25 | - run: rebar3 eunit -v
26 | if: matrix.otp >= 26
27 | - name: Send to Coveralls
28 | if: matrix.otp == 27
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | run: |
32 | apt-get -qq update
33 | apt-get -qq install pipx
34 | pipx install cpp-coveralls
35 | /github/home/.local/bin/cpp-coveralls -b `pwd` --verbose --gcov-options '\-lp' --dump c.json
36 | ADDJSONFILE=c.json COVERALLS=true rebar3 as test coveralls send
37 | curl -v -k https://coveralls.io/webhook \
38 | --header "Content-Type: application/json" \
39 | --data '{"repo_name":"$GITHUB_REPOSITORY",
40 | "repo_token":"$GITHUB_TOKEN",
41 | "payload":{"build_num":$GITHUB_RUN_ID,
42 | "status":"done"}}'
43 |
--------------------------------------------------------------------------------
/.github/workflows/hexpm-release.yml:
--------------------------------------------------------------------------------
1 | name: Hex
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-24.04
11 | steps:
12 |
13 | - name: Check out
14 | uses: actions/checkout@v4
15 |
16 | - name: Get Erlang/OTP
17 | uses: erlef/setup-beam@v1
18 | with:
19 | otp-version: 27
20 | rebar3-version: '3.25.0'
21 |
22 | - name: Generate documentation
23 | run: rebar3 edoc
24 |
25 | - name: list dir
26 | run: ls -la
27 | - name: Setup rebar3 hex
28 | run: |
29 | mkdir -p ~/.config/rebar3/
30 | echo "{plugins, [rebar3_hex]}." > ~/.config/rebar3/rebar.config
31 |
32 | - run: rebar3 edoc
33 |
34 | - name: Prepare Markdown
35 | run: |
36 | echo "" >>README.md
37 | echo "## EDoc documentation" >>README.md
38 | echo "" >>README.md
39 | echo "You can check this library's " >>README.md
40 | echo "[EDoc documentation](edoc.html), " >>README.md
41 | echo "generated automatically from the source code comments." >>README.md
42 |
43 | - name: Convert Markdown to HTML
44 | uses: natescherer/markdown-to-html-with-github-style-action@v1.1.0
45 | with:
46 | path: README.md
47 |
48 | - run: |
49 | mv doc/index.html doc/edoc.html
50 | mv README.html doc/index.html
51 |
52 | - name: Publish to hex.pm
53 | run: DEBUG=1 rebar3 hex publish --repo hexpm --yes
54 | env:
55 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
56 |
57 | - name: Show dump head
58 | if: failure()
59 | run: head rebar3.crashdump
60 |
61 | - uses: actions/upload-artifact@v4
62 | if: failure()
63 | with:
64 | name: rebar3-dump
65 | path: rebar*
66 | if-no-files-found: ignore
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swo
2 | *.swp
3 | .eunit
4 | .rebar
5 | _build
6 | autom4te.cache
7 | c_src/*.d
8 | c_src/*.gcda
9 | c_src/*.gcno
10 | c_src/*.o
11 | config.log
12 | config.status
13 | deps
14 | ebin
15 | priv
16 | rebar.lock
17 | vars.config
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Version 1.1.56
2 |
3 | * Updating p1_utils to version 1.0.27.
4 |
5 | # Version 1.1.54
6 |
7 | * Improve `make spec` when using Erlang 26+
8 |
9 | # Version 1.1.53
10 |
11 | * Add 'use_maps' option to fxml_stream.parse_element()
12 | * Various CI improvments
13 | * Fix issues with rebar3 and make spec
14 |
15 | # Version 1.1.52
16 |
17 | * Updating p1_utils to version 1.0.26.
18 | * Fix issues on OTP27
19 | * Make compatible with expat 2.6
20 |
21 | # Version 1.1.51
22 |
23 | * Fix hex release scripts
24 |
25 | # Version 1.1.50
26 |
27 | * Fix `make spec` on OTP >= 25
28 |
29 | # Version 1.1.49
30 |
31 | * Updating p1_utils to version 1.0.25.
32 | * Test for CVE-2022-25236 compilance
33 |
34 | # Version 1.1.48
35 |
36 | * Generate documentation before publishing to hex
37 | * Load the NIFs in the on_load callback, to support restart
38 | * Add always_encode field to #attr{}
39 |
40 | # Version 1.1.47
41 |
42 | * Updating p1_utils to version 1.0.23.
43 | * Switch from using Travis to Github Actions as CI
44 | * Fix compatibility with OTP24
45 |
46 | # Version 1.1.46
47 |
48 | * Updating p1_utils to version 1.0.22.
49 |
50 | # Version 1.1.45
51 |
52 | * Updating p1_utils to version 1.0.21.
53 |
54 | # Version 1.1.44
55 |
56 | * Get back compatibility with Erlang older than 20
57 | * Add JSON encode/decode functions generation
58 | * Update hex to compile ejabberd with rebar3
59 |
60 | # Version 1.1.43
61 |
62 | * Updating p1_utils to version 1.0.20.
63 |
64 | # Version 1.1.42
65 |
66 | * Fix compilation with Erlang/OTP 23.0
67 |
68 | # Version 1.1.41
69 |
70 | * Updating p1_utils to version 1.0.19.
71 |
72 | # Version 1.1.40
73 |
74 | * Fix issues with travis testing
75 |
76 | # Version 1.1.39
77 |
78 | * Updating p1_utils to version 1.0.18.
79 | * Update copyright year
80 |
81 | # Version 1.1.38
82 |
83 | * Updating p1_utils to version 1.0.17.
84 |
85 | # Version 1.1.37
86 |
87 | * Updating p1_utils to version 1.0.16.
88 | * Update XMLRPC codec
89 | * Fail with 'badarg' on unknown records
90 |
91 | # Version 1.1.36
92 |
93 | * Updating p1_utils to version 1.0.15.
94 |
95 | # Version 1.1.35
96 |
97 | * Updating p1_utils to version 1.0.14.
98 | * Add contribution guide
99 |
100 | # Version 1.1.34
101 |
102 | * Updating p1_utils to version 1.0.13.
103 |
104 | # Version 1.1.33
105 |
106 | * Updating p1_utils to version 6ff85e8.
107 | * Fix incompatibility with OTP21
108 |
109 | # Version 1.1.32
110 |
111 | * Don't crash when trying to encode xmlcdata
112 |
113 | # Version 1.1.31
114 |
115 | * Updating p1_utils to version 1.0.12.
116 |
117 | # Version 1.1.30
118 |
119 | * Improve detection of rebar3
120 | * Define p1\_utils as application dependency
121 |
122 | # Version 1.1.29
123 |
124 | * Updating p1_utils to version 1.0.11.
125 | * Fix compilation with rebar3
126 | * Get rid of $\_xmls label
127 |
128 | # Version 1.1.28
129 | * Include Makefile in package generated for hex
130 |
131 | # Version 1.1.27
132 |
133 | * Freeze dependencies in mix.lock file to be more friendly with hex.pm
134 | * Fix ambiguous Elixir syntax in mix.exs
135 |
136 | # Version 1.1.26
137 |
138 | * Simplify pretty printer generation
139 | * Generate get_els/1 and set_els/2
140 | * The pretty printer should traverse elements recursively
141 | * Extra test for too big input
142 |
143 | # Version 1.1.25
144 |
145 | * Invalidate sorted data when generating stanza-too-big-error
146 |
147 | # Version 1.1.24
148 |
149 | * Updating p1_utils to version 1.0.10.
150 | * Make XML generator work on R19.3+
151 |
152 | # Version 1.1.23
153 |
154 | * depends on p1_utils-1.0.9
155 |
156 | # Version 1.1.22
157 |
158 | * Fix md5 sum calculation of modules for OTP17 (Evgeniy Khramtsov)
159 | * Fix type spec for fxml_stream:parse_element/1 (Evgeniy Khramtsov)
160 |
161 | # Version 1.1.21
162 |
163 | * Add code for building on FreeBSD (Dave Cottlehuber)
164 |
165 | # Version 1.1.20
166 |
167 | * Make XML generator working on OTP 18 (Evgeniy Khramtsov)
168 |
169 | # Version 1.1.19
170 |
171 | * Add checks for empty string (Paweł Chmielowski)
172 | * Remove unused code (Paweł Chmielowski)
173 | * Load locally build .so file when performing tests (Paweł Chmielowski)
174 |
175 | # Version 1.1.18
176 |
177 | * Use p1_utils 1.0.6 (Paweł Chmielowski)
178 | * fix xref with otp 17 (Paweł Chmielowski)
179 |
180 | # Version 1.1.17
181 |
182 | * Add 'undefined' type to some record fields type specs (Evgeniy Khramtsov)
183 |
184 | # Version 1.1.16
185 |
186 | * Improve XML generator (Evgeniy Khramtsov)
187 |
188 | # Version 1.1.15
189 |
190 | * Update to p1_utils 1.0.5 (Mickaël Rémond)
191 |
192 | # Version 1.1.14
193 |
194 | * Erlang OTP R19 compliance (Paweł Chmielowski)
195 | * Fix compilation on rebar3 (Paweł Chmielowski)
196 |
197 | # Version 1.1.13
198 |
199 | * Use p1_utils 1.0.4 (Mickaël Rémond)
200 |
201 | # Version 1.1.12
202 |
203 | * Generator improvements (Evgeny Khramtsov)
204 |
205 | # Version 1.1.11
206 |
207 | * Now properly includes Elixir lib/ directory in hex.pm package (Mickaël Rémond)
208 |
209 | # Version 1.1.10
210 |
211 | * Split build in two steps to fix link step on Ubuntu (Paweł Chmielowski - Mickaël Rémond)
212 | * Clean Makefile.mix to remove duplicated code (Paweł Chmielowski)
213 |
214 | # Version 1.1.9
215 |
216 | * Fix Linux build with Mix (Paweł Chmielowski)
217 |
218 | # Version 1.1.8
219 |
220 | * Package priv/lib structure to make sure everything is properly build by mix (Mickaël Rémond)
221 |
222 | # Version 1.1.7
223 |
224 | * Fix indent issue in Mix Makefile (Mickaël Rémond)
225 |
226 | # Version 1.1.6
227 |
228 | * Add missing Makefile.mix file in rebar hex.pm package description (Mickaël Rémond)
229 | * Make sure priv dir is created when building with mix and included in package dir list (Mickaël Rémond)
230 |
231 | # Version 1.1.4
232 |
233 | This is an Elixir friendly update:
234 |
235 | * Add ability to return maps instead of xmlel record (Paweł Chmielowski)
236 | * Add ability to tell parser to return Elixir structs instead of records (Mickaël Rémond)
237 | * Add Elixir tests (Mickaël Rémond)
238 |
239 | # Version 1.1.3
240 |
241 | * Memory optimizations (Paweł Chmielowski)
242 | * Update to latest version of p1_utils (Mickaël Rémond)
243 | * Erlang OTP R18 compliance (Evgeniy Khramtsov)
244 |
245 | # Version 1.1.2
246 |
247 | * Application is now called fast_xml (Mickaël Rémond)
248 |
249 | # Version 1.1.1
250 |
251 | * Support for both rebar and rebar3 (Mickaël Rémond)
252 | * Huge performance and memory improvements (Paweł Chmielowski)
253 | * Normalize namespace prefixed elements (Paweł Chmielowski)
254 | * Document how to run tests (Mickaël Rémond)
255 | * Architecture documentation in README.md (Mickaël Rémond)
256 | * Introduce Elixir Quickcheck tests (Mickaël Rémond)
257 | * Support C code coverage (Paweł Chmielowski)
258 | * Better test case coverage (Evgeniy Khramtsov)
259 | * Continuous integration with Travis CI and Coveralls (Paweł Chmielowski - Mickaël Rémond)
260 | * Test refactoring (Evgeniy Khramtsov)
261 | * Save cflags/ldflags passed to configure (Paweł Chmielowski)
262 | * Move code for locating nif files to p1_utils package (Paweł Chmielowski)
263 | * Improve code for locating .so part (Paweł Chmielowski)
264 | * Do not check Expat presence via m4 macro (Evgeniy Khramtsov)
265 |
266 | # Version 1.1.0
267 |
268 | * Initial release on Hex.pm (Mickaël Rémond)
269 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at contact@process-one.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We'd love for you to contribute to our source code and to make our project even better than it is
4 | today! Here are the guidelines we'd like you to follow:
5 |
6 | * [Code of Conduct](#coc)
7 | * [Questions and Problems](#question)
8 | * [Issues and Bugs](#issue)
9 | * [Feature Requests](#feature)
10 | * [Issue Submission Guidelines](#submit)
11 | * [Pull Request Submission Guidelines](#submit-pr)
12 | * [Signing the CLA](#cla)
13 |
14 | ## Code of Conduct
15 |
16 | Help us keep our community open-minded and inclusive. Please read and follow our [Code of Conduct][coc].
17 |
18 | ## Questions, Bugs, Features
19 |
20 | ### Got a Question or Problem?
21 |
22 | Do not open issues for general support questions as we want to keep GitHub issues for bug reports
23 | and feature requests. You've got much better chances of getting your question answered on dedicated
24 | support platforms, the best being [Stack Overflow][stackoverflow].
25 |
26 | Stack Overflow is a much better place to ask questions since:
27 |
28 | - there are thousands of people willing to help on Stack Overflow
29 | - questions and answers stay available for public viewing so your question / answer might help
30 | someone else
31 | - Stack Overflow's voting system assures that the best answers are prominently visible.
32 |
33 | To save your and our time, we will systematically close all issues that are requests for general
34 | support and redirect people to the section you are reading right now.
35 |
36 | ### Found an Issue or Bug?
37 |
38 | If you find a bug in the source code, you can help us by submitting an issue to our
39 | [GitHub Repository][github]. Even better, you can submit a Pull Request with a fix.
40 |
41 | ### Missing a Feature?
42 |
43 | You can request a new feature by submitting an issue to our [GitHub Repository][github-issues].
44 |
45 | If you would like to implement a new feature then consider what kind of change it is:
46 |
47 | * **Major Changes** that you wish to contribute to the project should be discussed first in an
48 | [GitHub issue][github-issues] that clearly outlines the changes and benefits of the feature.
49 | * **Small Changes** can directly be crafted and submitted to the [GitHub Repository][github]
50 | as a Pull Request. See the section about [Pull Request Submission Guidelines](#submit-pr).
51 |
52 | ## Issue Submission Guidelines
53 |
54 | Before you submit your issue search the archive, maybe your question was already answered.
55 |
56 | If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize
57 | the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
58 |
59 | The "[new issue][github-new-issue]" form contains a number of prompts that you should fill out to
60 | make it easier to understand and categorize the issue.
61 |
62 | ## Pull Request Submission Guidelines
63 |
64 | By submitting a pull request for a code or doc contribution, you need to have the right
65 | to grant your contribution's copyright license to ProcessOne. Please check [ProcessOne CLA][cla]
66 | for details.
67 |
68 | Before you submit your pull request consider the following guidelines:
69 |
70 | * Search [GitHub][github-pr] for an open or closed Pull Request
71 | that relates to your submission. You don't want to duplicate effort.
72 | * Make your changes in a new git branch:
73 |
74 | ```shell
75 | git checkout -b my-fix-branch master
76 | ```
77 | * Test your changes and, if relevant, expand the automated test suite.
78 | * Create your patch commit, including appropriate test cases.
79 | * If the changes affect public APIs, change or add relevant documentation.
80 | * Commit your changes using a descriptive commit message.
81 |
82 | ```shell
83 | git commit -a
84 | ```
85 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
86 |
87 | * Push your branch to GitHub:
88 |
89 | ```shell
90 | git push origin my-fix-branch
91 | ```
92 |
93 | * In GitHub, send a pull request to `master` branch. This will trigger the continuous integration and run the test.
94 | We will also notify you if you have not yet signed the [contribution agreement][cla].
95 |
96 | * If you find that the continunous integration has failed, look into the logs to find out
97 | if your changes caused test failures, the commit message was malformed etc. If you find that the
98 | tests failed or times out for unrelated reasons, you can ping a team member so that the build can be
99 | restarted.
100 |
101 | * If we suggest changes, then:
102 |
103 | * Make the required updates.
104 | * Test your changes and test cases.
105 | * Commit your changes to your branch (e.g. `my-fix-branch`).
106 | * Push the changes to your GitHub repository (this will update your Pull Request).
107 |
108 | You can also amend the initial commits and force push them to the branch.
109 |
110 | ```shell
111 | git rebase master -i
112 | git push origin my-fix-branch -f
113 | ```
114 |
115 | This is generally easier to follow, but separate commits are useful if the Pull Request contains
116 | iterations that might be interesting to see side-by-side.
117 |
118 | That's it! Thank you for your contribution!
119 |
120 | ## Signing the Contributor License Agreement (CLA)
121 |
122 | Upon submitting a Pull Request, we will ask you to sign our CLA if you haven't done
123 | so before. It's a quick process, we promise, and you will be able to do it all online
124 |
125 | You can read [ProcessOne Contribution License Agreement][cla] in PDF.
126 |
127 | This is part of the legal framework of the open-source ecosystem that adds some red tape,
128 | but protects both the contributor and the company / foundation behind the project. It also
129 | gives us the option to relicense the code with a more permissive license in the future.
130 |
131 |
132 | [coc]: https://github.com/processone/fast_xml/blob/master/CODE_OF_CONDUCT.md
133 | [stackoverflow]: https://stackoverflow.com/
134 | [github]: https://github.com/processone/fast_xml
135 | [github-issues]: https://github.com/processone/fast_xml/issues
136 | [github-new-issue]: https://github.com/processone/fast_xml/issues/new
137 | [github-pr]: https://github.com/processone/fast_xml/pulls
138 | [cla]: https://www.process-one.net/resources/ejabberd-cla.pdf
139 | [license]: https://github.com/processone/fast_xml/blob/master/LICENSE.txt
140 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REBAR ?= rebar
2 |
3 | IS_REBAR3:=$(shell expr `$(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}'` '>=' 3)
4 |
5 | ERL=erl
6 |
7 | all: src
8 |
9 | src:
10 | $(REBAR) get-deps
11 | $(REBAR) compile
12 |
13 | clean:
14 | $(REBAR) clean
15 |
16 | ifeq "$(IS_REBAR3)" "1"
17 | test:
18 | $(REBAR) eunit -v
19 |
20 | spec:
21 | $(ERL) -noinput +B -pa _build/default/lib/fast_xml/ebin -eval 'case fxml_gen:compile("spec/fxmlrpc_codec.spec", [{erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
22 | else
23 | test: all
24 | $(REBAR) -v skip_deps=true eunit
25 | endif
26 |
27 | spec:
28 | $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval 'case fxml_gen:compile("spec/fxmlrpc_codec.spec", [{erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
29 |
30 | check-syntax:
31 | gcc -o nul -S ${CHK_SOURCES}
32 |
33 | .PHONY: clean src test all spec
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Erlang and Elixir XML Parsing
2 |
3 | [](https://github.com/processone/fast_xml/actions/workflows/ci.yml)
4 | [](https://coveralls.io/github/processone/fast_xml?branch=master)
5 | [](https://hex.pm/packages/fast_xml)
6 |
7 | Fast Expat based Erlang XML parsing and manipulation library, with a
8 | strong focus on XML stream parsing from network.
9 |
10 | It supports:
11 |
12 | - Full XML structure parsing: Suitable for small but complete XML chunks.
13 | - XML stream parsing: Suitable for large XML document, or infinite
14 | network XML stream like XMPP.
15 |
16 | This module can parse files much faster than built-in module `xmerl`.
17 | Depending on file complexity and size `fxml_stream:parse_element/1` can
18 | be 8-18 times faster than calling `xmerl_scan:string/2`.
19 |
20 | This application was previously called
21 | [p1_xml](https://github.com/processone/xml) and was renamed after
22 | major optimisations to put emphasis on the fact it is damn fast.
23 |
24 | ## Building
25 |
26 | Erlang XML parser can be build as follow:
27 |
28 | ./configure && make
29 |
30 | Erlang XML parser is a rebar-compatible OTP
31 | application. Alternatively, you can build it with rebar:
32 |
33 | rebar compile
34 |
35 | ## Dependencies
36 |
37 | Erlang XML parser depends on Expat XML parser. You need development
38 | headers for Expat library to build it.
39 |
40 | You can use `configure` options to pass custom path to Expat libraries and headers:
41 |
42 | --with-expat=[ARG] use Expat XML Parser from given prefix (ARG=path);
43 | check standard prefixes (ARG=yes); disable (ARG=no)
44 | --with-expat-inc=[DIR] path to Expat XML Parser headers
45 | --with-expat-lib=[ARG] link options for Expat XML Parser libraries
46 |
47 | ## xmlel record and types
48 |
49 | XML elements are provided as Erlang xmlel records.
50 |
51 | Format of the record allows defining a simple tree-like
52 | structure. xmlel record has the following fields:
53 |
54 | - name :: binary()
55 | - attrs :: [attr()]
56 | - children :: [xmlel() | cdata()]
57 |
58 | cdata type is a tuple of the form:
59 |
60 | {xmlcdata, CData::binary()}
61 |
62 | attr type if a tuple of the form:
63 |
64 | {Name::binary(), Value::binary()}
65 |
66 | ## XML full structure parsing
67 |
68 | You can definitely parse a complete XML structure with `fast_xml`:
69 |
70 | ```shell
71 | $ erl -pa ebin
72 | Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
73 |
74 | Eshell V6.3 (abort with ^G)
75 | 1> application:start(fast_xml).
76 | ok
77 | 2> rr(fxml).
78 | [xmlel]
79 | 3> fxml_stream:parse_element(<<"content cdata">>).
80 | #xmlel{name = <<"test">>,attrs = [],
81 | children = [{xmlcdata,<<"content cdata">>}]}
82 | ```
83 |
84 | ## XML Stream parsing example
85 |
86 | You can also parse continuous stream. Our design allows decoupling
87 | very easily the process receiving the raw XML to parse from the
88 | process receiving the parsed content.
89 |
90 | The workflow is as follow:
91 |
92 | state = new(CallbackPID); parse(state, data); parse(state, moredata); ...
93 |
94 | and the parsed XML fragments (stanzas) are send to CallbackPID.
95 |
96 | With that approach you can be very flexible on how you architect your
97 | own application.
98 |
99 | Here is an example XML stream parsing:
100 |
101 | ```shell
102 | $ erl -pa ebin
103 | Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
104 |
105 | Eshell V6.3 (abort with ^G)
106 |
107 | % Start the application:
108 | 1> application:start(fast_xml).
109 | ok
110 |
111 | % Create a new stream, using self PID to received XML parsing event:
112 | 2> S1 = fxml_stream:new(self()).
113 | <<>>
114 |
115 | % Start feeding content to the XML parser.
116 | 3> S2 = fxml_stream:parse(S1, <<"">>).
117 | <<>>
118 |
119 | % Receive Erlang message send to shell process:
120 | 4> flush().
121 | Shell got {'$gen_event',{xmlstreamstart,<<"root">>,[]}}
122 | ok
123 |
124 | % Feed more content:
125 | 5> S3 = fxml_stream:parse(S2, <<"content cdata">>).
126 | <<>>
127 |
128 | % Receive more messages:
129 | 6> flush().
130 | Shell got {'$gen_event',
131 | {xmlstreamelement,
132 | {xmlel,<<"xmlelement">>,[],
133 | [{xmlcdata,<<"content cdata">>}]}}}
134 | ok
135 |
136 | % Feed more content:
137 | 7> S4 = fxml_stream:parse(S3, <<"">>).
138 | <<>>
139 |
140 | % Receive messages:
141 | 8> flush().
142 | Shell got {'$gen_event',{xmlstreamend,<<"root">>}}
143 | ok
144 |
145 | 9> fxml_stream:close(S4).
146 | true
147 | ```
148 |
149 | Note how the root element is important. We expect to have the root
150 | element serve as boundary with stream start and stream end
151 | event. Then, lower level tags are passed as sub stream elements.
152 |
153 | ## How does this module relate to exmpp ?
154 |
155 | This module is a low level fast XML parser. It is not an XMPP client
156 | library like [exmpp](https://processone.github.io/exmpp/).
157 |
158 | ## References
159 |
160 | This module is use at large scale for parsing massive XML content in
161 | [ejabberd](https://www.ejabberd.im) XMPP server project. It is used in
162 | production in thousands of real life deployments.
163 |
164 | ## Development
165 |
166 | ### Test
167 |
168 | #### Unit test
169 |
170 | You can run eunit test with the command:
171 |
172 | $ rebar eunit
173 |
--------------------------------------------------------------------------------
/c_src/fxml.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | #include
19 | #include
20 | #include
21 |
22 | #define SSL40
23 |
24 | #ifdef SSL40
25 | #define ENIF_ALLOC(SIZE) enif_alloc(SIZE)
26 | #define ENIF_FREE(PTR) enif_free(PTR)
27 | #define ENIF_REALLOC(PTR, SIZE) enif_realloc(PTR, SIZE)
28 | #define ENIF_ALLOC_BINARY(SIZE, BIN) enif_alloc_binary(SIZE, BIN)
29 | #define ENIF_COMPARE(TERM1, TERM2) enif_compare(TERM1, TERM2)
30 | #else
31 | #define ENIF_ALLOC(SIZE) enif_alloc(env, SIZE)
32 | #define ENIF_FREE(PTR) enif_free(env, PTR)
33 | #define ENIF_REALLOC(PTR, SIZE) enif_realloc(env, PTR, SIZE)
34 | #define ENIF_ALLOC_BINARY(SIZE, BIN) enif_alloc_binary(env, SIZE, BIN)
35 | #define ENIF_COMPARE(TERM1, TERM2) enif_compare(env, TERM1, TERM2)
36 | #endif
37 |
38 | static ERL_NIF_TERM atom_xmlelement;
39 | static ERL_NIF_TERM atom_xmlcdata;
40 |
41 | struct buf {
42 | int limit;
43 | int len;
44 | unsigned char *b;
45 | };
46 |
47 | static int make_element(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM el, int is_header);
48 |
49 | static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
50 | {
51 | atom_xmlelement = enif_make_atom(env, "xmlel");
52 | atom_xmlcdata = enif_make_atom(env, "xmlcdata");
53 | return 0;
54 | }
55 |
56 | static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info)
57 | {
58 | return load(env, priv, load_info);
59 | }
60 |
61 | static struct buf *init_buf(ErlNifEnv* env)
62 | {
63 | struct buf *rbuf = ENIF_ALLOC(sizeof(struct buf));
64 | rbuf->limit = 1024;
65 | rbuf->len = 0;
66 | rbuf->b = ENIF_ALLOC(rbuf->limit);
67 | return rbuf;
68 | }
69 |
70 | static void destroy_buf(ErlNifEnv* env, struct buf *rbuf)
71 | {
72 | if (rbuf) {
73 | if (rbuf->b) {
74 | ENIF_FREE(rbuf->b);
75 | };
76 | ENIF_FREE(rbuf);
77 | };
78 | }
79 |
80 | static void resize_buf(ErlNifEnv* env, struct buf *rbuf, int len_to_add)
81 | {
82 | int new_len = rbuf->len + len_to_add;
83 |
84 | if (new_len > rbuf->limit) {
85 | while (new_len > rbuf->limit)
86 | rbuf->limit *= 2;
87 | rbuf->b = ENIF_REALLOC(rbuf->b, rbuf->limit);
88 | }
89 | }
90 |
91 | static void buf_add_char(ErlNifEnv* env, struct buf *rbuf, unsigned char c)
92 | {
93 | resize_buf(env, rbuf, 1);
94 | (rbuf->b)[rbuf->len] = c;
95 | rbuf->len += 1;
96 | }
97 |
98 | static void buf_add_str(ErlNifEnv* env, struct buf *rbuf, char *data, int len)
99 | {
100 | resize_buf(env, rbuf, len);
101 | memcpy(rbuf->b + rbuf->len, data, len);
102 | rbuf->len += len;
103 | }
104 |
105 | static void xml_encode(ErlNifEnv* env, struct buf *rbuf, unsigned char *data, int len)
106 | {
107 | int i;
108 |
109 | for (i = 0; i < len; i++) {
110 | switch (data[i]) {
111 | case '&':
112 | buf_add_str(env, rbuf, "&", 5);
113 | break;
114 | case '<':
115 | buf_add_str(env, rbuf, "<", 4);
116 | break;
117 | case '>':
118 | buf_add_str(env, rbuf, ">", 4);
119 | break;
120 | case '"':
121 | buf_add_str(env, rbuf, """, 6);
122 | break;
123 | case '\'':
124 | buf_add_str(env, rbuf, "'", 6);
125 | break;
126 | default:
127 | buf_add_char(env, rbuf, data[i]);
128 | break;
129 | };
130 | };
131 | }
132 |
133 | static void attr_encode(ErlNifEnv* env, struct buf *rbuf, unsigned char *data, int len)
134 | {
135 | int i;
136 |
137 | for (i = 0; i < len; i++) {
138 | switch (data[i]) {
139 | case '&':
140 | buf_add_str(env, rbuf, "&", 5);
141 | break;
142 | case '<':
143 | buf_add_str(env, rbuf, "<", 4);
144 | break;
145 | case '"':
146 | buf_add_str(env, rbuf, """, 6);
147 | break;
148 | case '\'':
149 | buf_add_str(env, rbuf, "'", 6);
150 | break;
151 | case '\t':
152 | buf_add_str(env, rbuf, " ", 5);
153 | break;
154 | case '\n':
155 | buf_add_str(env, rbuf, "
", 5);
156 | break;
157 | case '\r':
158 | buf_add_str(env, rbuf, "
", 5);
159 | break;
160 | default:
161 | buf_add_char(env, rbuf, data[i]);
162 | break;
163 | };
164 | };
165 | }
166 |
167 | static int make_elements(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM els)
168 | {
169 | ERL_NIF_TERM head, tail;
170 | int ret = 0;
171 |
172 | while (enif_get_list_cell(env, els, &head, &tail)) {
173 | ret = make_element(env, rbuf, head, 0);
174 | if (ret) {
175 | els = tail;
176 | } else {
177 | break;
178 | };
179 | };
180 |
181 | return ret;
182 | }
183 |
184 | static int make_attrs(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM attrs)
185 | {
186 | ErlNifBinary name, data;
187 | ERL_NIF_TERM head, tail;
188 | const ERL_NIF_TERM *tuple;
189 | int arity, ret = 1;
190 |
191 | while (enif_get_list_cell(env, attrs, &head, &tail)) {
192 | if (enif_get_tuple(env, head, &arity, &tuple)) {
193 | if (arity == 2) {
194 | if (enif_inspect_iolist_as_binary(env, tuple[0], &name) &&
195 | enif_inspect_iolist_as_binary(env, tuple[1], &data)) {
196 | buf_add_char(env, rbuf, ' ');
197 | buf_add_str(env, rbuf, (char *)name.data, name.size);
198 | buf_add_str(env, rbuf, "='", 2);
199 | attr_encode(env, rbuf, data.data, data.size);
200 | buf_add_char(env, rbuf, '\'');
201 | attrs = tail;
202 | } else {
203 | ret = 0;
204 | break;
205 | };
206 | } else {
207 | ret = 0;
208 | break;
209 | };
210 | } else {
211 | ret = 0;
212 | break;
213 | };
214 | };
215 |
216 | return ret;
217 | }
218 |
219 | static int make_element(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM el, int is_header)
220 | {
221 | ErlNifBinary cdata, name;
222 | const ERL_NIF_TERM *tuple;
223 | int arity, ret = 0;
224 |
225 | if (enif_get_tuple(env, el, &arity, &tuple)) {
226 | if (arity == 2 && !is_header) {
227 | if (!ENIF_COMPARE(tuple[0], atom_xmlcdata)) {
228 | if (enif_inspect_iolist_as_binary(env, tuple[1], &cdata)) {
229 | xml_encode(env, rbuf, cdata.data, cdata.size);
230 | ret = 1;
231 | };
232 | };
233 | };
234 | if (arity == 4) {
235 | if (!ENIF_COMPARE(tuple[0], atom_xmlelement)) {
236 | if (enif_inspect_iolist_as_binary(env, tuple[1], &name)) {
237 | if (is_header)
238 | buf_add_str(env, rbuf, "", 21);
239 | buf_add_char(env, rbuf, '<');
240 | buf_add_str(env, rbuf, (char *)name.data, name.size);
241 | ret = make_attrs(env, rbuf, tuple[2]);
242 | if (ret) {
243 | if (is_header) {
244 | buf_add_char(env, rbuf, '>');
245 | } else if (enif_is_empty_list(env, tuple[3])) {
246 | buf_add_str(env, rbuf, "/>", 2);
247 | } else {
248 | buf_add_char(env, rbuf, '>');
249 | ret = make_elements(env, rbuf, tuple[3]);
250 | if (ret) {
251 | buf_add_str(env, rbuf, "", 2);
252 | buf_add_str(env, rbuf, (char*)name.data, name.size);
253 | buf_add_char(env, rbuf, '>');
254 | };
255 | };
256 | };
257 | };
258 | };
259 | };
260 | };
261 |
262 | return ret;
263 | }
264 |
265 | static ERL_NIF_TERM element_to(ErlNifEnv* env, int argc,
266 | const ERL_NIF_TERM argv[],
267 | int is_header)
268 | {
269 | ErlNifBinary output;
270 | ERL_NIF_TERM result;
271 | struct buf *rbuf;
272 |
273 | if (argc == 1) {
274 | rbuf = init_buf(env);
275 | if (make_element(env, rbuf, argv[0], is_header)) {
276 | if (ENIF_ALLOC_BINARY(rbuf->len, &output)) {
277 | memcpy(output.data, rbuf->b, rbuf->len);
278 | result = enif_make_binary(env, &output);
279 | destroy_buf(env, rbuf);
280 | return result;
281 | };
282 | };
283 | destroy_buf(env, rbuf);
284 | };
285 |
286 | return enif_make_badarg(env);
287 | }
288 |
289 | static ERL_NIF_TERM element_to_binary(ErlNifEnv* env, int argc,
290 | const ERL_NIF_TERM argv[])
291 | {
292 | return element_to(env, argc, argv, 0);
293 | }
294 |
295 | static ERL_NIF_TERM element_to_header(ErlNifEnv* env, int argc,
296 | const ERL_NIF_TERM argv[])
297 | {
298 | return element_to(env, argc, argv, 1);
299 | }
300 |
301 | static ErlNifFunc nif_funcs[] =
302 | {
303 | {"element_to_binary", 1, element_to_binary},
304 | {"element_to_header", 1, element_to_header}
305 | };
306 |
307 | ERL_NIF_INIT(fxml, nif_funcs, load, NULL, upgrade, NULL)
308 |
--------------------------------------------------------------------------------
/c_src/fxml_stream.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | #define PARSING_NOT_RESUMABLE XML_FALSE
24 |
25 | #define ASSERT(x) if (!(x)) return 0
26 | #define PARSER_ASSERT(X, E) do { if (!(X)) { state->error = (E); XML_StopParser(state->parser, PARSING_NOT_RESUMABLE); return; } } while(0)
27 | #define PARSER_MEM_ASSERT(x) PARSER_ASSERT((x), "enomem")
28 |
29 | typedef struct children_list_t {
30 | union {
31 | ERL_NIF_TERM term;
32 | ErlNifBinary cdata;
33 | };
34 | struct children_list_t *next;
35 | char is_cdata;
36 | } children_list_t;
37 |
38 | typedef struct attrs_list_t {
39 | ErlNifBinary name;
40 | ErlNifBinary value;
41 | struct attrs_list_t *next;
42 | } attrs_list_t;
43 |
44 | typedef struct xmlel_stack_t {
45 | ERL_NIF_TERM name;
46 | ERL_NIF_TERM attrs;
47 | children_list_t *children;
48 | struct xmlel_stack_t *next;
49 | char *namespace_str;
50 | int redefined_top_prefix;
51 | } xmlel_stack_t;
52 |
53 |
54 | typedef struct {
55 | ErlNifEnv *env;
56 | ErlNifEnv *send_env;
57 | ErlNifPid *pid;
58 | size_t depth;
59 | size_t size;
60 | size_t max_size;
61 | XML_Parser parser;
62 | xmlel_stack_t *elements_stack;
63 | attrs_list_t *xmlns_attrs;
64 | attrs_list_t *top_xmlns_attrs;
65 | const char *error;
66 | char normalize_ns:1;
67 | char gen_server:1;
68 | char use_maps:1;
69 | } state_t;
70 |
71 | typedef enum xmlns_op {
72 | OP_ERROR = 0,
73 | OP_REMOVE_PREFIX,
74 | OP_REMOVE_XMLNS,
75 | OP_REPLACE_XMLNS,
76 | OP_NOP
77 | } xmlns_op;
78 |
79 | static XML_Memory_Handling_Suite ms = {
80 | .malloc_fcn = enif_alloc,
81 | .realloc_fcn = enif_realloc,
82 | .free_fcn = enif_free
83 | };
84 |
85 | static ErlNifResourceType *parser_state_t = NULL;
86 |
87 | #define FAKE_BIN(STR) { sizeof(STR)-1, (unsigned char*)STR }
88 |
89 | static attrs_list_t stream_stream_ns_attr = {
90 | FAKE_BIN("stream:stream"),
91 | FAKE_BIN("http://etherx.jabber.org/streams")
92 | };
93 |
94 | static int same_str_buf(const char *str, const char *buf, size_t buf_len)
95 | {
96 | if (strlen(str) != buf_len)
97 | return 0;
98 | if (!buf_len)
99 | return 1;
100 | return memcmp(str, buf, buf_len) == 0;
101 | }
102 |
103 | static char *dup_buf(const char *buf, size_t buf_len)
104 | {
105 | char *res = enif_alloc(buf_len+1);
106 | if (!res)
107 | return NULL;
108 |
109 | if (buf_len)
110 | memcpy(res, buf, buf_len);
111 | res[buf_len] = '\0';
112 |
113 | return res;
114 | }
115 |
116 | static int dup_to_bin(ErlNifBinary *bin, const char *buf, size_t buf_len)
117 | {
118 | if (!enif_alloc_binary(buf_len, bin))
119 | return 0;
120 |
121 | memcpy(bin->data, buf, buf_len);
122 |
123 | return 1;
124 | }
125 |
126 | static ERL_NIF_TERM dup_to_term(ErlNifEnv *env, const char *buf, size_t buf_len)
127 | {
128 | ERL_NIF_TERM term;
129 |
130 | unsigned char *str = enif_make_new_binary(env, buf_len, &term);
131 | memcpy(str, buf, buf_len);
132 |
133 | return term;
134 | }
135 |
136 | static int has_prefix_ns_from_list(attrs_list_t*list, const char *pfx, size_t pfx_len,
137 | const char *ns, size_t ns_len)
138 | {
139 | while (pfx_len && list) {
140 | if ((pfx == NULL ||
141 | (list->name.size == pfx_len && memcmp(list->name.data, pfx, pfx_len) == 0)) &&
142 | (ns == NULL ||
143 | (list->value.size == ns_len && memcmp(list->value.data, ns, ns_len) == 0)))
144 | {
145 | return 1;
146 | }
147 | list = list->next;
148 | }
149 | return 0;
150 | }
151 |
152 | static int has_prefix_ns_from_top(state_t *state, const char *pfx, size_t pfx_len,
153 | const char *ns, size_t ns_len)
154 | {
155 | if (state->elements_stack->redefined_top_prefix || !pfx_len)
156 | return 0;
157 |
158 | return has_prefix_ns_from_list(state->top_xmlns_attrs, pfx, pfx_len, ns, ns_len);
159 | }
160 |
161 | static xmlns_op encode_name(state_t *state, const char *xml_name, ErlNifBinary *buf,
162 | char **ns_str, char **pfx_str, int top_element)
163 | {
164 | const char *parts[3];
165 | int i, idx = 0;
166 |
167 | for (i = 0; ; i++) {
168 | if (!xml_name[i] || xml_name[i] == '\n') {
169 | parts[idx++] = xml_name + i;
170 | if (!xml_name[i])
171 | break;
172 | }
173 | if (idx >= 3)
174 | return OP_ERROR;
175 | }
176 | const char *ns = NULL, *name = NULL, *prefix = NULL;
177 | size_t ns_len = 0, name_len = 0, prefix_len = 0;
178 |
179 | if (idx == 1) {
180 | name = xml_name;
181 | name_len = parts[0] - xml_name;
182 | } else {
183 | ns = xml_name;
184 | ns_len = parts[0] - xml_name;
185 | name = parts[0] + 1;
186 | name_len = parts[1] - parts[0] - 1;
187 | if (idx == 3) {
188 | prefix = parts[1] + 1;
189 | prefix_len = parts[2] - parts[1] - 1;
190 | }
191 | }
192 |
193 | int with_prefix = prefix_len && (top_element || !ns_str);
194 | xmlns_op res = OP_REPLACE_XMLNS;
195 |
196 | if (state->normalize_ns && !top_element) {
197 | if (ns_str) {
198 | if (!state->elements_stack->redefined_top_prefix && prefix_len &&
199 | has_prefix_ns_from_top(state, prefix, prefix_len, ns, ns_len))
200 | {
201 | res = OP_REMOVE_PREFIX;
202 | with_prefix = 1;
203 | } else if (same_str_buf(state->elements_stack->namespace_str, ns, ns_len)) {
204 | res = OP_REMOVE_XMLNS;
205 | with_prefix = 0;
206 | }
207 | }
208 | } else
209 | res = OP_NOP;
210 |
211 | if (with_prefix) {
212 | ASSERT(enif_alloc_binary(name_len + prefix_len + 1, buf));
213 | memcpy(buf->data, prefix, prefix_len);
214 | buf->data[prefix_len] = ':';
215 | memcpy(buf->data + prefix_len + 1, name, name_len);
216 | } else {
217 | ASSERT(dup_to_bin(buf, name, name_len));
218 | }
219 |
220 | if (ns_str) {
221 | if (top_element && prefix_len > 0)
222 | *ns_str = NULL;
223 | else {
224 | *ns_str = top_element ? dup_buf(ns, ns_len) :
225 | res == OP_REMOVE_PREFIX ?
226 | state->elements_stack->namespace_str :
227 | dup_buf(ns, ns_len);
228 |
229 | if (!*ns_str) {
230 | enif_release_binary(buf);
231 | return OP_ERROR;
232 | }
233 | }
234 | if (pfx_str) {
235 | if (res == OP_REMOVE_PREFIX) {
236 | *pfx_str = dup_buf(prefix, prefix_len);
237 | if (!*pfx_str) {
238 | enif_release_binary(buf);
239 | if (ns_str && *ns_str)
240 | enif_free(*ns_str);
241 | return OP_ERROR;
242 | }
243 | } else
244 | *pfx_str = NULL;
245 | }
246 | }
247 |
248 | return res;
249 | }
250 |
251 | static ERL_NIF_TERM str2bin(ErlNifEnv *env, const char *s)
252 | {
253 | return dup_to_term(env, s, strlen(s));
254 | }
255 |
256 | static void send_event(state_t *state, ERL_NIF_TERM el)
257 | {
258 | state->size = 0;
259 | if (state->gen_server) {
260 | enif_send(state->env, state->pid, state->send_env,
261 | enif_make_tuple2(state->send_env,
262 | enif_make_atom(state->send_env, "$gen_event"),
263 | el));
264 | } else {
265 | enif_send(state->env, state->pid, state->send_env, el);
266 | }
267 | enif_clear_env(state->send_env);
268 | }
269 |
270 | static void send_all_state_event(state_t *state, ERL_NIF_TERM el)
271 | {
272 | state->size = 0;
273 | if (state->gen_server) {
274 | enif_send(state->env, state->pid, state->send_env,
275 | enif_make_tuple2(state->send_env,
276 | enif_make_atom(state->send_env, "$gen_all_state_event"),
277 | el));
278 | } else {
279 | enif_send(state->env, state->pid, state->send_env, el);
280 | }
281 | enif_clear_env(state->send_env);
282 | }
283 |
284 | static ERL_NIF_TERM append_attr(state_t *state, ERL_NIF_TERM root, ERL_NIF_TERM name, ERL_NIF_TERM value) {
285 | ErlNifEnv* env = state->send_env;
286 |
287 | if (state->use_maps) {
288 | ERL_NIF_TERM res;
289 | enif_make_map_put(env, root, name, value, &res);
290 | return res;
291 | } else {
292 | return enif_make_list_cell(env, enif_make_tuple2(env, name, value), root);
293 | }
294 | }
295 |
296 | void erlXML_StartElementHandler(state_t *state,
297 | const XML_Char *name,
298 | const XML_Char **atts)
299 | {
300 | int i = 0;
301 | ErlNifEnv* env = state->send_env;
302 | ERL_NIF_TERM attrs_term;
303 | ErlNifBinary name_bin;
304 |
305 | if (state->use_maps) {
306 | attrs_term = enif_make_new_map(env);
307 | } else {
308 | attrs_term = enif_make_list(env, 0);
309 | }
310 |
311 | if (state->error)
312 | return;
313 |
314 | state->depth++;
315 |
316 | while (atts[i])
317 | i += 2;
318 |
319 | i -= 2;
320 |
321 | while (i >= 0) {
322 | ErlNifBinary attr_name;
323 | ERL_NIF_TERM val;
324 | unsigned char *val_str;
325 |
326 | PARSER_MEM_ASSERT(encode_name(state, atts[i], &attr_name, NULL, NULL, 0));
327 |
328 | size_t val_len = strlen(atts[i+1]);
329 | val_str = enif_make_new_binary(env, val_len, &val);
330 | PARSER_MEM_ASSERT(val_str);
331 | memcpy(val_str, atts[i+1], val_len);
332 |
333 | attrs_term = append_attr(state, attrs_term, enif_make_binary(env, &attr_name), val);
334 | i -= 2;
335 | }
336 |
337 | char *ns = NULL, *pfx = NULL;
338 | int redefined_top_prefix = state->depth > 1 ? state->elements_stack->redefined_top_prefix : 0;
339 | int xmlns_op;
340 |
341 | if (state->normalize_ns)
342 | xmlns_op = encode_name(state, name, &name_bin, &ns, &pfx, state->depth == 1);
343 | else
344 | xmlns_op = encode_name(state, name, &name_bin, NULL, NULL, state->depth == 1);
345 |
346 | PARSER_MEM_ASSERT(xmlns_op);
347 |
348 | if (!state->normalize_ns)
349 | xmlns_op = OP_NOP;
350 |
351 | int non_xmpp_ns = -1;
352 | int had_stream_stream = 0;
353 |
354 | while (state->xmlns_attrs) {
355 | ERL_NIF_TERM tuple = 0;
356 | ERL_NIF_TERM tuple_name = 0, tuple_val = 0;
357 | attrs_list_t *c = state->xmlns_attrs;
358 | ErlNifBinary new_prefix, new_ns;
359 |
360 | state->xmlns_attrs = c->next;
361 |
362 | if (state->depth == 1 && state->normalize_ns && c->name.size > 6) {
363 | if (non_xmpp_ns != 1 || !has_prefix_ns_from_list(&stream_stream_ns_attr,
364 | (char*)c->name.data+6, c->name.size-6,
365 | (char*)c->value.data, c->value.size))
366 | {
367 | if (had_stream_stream) {
368 | PARSER_MEM_ASSERT(dup_to_bin(&new_prefix, (char*)stream_stream_ns_attr.name.data,
369 | stream_stream_ns_attr.name.size));
370 | PARSER_MEM_ASSERT(dup_to_bin(&new_ns, (char*)stream_stream_ns_attr.value.data,
371 | stream_stream_ns_attr.value.size));
372 | c->name = new_prefix;
373 | c->value = new_ns;
374 | c->next = state->top_xmlns_attrs;
375 | state->top_xmlns_attrs = c;
376 | had_stream_stream = 0;
377 | }
378 | non_xmpp_ns = 1;
379 | PARSER_MEM_ASSERT(dup_to_bin(&new_prefix, (char*)c->name.data+6, c->name.size-6));
380 | PARSER_MEM_ASSERT(dup_to_bin(&new_ns, (char*)c->value.data, c->value.size));
381 | } else {
382 | had_stream_stream = 1;
383 | non_xmpp_ns = 0;
384 | }
385 | }
386 |
387 | if (c->name.size == 5) { // xmlns
388 | if (xmlns_op == OP_REMOVE_XMLNS) {
389 | enif_release_binary(&c->name);
390 | enif_release_binary(&c->value);
391 | enif_free(c);
392 | continue;
393 | } else if (xmlns_op == OP_REPLACE_XMLNS) {
394 | enif_release_binary(&c->value);
395 | if (state->use_maps) {
396 | tuple_name = enif_make_binary(env, &c->name);
397 | tuple_val = dup_to_term(env, ns, strlen(ns));
398 | } else {
399 | tuple = enif_make_tuple2(env, enif_make_binary(env, &c->name),
400 | dup_to_term(env, ns, strlen(ns)));
401 | }
402 | xmlns_op = OP_NOP;
403 | }
404 | if (!ns && state->normalize_ns)
405 | PARSER_MEM_ASSERT(ns = dup_buf((char *) c->value.data, c->value.size));
406 | } else if (xmlns_op == OP_REMOVE_PREFIX &&
407 | same_str_buf(pfx, (char*)c->name.data + 6, c->name.size - 6)) {
408 | enif_release_binary(&c->name);
409 | enif_release_binary(&c->value);
410 | enif_free(c);
411 | continue;
412 | } else if (!redefined_top_prefix && state->depth > 1 && c->name.size > 6 &&
413 | has_prefix_ns_from_top(state, (char*)c->name.data + 6, c->name.size - 6, NULL, 0)) {
414 | redefined_top_prefix = 1;
415 | }
416 |
417 | if (state->use_maps) {
418 | if (!tuple_name) {
419 | enif_make_map_update(env, attrs_term, enif_make_binary(env, &c->name),
420 | enif_make_binary(env, &c->value), &attrs_term);
421 | } else
422 | enif_make_map_update(env, attrs_term, tuple_name, tuple_val, &attrs_term);
423 | } else {
424 | if (!tuple) {
425 | tuple = enif_make_tuple2(env, enif_make_binary(env, &c->name),
426 | enif_make_binary(env, &c->value));
427 | }
428 | attrs_term = enif_make_list_cell(env, tuple, attrs_term);
429 | }
430 |
431 | if (non_xmpp_ns && state->depth == 1 && state->normalize_ns && c->name.size > 6) {
432 | c->name = new_prefix;
433 | c->value = new_ns;
434 | c->next = state->top_xmlns_attrs;
435 | state->top_xmlns_attrs = c;
436 | } else
437 | enif_free(c);
438 | }
439 |
440 | if (!non_xmpp_ns && state->depth == 1 && state->normalize_ns) {
441 | state->top_xmlns_attrs = &stream_stream_ns_attr;
442 | }
443 |
444 | if (xmlns_op == OP_REPLACE_XMLNS) {
445 | attrs_term = append_attr(state, attrs_term, dup_to_term(env, "xmlns", 5),
446 | dup_to_term(env, ns, strlen(ns)));
447 | } else if (xmlns_op == OP_REMOVE_PREFIX) {
448 | enif_free(pfx);
449 | }
450 |
451 | if (!ns && state->normalize_ns)
452 | PARSER_MEM_ASSERT(ns = dup_buf("", 0));
453 |
454 | xmlel_stack_t *xmlel = enif_alloc(sizeof(xmlel_stack_t));
455 | PARSER_MEM_ASSERT(xmlel);
456 |
457 | xmlel->next = state->elements_stack;
458 | xmlel->attrs = attrs_term;
459 | xmlel->namespace_str = ns;
460 | xmlel->children = NULL;
461 | xmlel->redefined_top_prefix = redefined_top_prefix;
462 |
463 | state->elements_stack = xmlel;
464 |
465 | if (state->pid && state->depth == 1) {
466 | if (state->use_maps) {
467 | ERL_NIF_TERM map = enif_make_new_map(env);
468 | enif_make_map_put(env, map, enif_make_atom(env, "__struct__"),
469 | enif_make_atom(env, "Elixir.FastXML.StreamStart"), &map);
470 | enif_make_map_put(env, map, enif_make_atom(env, "name"),
471 | enif_make_binary(env, &name_bin), &map);
472 | enif_make_map_put(env, map, enif_make_atom(env, "attrs"),
473 | attrs_term, &map);
474 | send_event(state, map);
475 | } else {
476 | send_event(state,
477 | enif_make_tuple3(env,
478 | enif_make_atom(env, "xmlstreamstart"),
479 | enif_make_binary(env, &name_bin),
480 | attrs_term));
481 | }
482 | } else {
483 | xmlel->name = enif_make_binary(env, &name_bin);
484 | }
485 | }
486 |
487 | void erlXML_CharacterDataHandler(state_t *state, const XML_Char *s, int len)
488 | {
489 | ErlNifEnv *env = state->send_env;
490 |
491 | if (state->error)
492 | return;
493 |
494 | if (state->depth == 0)
495 | return;
496 |
497 | if (state->pid && state->depth == 1) {
498 | ErlNifBinary cdata;
499 | PARSER_MEM_ASSERT(enif_alloc_binary(len, &cdata));
500 | memcpy(cdata.data, s, len);
501 | send_all_state_event(state,
502 | enif_make_tuple2(env,
503 | enif_make_atom(env, "xmlstreamcdata"),
504 | enif_make_binary(env, &cdata)));
505 | return;
506 | }
507 |
508 | children_list_t *children = state->elements_stack->children;
509 |
510 | if (children && children->is_cdata) {
511 | int old_size = children->cdata.size;
512 | PARSER_MEM_ASSERT(enif_realloc_binary(&children->cdata, old_size + len));
513 | memcpy(children->cdata.data+old_size, s, len);
514 | } else {
515 | children = enif_alloc(sizeof(children_list_t));
516 | PARSER_MEM_ASSERT(children);
517 | if (!enif_alloc_binary(len, &children->cdata)) {
518 | enif_free(children);
519 | PARSER_MEM_ASSERT(0);
520 | }
521 | children->is_cdata = 1;
522 | memcpy(children->cdata.data, s, len);
523 | children->next = state->elements_stack->children;
524 | state->elements_stack->children = children;
525 | }
526 |
527 | return;
528 | }
529 |
530 | ERL_NIF_TERM
531 | make_xmlel_children_list(state_t *state, children_list_t *list) {
532 | ErlNifEnv *env = state->send_env;
533 | ERL_NIF_TERM children_list = enif_make_list(env, 0);
534 |
535 | while (list) {
536 | if (list->is_cdata) {
537 | ERL_NIF_TERM data;
538 | if (state->use_maps) {
539 | data = enif_make_binary(env, &list->cdata);
540 | } else {
541 | data = enif_make_tuple2(env,
542 | enif_make_atom(env, "xmlcdata"),
543 | enif_make_binary(env, &list->cdata));
544 | }
545 | children_list = enif_make_list_cell(env, data, children_list);
546 | } else
547 | children_list = enif_make_list_cell(env, list->term, children_list);
548 |
549 | children_list_t *old_head = list;
550 | list = list->next;
551 |
552 | enif_free(old_head);
553 | }
554 |
555 | return children_list;
556 | }
557 |
558 | void erlXML_EndElementHandler(state_t *state, const XML_Char *name)
559 | {
560 | ErlNifEnv *env = state->send_env;
561 |
562 | if (state->error)
563 | return;
564 |
565 | state->depth--;
566 |
567 | if (state->pid && state->depth == 0) {
568 | ErlNifBinary name_bin;
569 |
570 | PARSER_MEM_ASSERT(encode_name(state, name, &name_bin, NULL, NULL, 0));
571 |
572 | if (state->use_maps) {
573 | ERL_NIF_TERM map = enif_make_new_map(env);
574 | enif_make_map_put(env, map, enif_make_atom(env, "__struct__"),
575 | enif_make_atom(env, "Elixir.FastXML.StreamEnd"), &map);
576 | enif_make_map_put(env, map, enif_make_atom(env, "name"),
577 | enif_make_binary(env, &name_bin), &map);
578 | send_event(state, map);
579 | } else {
580 | send_event(state,
581 | enif_make_tuple2(env,
582 | enif_make_atom(env, "xmlstreamend"),
583 | enif_make_binary(env, &name_bin)));
584 | }
585 | return;
586 | }
587 |
588 | ERL_NIF_TERM xmlel_term;
589 |
590 | if (state->use_maps) {
591 | xmlel_term = enif_make_new_map(env);
592 | enif_make_map_put(env, xmlel_term, enif_make_atom(env, "__struct__"),
593 | enif_make_atom(env, "Elixir.FastXML.El"), &xmlel_term);
594 | enif_make_map_put(env, xmlel_term, enif_make_atom(env, "name"), state->elements_stack->name, &xmlel_term);
595 | enif_make_map_put(env, xmlel_term, enif_make_atom(env, "attrs"), state->elements_stack->attrs, &xmlel_term);
596 | enif_make_map_put(env, xmlel_term, enif_make_atom(env, "children"),
597 | make_xmlel_children_list(state, state->elements_stack->children), &xmlel_term);
598 | } else {
599 | xmlel_term = enif_make_tuple4(env, enif_make_atom(env, "xmlel"),
600 | state->elements_stack->name,
601 | state->elements_stack->attrs,
602 | make_xmlel_children_list(state, state->elements_stack->children));
603 | }
604 |
605 | if (!state->pid || state->depth > 1) {
606 | children_list_t *el;
607 | xmlel_stack_t *cur_el = state->elements_stack;
608 |
609 | PARSER_MEM_ASSERT(el = enif_alloc(sizeof(children_list_t)));
610 |
611 | state->elements_stack = state->elements_stack->next;
612 |
613 | el->is_cdata = 0;
614 | el->term = xmlel_term;
615 | el->next = state->elements_stack->children;
616 | state->elements_stack->children = el;
617 | if (cur_el->namespace_str != state->elements_stack->namespace_str)
618 | enif_free(cur_el->namespace_str);
619 | enif_free(cur_el);
620 | } else {
621 | xmlel_stack_t *cur_el = state->elements_stack;
622 | state->elements_stack = cur_el->next;
623 | if (!state->elements_stack || cur_el->namespace_str != state->elements_stack->namespace_str)
624 | enif_free(cur_el->namespace_str);
625 | enif_free(cur_el);
626 | if (state->use_maps) {
627 | enif_make_map_put(env, xmlel_term, enif_make_atom(env, "__struct__"),
628 | enif_make_atom(env, "Elixir.FastXML.El"), &xmlel_term);
629 | send_event(state, xmlel_term);
630 | } else {
631 | send_event(state,
632 | enif_make_tuple2(state->send_env,
633 | enif_make_atom(state->send_env, "xmlstreamelement"),
634 | xmlel_term));
635 | }
636 | }
637 |
638 | return;
639 | }
640 |
641 | void erlXML_StartNamespaceDeclHandler(state_t *state,
642 | const XML_Char *prefix,
643 | const XML_Char *uri)
644 | {
645 | /* From the expat documentation:
646 | "For a default namespace declaration (xmlns='...'),
647 | the prefix will be null ...
648 | ... The URI will be null for the case where
649 | the default namespace is being unset."
650 |
651 | FIXME: I'm not quite sure what all that means */
652 | if (uri == NULL)
653 | return;
654 |
655 | if (state->error)
656 | return;
657 |
658 | attrs_list_t *c = enif_alloc(sizeof(attrs_list_t));
659 | PARSER_MEM_ASSERT(c);
660 |
661 | if (prefix) {
662 | size_t len = strlen(prefix);
663 |
664 | if (!enif_alloc_binary(len + 6, &c->name)) {
665 | enif_free(c);
666 | PARSER_MEM_ASSERT(0);
667 | }
668 | memcpy(c->name.data, "xmlns:", 6);
669 | memcpy(c->name.data + 6, prefix, len);
670 | } else {
671 | if (!enif_alloc_binary(5, &c->name)) {
672 | enif_free(c);
673 | PARSER_MEM_ASSERT(0);
674 | }
675 | memcpy(c->name.data, "xmlns", 5);
676 | };
677 |
678 | size_t len = strlen(uri);
679 | if (!enif_alloc_binary(len, &c->value)) {
680 | enif_release_binary(&c->name);
681 | enif_free(c);
682 | PARSER_MEM_ASSERT(0);
683 | }
684 |
685 | memcpy(c->value.data, uri, len);
686 |
687 | c->next = state->xmlns_attrs;
688 | state->xmlns_attrs = c;
689 |
690 | return;
691 | }
692 |
693 | /*
694 | * Prevent entity expansion attacks (CVE-2013-1664) by refusing
695 | * to process any XML that contains a DTD.
696 | */
697 | void erlXML_StartDoctypeDeclHandler(state_t *state,
698 | const XML_Char *doctypeName,
699 | const XML_Char *doctypeSysid,
700 | const XML_Char *doctypePubid,
701 | int hasInternalSubset)
702 | {
703 | XML_StopParser(state->parser, PARSING_NOT_RESUMABLE);
704 | return;
705 | }
706 |
707 | /*
708 | * Prevent entity expansion attacks (CVE-2013-1664) by having an explicit
709 | * default handler. According to the documentation,
710 | *
711 | * "Setting the handler with this call has the side effect of turning off
712 | * expansion of references to internally defined general entities. Instead
713 | * these references are passed to the default handler."
714 | */
715 | void erlXML_DefaultHandler(state_t *state, const XML_Char *s, int len)
716 | {
717 | return;
718 | }
719 |
720 | static void free_parser_allocated_structs(state_t *state) {
721 | while (state->xmlns_attrs) {
722 | attrs_list_t *c = state->xmlns_attrs;
723 | state->xmlns_attrs = c->next;
724 |
725 | enif_release_binary(&c->name);
726 | enif_release_binary(&c->value);
727 | enif_free(c);
728 | }
729 | while (state->elements_stack) {
730 | xmlel_stack_t *c = state->elements_stack;
731 | while (c->children) {
732 | children_list_t *cc = c->children;
733 | if (cc->is_cdata)
734 | enif_release_binary(&cc->cdata);
735 | c->children = cc->next;
736 | enif_free(cc);
737 | }
738 | if (!c->next || c->namespace_str != c->next->namespace_str)
739 | enif_free(c->namespace_str);
740 | state->elements_stack = c->next;
741 | enif_free(c);
742 | }
743 | if (state->top_xmlns_attrs != &stream_stream_ns_attr)
744 | while (state->top_xmlns_attrs) {
745 | attrs_list_t *c = state->top_xmlns_attrs;
746 | state->top_xmlns_attrs = c->next;
747 | enif_release_binary(&c->name);
748 | enif_release_binary(&c->value);
749 | enif_free(c);
750 | }
751 | }
752 |
753 | static void destroy_parser_state(ErlNifEnv *env, void *data)
754 | {
755 | state_t *state = (state_t *) data;
756 | if (state) {
757 | if (state->parser) XML_ParserFree(state->parser);
758 | if (state->pid) enif_free(state->pid);
759 | if (state->send_env) enif_free_env(state->send_env);
760 |
761 | free_parser_allocated_structs(state);
762 |
763 | memset(state, 0, sizeof(state_t));
764 | }
765 | }
766 |
767 | static void setup_parser(state_t *state)
768 | {
769 | XML_SetUserData(state->parser, state);
770 | XML_SetStartElementHandler(state->parser,
771 | (XML_StartElementHandler) erlXML_StartElementHandler);
772 | XML_SetEndElementHandler(state->parser,
773 | (XML_EndElementHandler) erlXML_EndElementHandler);
774 | XML_SetCharacterDataHandler(state->parser,
775 | (XML_CharacterDataHandler) erlXML_CharacterDataHandler);
776 | XML_SetStartNamespaceDeclHandler(state->parser,
777 | (XML_StartNamespaceDeclHandler)
778 | erlXML_StartNamespaceDeclHandler);
779 | XML_SetStartDoctypeDeclHandler(state->parser,
780 | (XML_StartDoctypeDeclHandler)
781 | erlXML_StartDoctypeDeclHandler);
782 | XML_SetReturnNSTriplet(state->parser, 1);
783 | XML_SetDefaultHandler(state->parser, (XML_DefaultHandler) erlXML_DefaultHandler);
784 | #if XML_MAJOR_VERSION > 2 || (XML_MAJOR_VERSION == 2 && XML_MINOR_VERSION >= 6)
785 | XML_SetReparseDeferralEnabled(state->parser, XML_FALSE);
786 | #endif
787 | }
788 |
789 | static state_t *init_parser_state(ErlNifPid *pid)
790 | {
791 | state_t *state = enif_alloc_resource(parser_state_t, sizeof(state_t));
792 | ASSERT(state);
793 | memset(state, 0, sizeof(state_t));
794 | if (pid) {
795 | state->send_env = enif_alloc_env();
796 | ASSERT(state->send_env);
797 | state->pid = enif_alloc(sizeof(ErlNifPid));
798 | ASSERT(state->pid);
799 | memcpy(state->pid, pid, sizeof(ErlNifPid));
800 | }
801 | state->parser = XML_ParserCreate_MM("UTF-8", &ms, "\n");
802 | setup_parser(state);
803 | return state;
804 | }
805 |
806 | static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
807 | {
808 | ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
809 | parser_state_t = enif_open_resource_type(env, NULL, "parser_state_t",
810 | destroy_parser_state,
811 | flags, NULL);
812 |
813 | return 0;
814 | }
815 |
816 | static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info)
817 | {
818 | return load(env, priv, load_info);
819 | }
820 |
821 | static ERL_NIF_TERM make_parse_error(ErlNifEnv *env, XML_Parser parser)
822 | {
823 | enum XML_Error errcode = XML_GetErrorCode(parser);
824 | const char *errstring;
825 |
826 | if (errcode == XML_ERROR_EXTERNAL_ENTITY_HANDLING)
827 | errstring = "DTDs are not allowed";
828 | else
829 | errstring = XML_ErrorString(errcode);
830 |
831 | return enif_make_tuple2(env, enif_make_uint(env, errcode),
832 | str2bin(env, errstring));
833 | }
834 |
835 | static ERL_NIF_TERM reset_nif(ErlNifEnv* env, int argc,
836 | const ERL_NIF_TERM argv[])
837 | {
838 | state_t *state = NULL;
839 |
840 | if (argc != 1)
841 | return enif_make_badarg(env);
842 |
843 | if (!enif_get_resource(env, argv[0], parser_state_t, (void *) &state))
844 | return enif_make_badarg(env);
845 |
846 | ASSERT(XML_ParserReset(state->parser, "UTF-8"));
847 | setup_parser(state);
848 |
849 | free_parser_allocated_structs(state);
850 |
851 | enif_clear_env(state->send_env);
852 |
853 | state->size = 0;
854 | state->depth = 0;
855 | state->error = NULL;
856 |
857 | return argv[0];
858 | }
859 |
860 | static ERL_NIF_TERM parse_element_nif(ErlNifEnv* env, int argc,
861 | const ERL_NIF_TERM argv[])
862 | {
863 | ERL_NIF_TERM el;
864 | ErlNifBinary bin;
865 | int use_maps = 0;
866 |
867 | if (argc != 1 && argc != 2)
868 | return enif_make_badarg(env);
869 |
870 | if (argc == 2) {
871 | if (!enif_is_list(env, argv[1]))
872 | return enif_make_badarg(env);
873 |
874 | ERL_NIF_TERM head, tail = argv[1];
875 | while (enif_get_list_cell(env, tail, &head, &tail)) {
876 | char buf[16];
877 | if (enif_get_atom(env, head, buf, sizeof(buf), ERL_NIF_LATIN1)) {
878 | if (strcmp("use_maps", buf) == 0)
879 | use_maps = 1;
880 | }
881 | }
882 | }
883 |
884 | if (!enif_inspect_binary(env, argv[0], &bin))
885 | return enif_make_badarg(env);
886 |
887 | state_t *state = init_parser_state(NULL);
888 | if (!state)
889 | return enif_make_badarg(env);
890 |
891 | state->send_env = env;
892 | state->use_maps = use_maps;
893 |
894 | xmlel_stack_t *xmlel = enif_alloc(sizeof(xmlel_stack_t));
895 | if (!xmlel) {
896 | enif_release_resource(state);
897 | return enif_make_badarg(env);
898 | }
899 |
900 | memset(xmlel, 0, sizeof(xmlel_stack_t));
901 |
902 | xmlel->next = state->elements_stack;
903 | xmlel->children = NULL;
904 |
905 | state->elements_stack = xmlel;
906 |
907 | int res = XML_Parse(state->parser, (char *)bin.data, bin.size, 1);
908 | if (res == XML_STATUS_OK && state->elements_stack->children &&
909 | !state->elements_stack->children->is_cdata)
910 | el = state->elements_stack->children->term;
911 | else if (state->error)
912 | el = enif_make_tuple2(env, enif_make_atom(env, "error"),
913 | enif_make_atom(env, state->error));
914 | else
915 | el = enif_make_tuple2(env, enif_make_atom(env, "error"),
916 | make_parse_error(env, state->parser));
917 |
918 | state->send_env = NULL;
919 |
920 | enif_release_resource(state);
921 |
922 | return el;
923 | }
924 |
925 | static void send_error(state_t *state, ERL_NIF_TERM msg) {
926 | ErlNifEnv *env = state->send_env;
927 |
928 | if (state->use_maps) {
929 | ERL_NIF_TERM map = enif_make_new_map(env);
930 | enif_make_map_put(env, map, enif_make_atom(env, "__struct__"),
931 | enif_make_atom(env, "Elixir.FastXML.StreamError"), &map);
932 | enif_make_map_put(env, map, enif_make_atom(env, "desc"),
933 | msg, &map);
934 |
935 | send_event(state, map);
936 | } else {
937 | send_event(state,
938 | enif_make_tuple2(env,
939 | enif_make_atom(env, "xmlstreamerror"),
940 | msg));
941 | }
942 | }
943 |
944 | static ERL_NIF_TERM parse_nif(ErlNifEnv* env, int argc,
945 | const ERL_NIF_TERM argv[])
946 | {
947 | state_t *state = NULL;
948 | ErlNifBinary bin;
949 |
950 | if (argc != 2)
951 | return enif_make_badarg(env);
952 |
953 | if (!enif_get_resource(env, argv[0], parser_state_t, (void *) &state))
954 | return enif_make_badarg(env);
955 |
956 | if (!enif_inspect_binary(env, argv[1], &bin))
957 | return enif_make_badarg(env);
958 |
959 | if (!state->parser || !state->pid || !state->send_env)
960 | return enif_make_badarg(env);
961 |
962 | state->size += bin.size;
963 | state->env = env;
964 |
965 | if (state->size >= state->max_size) {
966 | size_t size = state->size;
967 | send_error(state, str2bin(state->send_env, "XML stanza is too big"));
968 | /* Don't let send_event() to set size to zero */
969 | state->size = size;
970 | } else {
971 | int res = XML_Parse(state->parser, (char *)bin.data, bin.size, 0);
972 | if (!res)
973 | send_error(state, state->error ?
974 | str2bin(state->send_env, state->error) :
975 | make_parse_error(state->send_env, state->parser));
976 | }
977 |
978 | return argv[0];
979 | }
980 |
981 | static ERL_NIF_TERM change_callback_pid_nif(ErlNifEnv* env, int argc,
982 | const ERL_NIF_TERM argv[])
983 | {
984 | state_t *state = NULL;
985 | ErlNifPid pid;
986 |
987 | if (argc != 2)
988 | return enif_make_badarg(env);
989 |
990 | if (!enif_get_resource(env, argv[0], parser_state_t, (void *) &state))
991 | return enif_make_badarg(env);
992 |
993 | if (!state->parser || !state->pid || !state->send_env)
994 | return enif_make_badarg(env);
995 |
996 | if (!enif_get_local_pid(env, argv[1], &pid))
997 | return enif_make_badarg(env);
998 |
999 | memcpy(state->pid, &pid, sizeof(ErlNifPid));
1000 |
1001 | return enif_make_resource(env, state);
1002 | }
1003 |
1004 | static ERL_NIF_TERM close_nif(ErlNifEnv* env, int argc,
1005 | const ERL_NIF_TERM argv[])
1006 | {
1007 | state_t *state = NULL;
1008 |
1009 | if (argc != 1)
1010 | return enif_make_badarg(env);
1011 |
1012 | if (!enif_get_resource(env, argv[0], parser_state_t, (void *) &state))
1013 | return enif_make_badarg(env);
1014 |
1015 | if (!state->parser || !state->pid)
1016 | return enif_make_badarg(env);
1017 |
1018 | destroy_parser_state(env, state);
1019 |
1020 | return enif_make_atom(env, "true");
1021 | }
1022 |
1023 | static ERL_NIF_TERM new_nif(ErlNifEnv* env, int argc,
1024 | const ERL_NIF_TERM argv[])
1025 | {
1026 | int gen_srv = 1;
1027 | int use_maps = 0;
1028 |
1029 | if (argc != 2 && argc != 3)
1030 | return enif_make_badarg(env);
1031 |
1032 | if (argc == 3) {
1033 | if (!enif_is_list(env, argv[2]))
1034 | return enif_make_badarg(env);
1035 | ERL_NIF_TERM head, tail = argv[2];
1036 | while (enif_get_list_cell(env, tail, &head, &tail)) {
1037 | char buf[16];
1038 | if (enif_get_atom(env, head, buf, sizeof(buf), ERL_NIF_LATIN1)) {
1039 | if (strcmp("no_gen_server", buf) == 0)
1040 | gen_srv = 0;
1041 | else if (strcmp("use_maps", buf) == 0)
1042 | use_maps = 1;
1043 | }
1044 | }
1045 | }
1046 |
1047 | ErlNifPid pid;
1048 | if (!enif_get_local_pid(env, argv[0], &pid))
1049 | return enif_make_badarg(env);
1050 |
1051 | state_t *state = init_parser_state(&pid);
1052 | if (!state)
1053 | return enif_make_badarg(env);
1054 |
1055 | state->normalize_ns = 1;
1056 | state->use_maps = use_maps;
1057 | state->gen_server = gen_srv;
1058 |
1059 | ERL_NIF_TERM result = enif_make_resource(env, state);
1060 | enif_release_resource(state);
1061 |
1062 | ErlNifUInt64 max_size;
1063 | if (enif_get_uint64(env, argv[1], &max_size))
1064 | state->max_size = (size_t) max_size;
1065 | else if (!enif_compare(argv[1], enif_make_atom(env, "infinity")))
1066 | state->max_size = (size_t) - 1;
1067 | else
1068 | return enif_make_badarg(env);
1069 |
1070 | return result;
1071 | }
1072 |
1073 | static ErlNifFunc nif_funcs[] =
1074 | {
1075 | {"new", 2, new_nif},
1076 | {"new", 3, new_nif},
1077 | {"parse", 2, parse_nif},
1078 | {"parse_element", 1, parse_element_nif},
1079 | {"parse_element", 2, parse_element_nif},
1080 | {"reset", 1, reset_nif},
1081 | {"close", 1, close_nif},
1082 | {"change_callback_pid", 2, change_callback_pid_nif}
1083 | };
1084 |
1085 | ERL_NIF_INIT(fxml_stream, nif_funcs, load, NULL, upgrade, NULL)
1086 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | # -*- Autoconf -*-
2 | # Process this file with autoconf to produce a configure script.
3 |
4 | AC_PREREQ(2.53)
5 | AC_PACKAGE_VERSION(1.1.21)
6 | AC_INIT(fast_xml, 1.1.21, [], [])
7 |
8 | # Checks for programs.
9 | AC_PROG_CC
10 | AC_PROG_MAKE_SET
11 |
12 | if test "x$GCC" = "xyes"; then
13 | CFLAGS="$CFLAGS -Wall"
14 | fi
15 |
16 | # Checks for typedefs, structures, and compiler characteristics.
17 | AC_C_CONST
18 |
19 | # Checks for library functions.
20 | AC_FUNC_MALLOC
21 | AC_HEADER_STDC
22 |
23 | # Checks Erlang runtime and compiler
24 | AC_ERLANG_NEED_ERL
25 | AC_ERLANG_NEED_ERLC
26 |
27 | # Checks and sets ERLANG_ROOT_DIR and ERLANG_LIB_DIR variable
28 | # AC_ERLANG_SUBST_ROOT_DIR
29 | # AC_ERLANG_SUBST_LIB_DIR
30 |
31 | AC_CHECK_HEADERS([expat.h], [], [
32 | AC_MSG_ERROR([libexpat header file expat.h was not found])])
33 |
34 | AC_SEARCH_LIBS([XML_ParserCreate], [expat], [], [
35 | AC_MSG_ERROR([libexpat library was not found])])
36 |
37 | AC_ARG_ENABLE(gcov,
38 | [AC_HELP_STRING([--enable-gcov], [compile with gcov enabled (default: no)])],
39 | [case "${enableval}" in
40 | yes) gcov=true ;;
41 | no) gcov=false ;;
42 | *) AC_MSG_ERROR(bad value ${enableval} for --enable-gcov) ;;
43 | esac],[gcov=false])
44 |
45 |
46 | AC_SUBST(gcov)
47 |
48 | AC_CONFIG_FILES([vars.config])
49 | AC_OUTPUT
50 |
--------------------------------------------------------------------------------
/include/fxml.hrl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml.hrl
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : XML utils
5 | %%% Created : 1 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -record(xmlel,
25 | {
26 | name = <<"">> :: binary(),
27 | attrs = [] :: [attr()],
28 | children = [] :: [xmlel() | cdata()]
29 | }).
30 |
31 | -type(cdata() :: {xmlcdata, CData::binary()}).
32 |
33 | -type(attr() :: {Name::binary(), Value::binary()}).
34 |
35 | -type(xmlel() :: #xmlel{}).
36 |
--------------------------------------------------------------------------------
/include/fxml_gen.hrl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : xml_gen.hrl
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : XML utils
5 | %%% Created : 1 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -define(AST_MARK, ast__mark_).
25 | -define(AST(Code), ?AST_MARK(Code)).
26 |
27 | -record(attr, {name,
28 | label,
29 | required = false,
30 | default = '$unset',
31 | always_encode = false,
32 | dec,
33 | enc}).
34 |
35 | -record(cdata, {required = false,
36 | label = '$cdata',
37 | default = '$unset',
38 | dec,
39 | enc}).
40 |
41 | -record(elem, {name,
42 | module,
43 | xmlns = <<"">>,
44 | cdata = #cdata{},
45 | ignore_els = false,
46 | result,
47 | attrs = [],
48 | refs = []}).
49 |
50 | -record(ref, {name,
51 | label,
52 | min = 0,
53 | max = infinity,
54 | default}).
55 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : rebar.config
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : Rebar build script. Compliant with rebar and rebar3.
5 | %%% Created : 8 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
8 | %%%
9 | %%% Licensed under the Apache License, Version 2.0 (the "License");
10 | %%% you may not use this file except in compliance with the License.
11 | %%% You may obtain a copy of the License at
12 | %%%
13 | %%% http://www.apache.org/licenses/LICENSE-2.0
14 | %%%
15 | %%% Unless required by applicable law or agreed to in writing, software
16 | %%% distributed under the License is distributed on an "AS IS" BASIS,
17 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | %%% See the License for the specific language governing permissions and
19 | %%% limitations under the License.
20 | %%%
21 | %%%----------------------------------------------------------------------
22 |
23 | {erl_opts, [debug_info,
24 | nowarn_export_all,
25 | {platform_define, "^(1|20|21|22|23|24|25|26|27)", 'OTP_RELEASE_MINOR_28'},
26 | {platform_define, "^(1|20|21|22|23|24)", 'OTP_RELEASE_MINOR_25'},
27 | {platform_define, "^(1|20|21|22|23)", 'OLD_DIALYZER_NO_FILENAMES'},
28 | {platform_define, "^(15|16|17|18)", 'HAVE_REMOTE_TYPES'},
29 | {platform_define, "^(15|16|17)", 'HAVE_FROM_FORM0'},
30 | {platform_define, "^(1)", 'OLD_STRING'},
31 | {platform_define, "^(15|16|17|18|19\\.0|19\\.1|19\\.2)", 'USE_DICT'}]}.
32 | {port_env, [{"CFLAGS", "$CFLAGS"}, {"LDFLAGS", "$LDFLAGS"},
33 | {"ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lei"},
34 | {"freebsd", "CFLAGS", "$CFLAGS -I/usr/local/include"},
35 | {"freebsd","LDFLAGS", "$LDFLAGS -L/usr/local/lib"}]}.
36 |
37 | {port_specs, [{"priv/lib/fxml.so", ["c_src/fxml.c"]},
38 | {"priv/lib/fxml_stream.so", ["c_src/fxml_stream.c"]}]}.
39 |
40 | {deps, [{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.26"}}}]}.
41 |
42 | {clean_files, ["c_src/fxml.gcda", "c_src/fxml.gcno", "c_src/fxml_stream.gcda", "c_src/fxml_stream.gcno"]}.
43 |
44 | {cover_enabled, true}.
45 | {cover_export_enabled, true}.
46 | {coveralls_coverdata , "_build/test/cover/eunit.coverdata"}.
47 | {coveralls_service_name , "github"}.
48 |
49 | {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}.
50 |
51 | {dialyzer, [{plt_extra_apps, [compiler, dialyzer, syntax_tools]}]}.
52 |
53 | {plugins, []}.
54 |
55 | %% Local Variables:
56 | %% mode: erlang
57 | %% End:
58 | %% vim: set filetype=erlang tabstop=8:
59 |
--------------------------------------------------------------------------------
/rebar.config.script:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : rebar.config.script
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : Rebar build script. Compliant with rebar and rebar3.
5 | %%% Created : 8 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
8 | %%%
9 | %%% Licensed under the Apache License, Version 2.0 (the "License");
10 | %%% you may not use this file except in compliance with the License.
11 | %%% You may obtain a copy of the License at
12 | %%%
13 | %%% http://www.apache.org/licenses/LICENSE-2.0
14 | %%%
15 | %%% Unless required by applicable law or agreed to in writing, software
16 | %%% distributed under the License is distributed on an "AS IS" BASIS,
17 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | %%% See the License for the specific language governing permissions and
19 | %%% limitations under the License.
20 | %%%
21 | %%%----------------------------------------------------------------------
22 |
23 | Cfg = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])) of
24 | {ok, Terms} ->
25 | Terms;
26 | _Err ->
27 | []
28 | end ++ [{cflags, "-g -O2 -Wall"}, {ldflags, "-lexpat"}, {with_gcov, "false"}],
29 | {cflags, CfgCFlags} = lists:keyfind(cflags, 1, Cfg),
30 | {ldflags, CfgLDFlags} = lists:keyfind(ldflags, 1, Cfg),
31 | {with_gcov, CfgWithGCov} = lists:keyfind(with_gcov, 1, Cfg),
32 |
33 | IsRebar3 = case application:get_key(rebar, vsn) of
34 | {ok, VSN} ->
35 | [VSN1 | _] = string:tokens(VSN, "-"),
36 | [Maj|_] = string:tokens(VSN1, "."),
37 | (list_to_integer(Maj) >= 3);
38 | undefined ->
39 | lists:keymember(mix, 1, application:loaded_applications())
40 | end,
41 |
42 | ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
43 | {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of
44 | {value, {_, V1}, V2} -> {V1, V2};
45 | false -> {if Tail == [] -> Default; true -> [] end, Cfg}
46 | end,
47 | case Tail of
48 | [] ->
49 | [{Key, Op(OldVal)} | PartCfg];
50 | _ ->
51 | [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg]
52 | end
53 | end,
54 | ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op,
55 | Default) end,
56 |
57 | ModCfgS = fun(Cfg, Keys, Val) -> ModCfg0(ModCfg0, Cfg, Keys, fun(_V) ->
58 | Val end, "") end,
59 |
60 |
61 | FilterConfig = fun(F, Cfg, [{Path, true, ModFun, Default} | Tail]) ->
62 | F(F, ModCfg0(ModCfg0, Cfg, Path, ModFun, Default), Tail);
63 | (F, Cfg, [_ | Tail]) ->
64 | F(F, Cfg, Tail);
65 | (F, Cfg, []) ->
66 | Cfg
67 | end,
68 |
69 | AppendStr = fun(Append) ->
70 | fun("") ->
71 | Append;
72 | (Val) ->
73 | Val ++ " " ++ Append
74 | end
75 | end,
76 | AppendList = fun(Append) ->
77 | fun(Val) ->
78 | Val ++ Append
79 | end
80 | end,
81 |
82 | % Convert our rich deps syntax to rebar2 format:
83 | % https://github.com/rebar/rebar/wiki/Dependency-management
84 | Rebar2DepsFilter =
85 | fun(DepsList) ->
86 | lists:map(fun({DepName, _HexVersion, Source}) ->
87 | {DepName, ".*", Source}
88 | end, DepsList)
89 | end,
90 |
91 | % Convert our rich deps syntax to rebar3 version definition format:
92 | % https://rebar3.org/docs/configuration/dependencies/#dependency-version-handling
93 | % https://hexdocs.pm/elixir/Version.html
94 | Rebar3DepsFilter =
95 | fun(DepsList) ->
96 | lists:map(fun({DepName, HexVersion, {git, _, {tag, GitVersion}} = Source}) ->
97 | case HexVersion == ".*" of
98 | true ->
99 | {DepName, GitVersion};
100 | false ->
101 | {DepName, HexVersion}
102 | end;
103 | ({DepName, _HexVersion, Source}) ->
104 | {DepName, ".*", Source}
105 | end, DepsList)
106 | end,
107 |
108 | GlobalDepsFilter = fun(Deps) ->
109 | DepNames = lists:map(fun({DepName, _, _}) -> DepName;
110 | ({DepName, _}) -> DepName
111 | end, Deps),
112 | lists:filtermap(fun(Dep) ->
113 | case code:lib_dir(Dep) of
114 | {error, _} ->
115 | {true,"Unable to locate dep '"++atom_to_list(Dep)++"' in system deps."};
116 | _ ->
117 | false
118 | end
119 | end, DepNames)
120 | end,
121 |
122 | GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of
123 | {"true", Token} when is_list(Token) ->
124 | CONFIG1 = [{coveralls_repo_token, Token},
125 | {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")},
126 | {coveralls_commit_sha, os:getenv("GITHUB_SHA")},
127 | {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}],
128 | case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request"
129 | andalso string:tokens(os:getenv("GITHUB_REF"), "/") of
130 | [_, "pull", PRNO, _] ->
131 | [{coveralls_service_pull_request, PRNO} | CONFIG1];
132 | _ ->
133 | CONFIG1
134 | end;
135 | _ ->
136 | []
137 | end,
138 |
139 | Rules = [
140 | {[port_env, "CFLAGS"], true,
141 | AppendStr(CfgCFlags), "$CFLAGS"},
142 | {[port_env, "LDFLAGS"], true,
143 | AppendStr(CfgLDFlags), "$LDFLAGS"},
144 | {[post_hooks], (not IsRebar3) and (CfgWithGCov == "true"),
145 | AppendList([{eunit, "gcov -o c_src fxml fxml_stream"},
146 | {eunit, "mv *.gcov .eunit/"}]), []},
147 | {[post_hooks], IsRebar3 and (CfgWithGCov == "true"),
148 | AppendList([{eunit, "gcov -o c_src fxml fxml_stream"},
149 | {eunit, "mv *.gcov _build/test/cover/"}]), []},
150 | {[port_env, "LDFLAGS"], CfgWithGCov == "true",
151 | AppendStr("--coverage"), ""},
152 | {[port_env, "CFLAGS"], CfgWithGCov == "true",
153 | AppendStr("--coverage"), ""},
154 | {[deps], (not IsRebar3),
155 | Rebar2DepsFilter, []},
156 | {[deps], IsRebar3,
157 | Rebar3DepsFilter, []},
158 | {[plugins], IsRebar3,
159 | AppendList([{pc, "~> 1.15.0"}]), []},
160 | {[provider_hooks], IsRebar3,
161 | AppendList([{pre, [
162 | {compile, {pc, compile}},
163 | {clean, {pc, clean}}
164 | ]}]), []},
165 | {[plugins], os:getenv("COVERALLS") == "true",
166 | AppendList([{coveralls, {git,
167 | "https://github.com/processone/coveralls-erl.git",
168 | {branch, "addjsonfile"}}} ]), []},
169 | {[deps], os:getenv("USE_GLOBAL_DEPS") /= false,
170 | GlobalDepsFilter, []}
171 | ],
172 |
173 |
174 | Config = FilterConfig(FilterConfig, CONFIG, Rules) ++ GithubConfig,
175 |
176 | %io:format("Rules:~n~p~n~nCONFIG:~n~p~n~nConfig:~n~p~n", [Rules, CONFIG, Config]),
177 |
178 | Config.
179 |
180 | %% Local Variables:
181 | %% mode: erlang
182 | %% End:
183 | %% vim: set filetype=erlang tabstop=8:
184 |
--------------------------------------------------------------------------------
/spec/README.md:
--------------------------------------------------------------------------------
1 | This file is used to generate `src/fxmlrpc_codec.erl`.
2 |
3 | From repository root, you can regenerate the file with:
4 |
5 | $ make spec
6 |
--------------------------------------------------------------------------------
/spec/fxmlrpc_codec.spec:
--------------------------------------------------------------------------------
1 | -xml(methodCall,
2 | #elem{name = <<"methodCall">>,
3 | xmlns = <<"xmlrpc">>,
4 | result = {call, '$name', '$params'},
5 | refs = [#ref{name = methodName,
6 | label = '$name',
7 | min = 1, max = 1},
8 | #ref{name = params,
9 | label = '$params',
10 | default = [],
11 | min = 0, max = 1}]}).
12 |
13 | -xml(methodResponse,
14 | #elem{name = <<"methodResponse">>,
15 | xmlns = <<"xmlrpc">>,
16 | result = {response, '$payload'},
17 | refs = [#ref{name = fault, label = '$payload', default = [],
18 | min = 0, max = 1},
19 | #ref{name = params, label = '$payload', default = [],
20 | min = 0, max = 1}]}).
21 |
22 | -xml(fault,
23 | #elem{name = <<"fault">>,
24 | xmlns = <<"xmlrpc">>,
25 | result = {fault, '$value'},
26 | refs = [#ref{name = value, label = '$value', min = 1, max = 1}]}).
27 |
28 | -xml(methodName,
29 | #elem{name = <<"methodName">>,
30 | xmlns = <<"xmlrpc">>,
31 | result = '$cdata',
32 | cdata = #cdata{required = true,
33 | dec = {erlang, binary_to_atom, [utf8]},
34 | enc = {erlang, atom_to_binary, [utf8]}}}).
35 |
36 | -xml(params,
37 | #elem{name = <<"params">>,
38 | xmlns = <<"xmlrpc">>,
39 | result = '$params',
40 | refs = [#ref{name = param, label = '$params'}]}).
41 |
42 | -xml(param,
43 | #elem{name = <<"param">>,
44 | xmlns = <<"xmlrpc">>,
45 | result = '$value',
46 | refs = [#ref{name = value, label = '$value',
47 | min = 1, max = 1}]}).
48 |
49 | -xml(value,
50 | #elem{name = <<"value">>,
51 | xmlns = <<"xmlrpc">>,
52 | result = {'$val', '$cdata'},
53 | refs = [#ref{name = i4, label = '$val', min = 0, max = 1},
54 | #ref{name = int, label = '$val', min = 0, max = 1},
55 | #ref{name = string, label = '$val', min = 0, max = 1},
56 | #ref{name = double, label = '$val', min = 0, max = 1},
57 | #ref{name = base64, label = '$val', min = 0, max = 1},
58 | #ref{name = boolean, label = '$val', min = 0, max = 1},
59 | #ref{name = array, label = '$val', min = 0, max = 1},
60 | #ref{name = nil, label = '$val', min = 0, max = 1},
61 | #ref{name = struct, label = '$val', min = 0, max = 1},
62 | #ref{name = dateTime, label = '$val', min = 0, max = 1}],
63 | cdata = #cdata{default = undefined}}).
64 |
65 | -xml(i4,
66 | #elem{name = <<"i4">>,
67 | xmlns = <<"xmlrpc">>,
68 | result = {i4, '$cdata'},
69 | cdata = #cdata{required = true,
70 | dec = {erlang, binary_to_integer, []},
71 | enc = {erlang, integer_to_binary, []}}}).
72 |
73 | -xml(int,
74 | #elem{name = <<"int">>,
75 | xmlns = <<"xmlrpc">>,
76 | result = {int, '$cdata'},
77 | cdata = #cdata{required = true,
78 | dec = {erlang, binary_to_integer, []},
79 | enc = {erlang, integer_to_binary, []}}}).
80 |
81 | -xml(string,
82 | #elem{name = <<"string">>,
83 | xmlns = <<"xmlrpc">>,
84 | cdata = #cdata{default = <<"">>},
85 | result = {string, '$cdata'}}).
86 |
87 | -xml(double,
88 | #elem{name = <<"double">>,
89 | xmlns = <<"xmlrpc">>,
90 | result = {double, '$cdata'},
91 | cdata = #cdata{required = true,
92 | dec = {erlang, binary_to_float, []},
93 | enc = {erlang, float_to_binary, []}}}).
94 |
95 | -xml(base64,
96 | #elem{name = <<"base64">>,
97 | xmlns = <<"xmlrpc">>,
98 | result = {base64, '$cdata'},
99 | cdata = #cdata{required = true}}).
100 |
101 | -xml(dateTime,
102 | #elem{name = <<"dateTime.iso8601">>,
103 | xmlns = <<"xmlrpc">>,
104 | result = {date, '$cdata'},
105 | cdata = #cdata{required = true}}).
106 |
107 | -xml(boolean,
108 | #elem{name = <<"boolean">>,
109 | xmlns = <<"xmlrpc">>,
110 | result = {boolean, '$cdata'},
111 | cdata = #cdata{required = true,
112 | dec = {dec_bool, []},
113 | enc = {enc_bool, []}}}).
114 |
115 | -xml(array,
116 | #elem{name = <<"array">>,
117 | xmlns = <<"xmlrpc">>,
118 | result = {array, '$data'},
119 | refs = [#ref{name = data, label = '$data', min = 1, max = 1}]}).
120 |
121 | -xml(data,
122 | #elem{name = <<"data">>,
123 | xmlns = <<"xmlrpc">>,
124 | result = '$v',
125 | refs = [#ref{name = value, label = '$v'}]}).
126 |
127 | -xml(nil,
128 | #elem{name = <<"nil">>,
129 | xmlns = <<"xmlrpc">>,
130 | result = nil}).
131 |
132 | -xml(struct,
133 | #elem{name = <<"struct">>,
134 | xmlns = <<"xmlrpc">>,
135 | result = {struct, '$members'},
136 | refs = [#ref{name = member, label = '$members'}]}).
137 |
138 | -xml(member,
139 | #elem{name = <<"member">>,
140 | xmlns = <<"xmlrpc">>,
141 | result = {'$name', '$value'},
142 | refs = [#ref{name = name, label = '$name', min = 1, max = 1},
143 | #ref{name = value, label = '$value', min = 1, max = 1}]}).
144 |
145 | -xml(name,
146 | #elem{name = <<"name">>,
147 | xmlns = <<"xmlrpc">>,
148 | result = '$cdata',
149 | cdata = #cdata{required = true,
150 | dec = {erlang, binary_to_atom, [utf8]},
151 | enc = {erlang, atom_to_binary, [utf8]}}}).
152 |
153 | dec_bool(<<"false">>) -> false;
154 | dec_bool(<<"0">>) -> false;
155 | dec_bool(<<"true">>) -> true;
156 | dec_bool(<<"1">>) -> true.
157 |
158 | enc_bool(false) -> <<"0">>;
159 | enc_bool(true) -> <<"1">>.
160 |
161 | %% Local Variables:
162 | %% mode: erlang
163 | %% End:
164 | %% vim: set filetype=erlang tabstop=8:
165 |
--------------------------------------------------------------------------------
/src/fast_xml.app.src:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fast_xml.app.src
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : Application package description
5 | %%% Created : 4 Apr 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | {application, fast_xml,
25 | [{description, "Fast Expat-based Erlang / Elixir XML parsing library"},
26 | {vsn, "1.1.56"},
27 | {modules, []},
28 | {registered, []},
29 | {applications, [kernel, stdlib, p1_utils]},
30 | {mod, {fast_xml,[]}},
31 |
32 | %% hex.pm packaging:
33 | {files, ["include/", "lib/", "src/",
34 | "c_src/fxml.c", "c_src/fxml_stream.c",
35 | "configure", "vars.config.in",
36 | "rebar.config", "rebar.config.script",
37 | "README.md", "LICENSE.txt"]},
38 | {licenses, ["Apache 2.0"]},
39 | {links, [{"Github", "https://github.com/processone/fast_xml"}]}]}.
40 |
41 | %% Local Variables:
42 | %% mode: erlang
43 | %% End:
44 | %% vim: set filetype=erlang tabstop=8:
45 |
--------------------------------------------------------------------------------
/src/fast_xml.erl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml_app.erl
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : Fast XML application
5 | %%% Created : 1 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -module(fast_xml).
25 |
26 | -behaviour(application).
27 |
28 | %% Application callbacks
29 | -export([start/2, stop/1]).
30 |
31 | %%%===================================================================
32 | %%% Application callbacks
33 | %%%===================================================================
34 |
35 | %%--------------------------------------------------------------------
36 | %% @private
37 | %% @doc
38 | %% This function is called whenever an application is started using
39 | %% application:start/[1,2], and should start the processes of the
40 | %% application. If the application is structured according to the OTP
41 | %% design principles as a supervision tree, this means starting the
42 | %% top supervisor of the tree.
43 | %%
44 | %% @spec start(StartType, StartArgs) -> {ok, Pid} |
45 | %% {ok, Pid, State} |
46 | %% {error, Reason}
47 | %% StartType = normal | {takeover, Node} | {failover, Node}
48 | %% StartArgs = term()
49 | %% @end
50 | %%--------------------------------------------------------------------
51 | start(_StartType, _StartArgs) ->
52 | fxml_sup:start_link().
53 |
54 | %%--------------------------------------------------------------------
55 | %% @private
56 | %% @doc
57 | %% This function is called whenever an application has stopped. It
58 | %% is intended to be the opposite of Module:start/2 and should do
59 | %% any necessary cleaning up. The return value is ignored.
60 | %%
61 | %% @spec stop(State) -> void()
62 | %% @end
63 | %%--------------------------------------------------------------------
64 | stop(_State) ->
65 | ok.
66 |
67 | %%%===================================================================
68 | %%% Internal functions
69 | %%%===================================================================
70 |
--------------------------------------------------------------------------------
/src/fxml.erl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml.erl
3 | %%% Author : Alexey Shchepin
4 | %%% Purpose : XML utils for parsing, matching, processing XML
5 | %%% Created : 20 Nov 2002 by Alexey Shchepin
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -module(fxml).
25 |
26 | -author('alexey@process-one.net').
27 |
28 | -compile(no_native).
29 |
30 | -on_load(init/0).
31 |
32 | -export([element_to_binary/1, element_to_header/1,
33 | crypt/1, remove_cdata/1,
34 | remove_subtags/3, get_cdata/1, get_tag_cdata/1,
35 | get_attr/2, get_attr_s/2, get_tag_attr/2,
36 | get_tag_attr_s/2, get_subtag/2, get_subtags/2, get_subtag_cdata/2,
37 | get_subtag_with_xmlns/3, get_subtags_with_xmlns/3,
38 | append_subtags/2, get_path_s/2,
39 | replace_tag_attr/3, replace_subtag/2, to_xmlel/1]).
40 |
41 | -export([load_nif/0, load_nif/1]).
42 |
43 | -include("fxml.hrl").
44 | -export_type([xmlel/0]).
45 |
46 | init() ->
47 | ok = load_nif().
48 |
49 | %% Replace element_to_binary/1 with NIF
50 | load_nif() ->
51 | SOPath = p1_nif_utils:get_so_path(?MODULE, [fast_xml], "fxml"),
52 | load_nif(SOPath).
53 |
54 | load_nif(SOPath) ->
55 | case catch erlang:load_nif(SOPath, 0) of
56 | ok -> ok;
57 | Err -> error_logger:warning_msg("unable to load fxml NIF: ~p~n", [Err]),
58 | {error, unable_to_load_nif}
59 | end.
60 |
61 | %%
62 | -spec element_to_binary(El :: xmlel() | cdata()) -> binary().
63 |
64 | element_to_binary(_El) ->
65 | erlang:nif_error(nif_not_loaded).
66 |
67 | -spec element_to_header(El :: xmlel()) -> binary().
68 |
69 | element_to_header(_El) ->
70 | erlang:nif_error(nif_not_loaded).
71 |
72 | crypt(S) ->
73 | << <<(case C of
74 | $& -> <<"&">>;
75 | $< -> <<"<">>;
76 | $> -> <<">">>;
77 | $" -> <<""">>;
78 | $' -> <<"'">>;
79 | _ -> <>
80 | end)/binary>>
81 | || <> <= S >>.
82 |
83 | %%
84 | -spec remove_cdata_p(El :: xmlel() | cdata()) -> boolean().
85 |
86 | remove_cdata_p(#xmlel{}) -> true;
87 | remove_cdata_p(_) -> false.
88 |
89 | %%
90 | -spec remove_cdata(L :: [xmlel() | cdata()]) -> [xmlel()].
91 |
92 | remove_cdata(L) -> [E || E <- L, remove_cdata_p(E)].
93 |
94 | %% This function is intended to remove subtags based on a name and an
95 | %% attribute, usually an xmlns attribute for a specific XMPP
96 | %% extension.
97 | -spec remove_subtags(Xmlel :: xmlel(), Name :: binary(),
98 | Attr :: attr()) -> Xmlel :: xmlel().
99 |
100 | remove_subtags(#xmlel{name = TagName, attrs = TagAttrs, children = Els},
101 | Name, Attr) ->
102 | #xmlel{name = TagName, attrs = TagAttrs,
103 | children = remove_subtags1(Els, [], Name, Attr)}.
104 |
105 | %%
106 | -spec remove_subtags1(Els :: [xmlel() | cdata()], NewEls :: [xmlel()],
107 | Name :: binary(), Attr :: attr()) ->
108 | NewEls :: [xmlel()].
109 |
110 | remove_subtags1([], NewEls, _Name, _Attr) ->
111 | lists:reverse(NewEls);
112 | remove_subtags1([El | Els], NewEls, Name,
113 | {AttrName, AttrValue} = Attr) ->
114 | case El of
115 | #xmlel{name = Name, attrs = Attrs} ->
116 | case get_attr(AttrName, Attrs) of
117 | false ->
118 | remove_subtags1(Els, [El | NewEls], Name, Attr);
119 | {value, AttrValue} ->
120 | remove_subtags1(Els, NewEls, Name, Attr);
121 | _ -> remove_subtags1(Els, [El | NewEls], Name, Attr)
122 | end;
123 | _ -> remove_subtags1(Els, [El | NewEls], Name, Attr)
124 | end.
125 |
126 | -spec get_cdata(L :: [xmlel() | cdata()]) -> binary().
127 |
128 | get_cdata(L) ->
129 | (iolist_to_binary(get_cdata(L, <<"">>))).
130 |
131 | -spec get_cdata(L :: [xmlel() | cdata()],
132 | S :: binary() | iolist()) ->
133 | binary() | iolist().
134 |
135 | get_cdata([{xmlcdata, CData} | L], S) ->
136 | get_cdata(L, [S, CData]);
137 | get_cdata([_ | L], S) -> get_cdata(L, S);
138 | get_cdata([], S) -> S.
139 |
140 | -spec get_tag_cdata(Xmlel :: xmlel()) -> binary().
141 |
142 | get_tag_cdata(#xmlel{children = Els}) -> get_cdata(Els).
143 |
144 | %%
145 | -spec get_attr(AttrName :: binary(), Attrs :: [attr()]) ->
146 | {value, binary()} | false.
147 |
148 | get_attr(AttrName, Attrs) ->
149 | case lists:keysearch(AttrName, 1, Attrs) of
150 | {value, {_, Val}} -> {value, Val};
151 | _ -> false
152 | end.
153 |
154 | %%
155 | -spec get_attr_s(AttrName :: binary(), Attrs :: [attr()]) ->
156 | Val :: binary().
157 |
158 | get_attr_s(AttrName, Attrs) ->
159 | case lists:keysearch(AttrName, 1, Attrs) of
160 | {value, {_, Val}} -> Val;
161 | _ -> <<"">>
162 | end.
163 |
164 | %%
165 | -spec get_tag_attr(AttrName :: binary(), Xmlel :: xmlel()) ->
166 | {value, binary()} | false.
167 |
168 | get_tag_attr(AttrName, #xmlel{attrs = Attrs}) ->
169 | get_attr(AttrName, Attrs).
170 |
171 | %%
172 | -spec get_tag_attr_s(AttrName :: binary(), Xmlel :: xmlel()) -> binary().
173 |
174 | get_tag_attr_s(AttrName, #xmlel{attrs = Attrs}) ->
175 | get_attr_s(AttrName, Attrs).
176 |
177 | %%
178 | -spec get_subtag(Xmlel :: xmlel(), Name :: binary()) -> xmlel() | false.
179 |
180 | get_subtag(#xmlel{children = Els}, Name) ->
181 | get_subtag1(Els, Name).
182 |
183 | %%
184 | -spec get_subtag1(Els :: [xmlel() | cdata()], Name :: binary()) ->
185 | xmlel() | false.
186 |
187 | get_subtag1( [El | Els], Name) ->
188 | case El of
189 | #xmlel{name = Name} -> El;
190 | _ -> get_subtag1(Els, Name)
191 | end;
192 | get_subtag1([], _) -> false.
193 |
194 | -spec get_subtags(Xmlel :: xmlel(), Name :: binary()) -> [xmlel()].
195 |
196 | get_subtags(#xmlel{children = Els}, Name) ->
197 | get_subtags1(Els, Name, []).
198 |
199 | get_subtags1([], _Name, Acc) ->
200 | lists:reverse(Acc);
201 | get_subtags1([El | Els], Name, Acc) ->
202 | case El of
203 | #xmlel{name = Name} -> get_subtags1(Els, Name, [El|Acc]);
204 | _ -> get_subtags1(Els, Name, Acc)
205 | end.
206 |
207 | %%
208 | -spec get_subtag_with_xmlns(Xmlel :: xmlel(), Name :: binary(),
209 | XMLNS :: binary()) -> xmlel() | false.
210 |
211 | get_subtag_with_xmlns(#xmlel{children = Els}, Name, XMLNS) ->
212 | get_subtag_with_xmlns1(Els, Name, XMLNS).
213 |
214 | %%
215 | -spec get_subtag_with_xmlns1(Els :: [xmlel() | cdata()], Name :: binary(),
216 | XMLNS :: binary()) -> xmlel() | false.
217 |
218 | get_subtag_with_xmlns1([El | Els], Name, XMLNS) ->
219 | case El of
220 | #xmlel{name = Name, attrs = Attrs} ->
221 | case get_attr(<<"xmlns">>, Attrs) of
222 | {value, XMLNS} ->
223 | El;
224 | _ ->
225 | get_subtag_with_xmlns1(Els, Name, XMLNS)
226 | end;
227 | _ ->
228 | get_subtag_with_xmlns1(Els, Name, XMLNS)
229 | end;
230 | get_subtag_with_xmlns1([], _, _) ->
231 | false.
232 |
233 | -spec get_subtags_with_xmlns(Xmlel :: xmlel(), Name :: binary(),
234 | XMLNS :: binary()) -> [xmlel()].
235 |
236 | get_subtags_with_xmlns(#xmlel{children = Els}, Name, XMLNS) ->
237 | get_subtags_with_xmlns1(Els, Name, XMLNS, []).
238 |
239 | get_subtags_with_xmlns1([], _Name, _XMLNS, Acc) ->
240 | lists:reverse(Acc);
241 | get_subtags_with_xmlns1([El | Els], Name, XMLNS, Acc) ->
242 | case El of
243 | #xmlel{name = Name, attrs = Attrs} ->
244 | case get_attr(<<"xmlns">>, Attrs) of
245 | {value, XMLNS} ->
246 | get_subtags_with_xmlns1(Els, Name, XMLNS, [El|Acc]);
247 | _ ->
248 | get_subtags_with_xmlns1(Els, Name, XMLNS, Acc)
249 | end;
250 | _ ->
251 | get_subtags_with_xmlns1(Els, Name, XMLNS, Acc)
252 | end.
253 |
254 | %%
255 | -spec get_subtag_cdata(Tag :: xmlel(), Name :: binary()) -> binary().
256 |
257 | get_subtag_cdata(Tag, Name) ->
258 | case get_subtag(Tag, Name) of
259 | false -> <<"">>;
260 | Subtag -> get_tag_cdata(Subtag)
261 | end.
262 |
263 | %%
264 | -spec append_subtags(Xmlel :: xmlel(), SubTags2 :: [xmlel() | cdata()]) ->
265 | Xmlel :: xmlel().
266 |
267 | append_subtags(#xmlel{name = Name, attrs = Attrs, children = SubTags1}, SubTags2) ->
268 | #xmlel{name = Name, attrs = Attrs, children = SubTags1 ++ SubTags2}.
269 |
270 | %%
271 | -spec get_path_s(El :: xmlel(), Path :: [{elem, Name::binary()} |
272 | {attr, Name::binary()} |cdata]) ->
273 | xmlel() | binary().
274 |
275 | get_path_s(El, []) -> El;
276 | get_path_s(El, [{elem, Name} | Path]) ->
277 | case get_subtag(El, Name) of
278 | false -> <<"">>;
279 | SubEl -> get_path_s(SubEl, Path)
280 | end;
281 | get_path_s(El, [{attr, Name}]) ->
282 | get_tag_attr_s(Name, El);
283 | get_path_s(El, [cdata]) -> get_tag_cdata(El).
284 |
285 | %%
286 | -spec replace_tag_attr(Name :: binary(), Value :: binary(),
287 | Xmlel :: xmlel()) ->
288 | Xmlel :: #xmlel{ name :: binary(),
289 | attrs :: [attr(),...],
290 | children :: [xmlel() | cdata()] }.
291 |
292 | replace_tag_attr(Name, Value, Xmlel) ->
293 | Xmlel#xmlel{
294 | attrs = [{Name, Value} | lists:keydelete(Name, 1, Xmlel#xmlel.attrs)]
295 | }.
296 |
297 |
298 | -spec replace_subtag(Tag :: xmlel(), Xmlel :: xmlel()) ->
299 | Xmlel :: #xmlel{ name :: binary(),
300 | attrs :: [attr(),...],
301 | children :: [xmlel() | cdata()] }.
302 |
303 | replace_subtag(#xmlel{name = Name} = Tag, Xmlel) ->
304 | Xmlel#xmlel{
305 | children = [Tag | lists:keydelete(Name, #xmlel.name, Xmlel#xmlel.children)]
306 | }.
307 |
308 | to_xmlel({_, Name, Attrs, Els}) ->
309 | #xmlel{name = iolist_to_binary(Name),
310 | attrs = [{iolist_to_binary(K), iolist_to_binary(V)}
311 | || {K, V} <- Attrs],
312 | children = [to_xmlel(El) || El <- Els]};
313 | to_xmlel({xmlcdata, CData}) ->
314 | {xmlcdata, iolist_to_binary(CData)}.
315 |
--------------------------------------------------------------------------------
/src/fxml_gen_pt.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %%% @author Evgeny Khramtsov
3 | %%% @copyright (C) 2016-2025, Evgeny Khramtsov
4 | %%% @doc
5 | %%%
6 | %%% @end
7 | %%% Created : 27 May 2016 by Evgeny Khramtsov
8 | %%%-------------------------------------------------------------------
9 | -module(fxml_gen_pt).
10 |
11 | %% API
12 | -export([parse_transform/2]).
13 |
14 | -include("fxml_gen.hrl").
15 |
16 | %%%===================================================================
17 | %%% API
18 | %%%===================================================================
19 | parse_transform(Forms, _Options) ->
20 | Result = lists:map(
21 | fun(Form) ->
22 | try
23 | Form2 = erl_syntax_lib:map(
24 | fun(Node) ->
25 | transform(Node)
26 | end, Form),
27 | Form3 = erl_syntax:revert(Form2),
28 | %%io:format("~s~n", [erl_prettypr:format(Form3)]),
29 | Form3
30 | catch
31 | throw:{error, Line, Error} ->
32 | {error, {Line, erl_parse, Error}}
33 | end
34 | end, Forms),
35 | Result.
36 |
37 | %%%===================================================================
38 | %%% Internal functions
39 | %%%===================================================================
40 | transform(Form) ->
41 | case erl_syntax:type(Form) of
42 | application ->
43 | case erl_syntax_lib:analyze_application(Form) of
44 | {?AST_MARK, 1} ->
45 | [Tree] = erl_syntax:application_arguments(Form),
46 | NewTree = erl_syntax_lib:map(
47 | fun(Node) ->
48 | transform_variable(Node)
49 | end, erl_syntax:abstract(Tree)),
50 | erl_syntax:revert(NewTree);
51 | _ ->
52 | Form
53 | end;
54 | _ ->
55 | Form
56 | end.
57 |
58 | transform_variable(Form) ->
59 | try
60 | Term = erl_syntax:concrete(Form),
61 | atom = erl_syntax:type(Term),
62 | "?" ++ Var = erl_syntax:atom_name(Term),
63 | {ok, Tokens, _} = erl_scan:string(Var ++ "."),
64 | {ok, [NewForm]} = erl_parse:parse_exprs(Tokens),
65 | NewForm
66 | catch _:_ ->
67 | Form
68 | end.
69 |
--------------------------------------------------------------------------------
/src/fxml_stream.erl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml_stream.erl
3 | %%% Author : Alexey Shchepin
4 | %%% Purpose : Parse XML streams
5 | %%% Created : 17 Nov 2002 by Alexey Shchepin
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -module(fxml_stream).
25 |
26 | -author('alexey@process-one.net').
27 |
28 | -compile(no_native).
29 |
30 | -on_load(init/0).
31 |
32 | -export([new/1, new/2, new/3, parse/2, close/1, reset/1,
33 | change_callback_pid/2, parse_element/1, parse_element/2]).
34 |
35 | -export([load_nif/0, load_nif/1]).
36 |
37 | -include("fxml.hrl").
38 |
39 | -record(xml_stream_state,
40 | {callback_pid = self() :: pid(),
41 | port :: port(),
42 | stack = [] :: stack(),
43 | size = 0 :: non_neg_integer(),
44 | maxsize = infinity :: non_neg_integer() | infinity}).
45 |
46 | -type xml_stream_el() :: {xmlstreamraw, binary()} |
47 | {xmlstreamcdata, binary()} |
48 | {xmlstreamelement, xmlel()} |
49 | {xmlstreamend, binary()} |
50 | {xmlstreamstart, binary(), [attr()]} |
51 | {xmlstreamerror, binary()}.
52 |
53 | -type xml_stream_state() :: #xml_stream_state{}.
54 | -type stack() :: [xmlel()].
55 |
56 | -export_type([xml_stream_state/0, xml_stream_el/0]).
57 |
58 | init() ->
59 | ok = load_nif().
60 |
61 | load_nif() ->
62 | SOPath = p1_nif_utils:get_so_path(?MODULE, [fast_xml], "fxml_stream"),
63 | load_nif(SOPath).
64 |
65 | load_nif(SOPath) ->
66 | case erlang:load_nif(SOPath, 0) of
67 | ok ->
68 | ok;
69 | {error, {Reason, Txt}} ->
70 | error_logger:error_msg("failed to load NIF ~s: ~s",
71 | [SOPath, Txt]),
72 | {error, Reason}
73 | end.
74 |
75 | -spec new(pid()) -> xml_stream_state().
76 |
77 | new(CallbackPid) ->
78 | new(CallbackPid, infinity).
79 |
80 | -spec new(pid(), non_neg_integer() | infinity) -> xml_stream_state().
81 |
82 | new(_CallbackPid, _MaxSize) ->
83 | erlang:nif_error(nif_not_loaded).
84 |
85 | -spec new(pid(), non_neg_integer() | infinity, list()) -> xml_stream_state().
86 |
87 | new(_CallbackPid, _MaxSize, _Options) ->
88 | erlang:nif_error(nif_not_loaded).
89 |
90 | -spec reset(xml_stream_state()) -> xml_stream_state().
91 |
92 | reset(_State) ->
93 | erlang:nif_error(nif_not_loaded).
94 |
95 | -spec change_callback_pid(xml_stream_state(), pid()) -> xml_stream_state().
96 |
97 | change_callback_pid(_State, _CallbackPid) ->
98 | erlang:nif_error(nif_not_loaded).
99 |
100 | -spec parse(xml_stream_state(), binary()) -> xml_stream_state().
101 |
102 | parse(_State, _Data) ->
103 | erlang:nif_error(nif_not_loaded).
104 |
105 | -spec close(xml_stream_state()) -> true.
106 |
107 | close(_State) ->
108 | erlang:nif_error(nif_not_loaded).
109 |
110 | -spec parse_element(binary()) -> xmlel() |
111 | {error, atom()} |
112 | {error, {integer(), binary()}}.
113 |
114 | parse_element(_Str) ->
115 | erlang:nif_error(nif_not_loaded).
116 |
117 | -spec parse_element(binary(), [use_maps]) -> xmlel() |
118 | {error, atom()} |
119 | {error, {integer(), binary()}}.
120 | parse_element(_Str, _Options) ->
121 | erlang:nif_error(nif_not_loaded).
122 |
--------------------------------------------------------------------------------
/src/fxml_sup.erl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml_sup.erl
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : XML supervisor
5 | %%% Created : 1 May 2013 by Evgeniy Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -module(fxml_sup).
25 |
26 | -behaviour(supervisor).
27 |
28 | %% API
29 | -export([start_link/0]).
30 |
31 | %% Supervisor callbacks
32 | -export([init/1]).
33 |
34 | -define(SERVER, ?MODULE).
35 |
36 | %%%===================================================================
37 | %%% API functions
38 | %%%===================================================================
39 |
40 | %%--------------------------------------------------------------------
41 | %% @doc
42 | %% Starts the supervisor
43 | %%
44 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
45 | %% @end
46 | %%--------------------------------------------------------------------
47 | start_link() ->
48 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
49 |
50 | %%%===================================================================
51 | %%% Supervisor callbacks
52 | %%%===================================================================
53 |
54 | %%--------------------------------------------------------------------
55 | %% @private
56 | %% @doc
57 | %% Whenever a supervisor is started using supervisor:start_link/[2,3],
58 | %% this function is called by the new process to find out about
59 | %% restart strategy, maximum restart frequency and child
60 | %% specifications.
61 | %%
62 | %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} |
63 | %% ignore |
64 | %% {error, Reason}
65 | %% @end
66 | %%--------------------------------------------------------------------
67 | init([]) ->
68 | {ok, {{one_for_one, 10, 1}, []}}.
69 |
70 | %%%===================================================================
71 | %%% Internal functions
72 | %%%===================================================================
73 |
--------------------------------------------------------------------------------
/src/fxmlrpc.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %%% File : fxmlrpc.erl
3 | %%% Author : Evgeny Khramtsov
4 | %%% Purpose : XMLRPC encoder/decoder
5 | %%% Created : 3 Oct 2014 by Evgeny Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%-------------------------------------------------------------------
23 | -module(fxmlrpc).
24 |
25 | %% API
26 | -export([decode/1, encode/1]).
27 |
28 | -include("fxml.hrl").
29 | -define(NS_XMLRPC, <<"xmlrpc">>).
30 |
31 | -type value() :: number() | binary() | boolean() | nil |
32 | {base64, binary()} |
33 | {date, binary()} |
34 | {array, [{atom(), value()}]} |
35 | {struct, [value()]}.
36 |
37 | -type fault() :: {fault, integer(), binary()}.
38 | -type call() :: {call, atom(), [value()]}.
39 | -type response() :: {response, [value()] | fault()}.
40 |
41 | %%%===================================================================
42 | %%% API
43 | %%%===================================================================
44 | -spec decode(xmlel()) -> {ok, call()} | {ok, response()} | {error, any()}.
45 |
46 | decode(El) ->
47 | try fxmlrpc_codec:decode(El, ?NS_XMLRPC, []) of
48 | {call, Name, Params} ->
49 | {ok, {call, Name, [decode_param(Param) || Param <- Params]}};
50 | {response, Params} when is_list(Params) ->
51 | {ok, {response, [decode_param(Param) || Param <- Params]}};
52 | {response, {fault, {{struct, Struct}, _}}} ->
53 | case proplists:get_value(faultCode, Struct) of
54 | {{Tag, Code}, _} when Tag == int; Tag == i4 ->
55 | case proplists:get_value(faultString, Struct) of
56 | {{string, String}, _} ->
57 | {ok, {response, {fault, Code, String}}};
58 | {undefined, undefined} ->
59 | {ok, {response, {fault, Code, <<"">>}}};
60 | {undefined, String} when is_binary(String) ->
61 | {ok, {response, {fault, Code, String}}};
62 | _ ->
63 | {error, {bad_struct, Struct}}
64 | end;
65 | _ ->
66 | {error, {bad_struct, Struct}}
67 | end;
68 | Other ->
69 | {error, {unexpected_element, Other}}
70 | catch error:{fxmlrpc_codec, Reason} ->
71 | {error, Reason}
72 | end.
73 |
74 | -spec encode(call() | response()) -> xmlel().
75 |
76 | encode({call, Name, Params}) ->
77 | fxmlrpc_codec:encode(
78 | {call, Name, [encode_param(Param) || Param <- Params]},
79 | ?NS_XMLRPC);
80 | encode({response, Params}) when is_list(Params) ->
81 | fxmlrpc_codec:encode(
82 | {response, [encode_param(Param) || Param <- Params]},
83 | ?NS_XMLRPC);
84 | encode({response, {fault, Code, String}}) ->
85 | fxmlrpc_codec:encode(
86 | {response, {fault, {{struct, [{faultCode, {{int, Code}, undefined}},
87 | {faultString, {{string, String}, undefined}}]},
88 | undefined}}},
89 | ?NS_XMLRPC).
90 |
91 | %%%===================================================================
92 | %%% Internal functions
93 | %%%===================================================================
94 | decode_param({undefined, B}) when is_binary(B) ->
95 | B;
96 | decode_param({undefined, undefined}) ->
97 | <<"">>;
98 | decode_param({{int, Int}, _}) ->
99 | Int;
100 | decode_param({{i4, Int}, _}) ->
101 | Int;
102 | decode_param({{boolean, Bool}, _}) ->
103 | Bool;
104 | decode_param({{string, S}, _}) ->
105 | S;
106 | decode_param({{double, D}, _}) ->
107 | D;
108 | decode_param({{array, L}, _}) ->
109 | {array, [decode_param(E) || E <- L]};
110 | decode_param({{struct, S}, _}) ->
111 | {struct, [{Name, decode_param(Value)} || {Name, Value} <- S]};
112 | decode_param({{base64, B64}, _}) ->
113 | {base64, B64};
114 | decode_param({{date, Date}, _}) ->
115 | {date, Date};
116 | decode_param({nil, _}) ->
117 | nil.
118 |
119 | encode_param(Int) when is_integer(Int) ->
120 | {{int, Int}, undefined};
121 | encode_param(B) when is_boolean(B) ->
122 | {{boolean, B}, undefined};
123 | encode_param(S) when is_binary(S) ->
124 | {{string, S}, undefined};
125 | encode_param(S) when is_list(S) ->
126 | {{string, iolist_to_binary(S)}, undefined};
127 | encode_param(D) when is_float(D) ->
128 | {{double, D}, undefined};
129 | encode_param({array, L}) ->
130 | {{array, [encode_param(E) || E <- L]}, undefined};
131 | encode_param({struct, S}) ->
132 | {{struct, [{Name, encode_param(Value)} || {Name, Value} <- S]}, undefined};
133 | encode_param({base64, B64}) ->
134 | {{base64, B64}, undefined};
135 | encode_param({date, Date}) ->
136 | {{date, Date}, undefined};
137 | encode_param(nil) ->
138 | {nil, undefined}.
139 |
--------------------------------------------------------------------------------
/src/fxmlrpc_codec.erl:
--------------------------------------------------------------------------------
1 | %% Created automatically by XML generator (fxml_gen.erl)
2 | %% Source: fxmlrpc_codec.spec
3 |
4 | -module(fxmlrpc_codec).
5 |
6 | -compile(export_all).
7 |
8 | decode(El) -> decode(El, <<>>, []).
9 |
10 | decode(El, Opts) -> decode(El, <<>>, Opts).
11 |
12 | decode({xmlel, Name, Attrs, _} = El, TopXMLNS, Opts) ->
13 | XMLNS = get_attr(<<"xmlns">>, Attrs, TopXMLNS),
14 | case get_mod(Name, XMLNS) of
15 | undefined when XMLNS == <<>> ->
16 | erlang:error({fxmlrpc_codec,
17 | {missing_tag_xmlns, Name}});
18 | undefined ->
19 | erlang:error({fxmlrpc_codec,
20 | {unknown_tag, Name, XMLNS}});
21 | Mod -> Mod:do_decode(Name, XMLNS, El, Opts)
22 | end.
23 |
24 | encode(El) -> encode(El, <<>>).
25 |
26 | encode({xmlel, _, _, _} = El, _) -> El;
27 | encode({xmlcdata, _} = CData, _) -> CData;
28 | encode(El, TopXMLNS) ->
29 | Mod = get_mod(El), Mod:do_encode(El, TopXMLNS).
30 |
31 | get_name(El) -> Mod = get_mod(El), Mod:do_get_name(El).
32 |
33 | get_ns(El) -> Mod = get_mod(El), Mod:do_get_ns(El).
34 |
35 | is_known_tag({xmlel, Name, Attrs, _}, TopXMLNS) ->
36 | XMLNS = get_attr(<<"xmlns">>, Attrs, TopXMLNS),
37 | get_mod(Name, XMLNS) /= undefined.
38 |
39 | get_els(Term) -> Mod = get_mod(Term), Mod:get_els(Term).
40 |
41 | set_els(Term, Els) ->
42 | Mod = get_mod(Term), Mod:set_els(Term, Els).
43 |
44 | do_decode(<<"name">>, <<"xmlrpc">>, El, Opts) ->
45 | decode_name(<<"xmlrpc">>, Opts, El);
46 | do_decode(<<"member">>, <<"xmlrpc">>, El, Opts) ->
47 | decode_member(<<"xmlrpc">>, Opts, El);
48 | do_decode(<<"struct">>, <<"xmlrpc">>, El, Opts) ->
49 | decode_struct(<<"xmlrpc">>, Opts, El);
50 | do_decode(<<"nil">>, <<"xmlrpc">>, El, Opts) ->
51 | decode_nil(<<"xmlrpc">>, Opts, El);
52 | do_decode(<<"data">>, <<"xmlrpc">>, El, Opts) ->
53 | decode_data(<<"xmlrpc">>, Opts, El);
54 | do_decode(<<"array">>, <<"xmlrpc">>, El, Opts) ->
55 | decode_array(<<"xmlrpc">>, Opts, El);
56 | do_decode(<<"boolean">>, <<"xmlrpc">>, El, Opts) ->
57 | decode_boolean(<<"xmlrpc">>, Opts, El);
58 | do_decode(<<"dateTime.iso8601">>, <<"xmlrpc">>, El,
59 | Opts) ->
60 | decode_dateTime(<<"xmlrpc">>, Opts, El);
61 | do_decode(<<"base64">>, <<"xmlrpc">>, El, Opts) ->
62 | decode_base64(<<"xmlrpc">>, Opts, El);
63 | do_decode(<<"double">>, <<"xmlrpc">>, El, Opts) ->
64 | decode_double(<<"xmlrpc">>, Opts, El);
65 | do_decode(<<"string">>, <<"xmlrpc">>, El, Opts) ->
66 | decode_string(<<"xmlrpc">>, Opts, El);
67 | do_decode(<<"int">>, <<"xmlrpc">>, El, Opts) ->
68 | decode_int(<<"xmlrpc">>, Opts, El);
69 | do_decode(<<"i4">>, <<"xmlrpc">>, El, Opts) ->
70 | decode_i4(<<"xmlrpc">>, Opts, El);
71 | do_decode(<<"value">>, <<"xmlrpc">>, El, Opts) ->
72 | decode_value(<<"xmlrpc">>, Opts, El);
73 | do_decode(<<"param">>, <<"xmlrpc">>, El, Opts) ->
74 | decode_param(<<"xmlrpc">>, Opts, El);
75 | do_decode(<<"params">>, <<"xmlrpc">>, El, Opts) ->
76 | decode_params(<<"xmlrpc">>, Opts, El);
77 | do_decode(<<"methodName">>, <<"xmlrpc">>, El, Opts) ->
78 | decode_methodName(<<"xmlrpc">>, Opts, El);
79 | do_decode(<<"fault">>, <<"xmlrpc">>, El, Opts) ->
80 | decode_fault(<<"xmlrpc">>, Opts, El);
81 | do_decode(<<"methodResponse">>, <<"xmlrpc">>, El,
82 | Opts) ->
83 | decode_methodResponse(<<"xmlrpc">>, Opts, El);
84 | do_decode(<<"methodCall">>, <<"xmlrpc">>, El, Opts) ->
85 | decode_methodCall(<<"xmlrpc">>, Opts, El);
86 | do_decode(Name, <<>>, _, _) ->
87 | erlang:error({fxmlrpc_codec,
88 | {missing_tag_xmlns, Name}});
89 | do_decode(Name, XMLNS, _, _) ->
90 | erlang:error({fxmlrpc_codec,
91 | {unknown_tag, Name, XMLNS}}).
92 |
93 | tags() ->
94 | [{<<"name">>, <<"xmlrpc">>},
95 | {<<"member">>, <<"xmlrpc">>},
96 | {<<"struct">>, <<"xmlrpc">>}, {<<"nil">>, <<"xmlrpc">>},
97 | {<<"data">>, <<"xmlrpc">>}, {<<"array">>, <<"xmlrpc">>},
98 | {<<"boolean">>, <<"xmlrpc">>},
99 | {<<"dateTime.iso8601">>, <<"xmlrpc">>},
100 | {<<"base64">>, <<"xmlrpc">>},
101 | {<<"double">>, <<"xmlrpc">>},
102 | {<<"string">>, <<"xmlrpc">>}, {<<"int">>, <<"xmlrpc">>},
103 | {<<"i4">>, <<"xmlrpc">>}, {<<"value">>, <<"xmlrpc">>},
104 | {<<"param">>, <<"xmlrpc">>},
105 | {<<"params">>, <<"xmlrpc">>},
106 | {<<"methodName">>, <<"xmlrpc">>},
107 | {<<"fault">>, <<"xmlrpc">>},
108 | {<<"methodResponse">>, <<"xmlrpc">>},
109 | {<<"methodCall">>, <<"xmlrpc">>}].
110 |
111 | do_encode({call, _, _} = Methodcall, TopXMLNS) ->
112 | encode_methodCall(Methodcall, TopXMLNS);
113 | do_encode({response, _} = Methodresponse, TopXMLNS) ->
114 | encode_methodResponse(Methodresponse, TopXMLNS);
115 | do_encode({fault, _} = Fault, TopXMLNS) ->
116 | encode_fault(Fault, TopXMLNS);
117 | do_encode({i4, _} = I4, TopXMLNS) ->
118 | encode_i4(I4, TopXMLNS);
119 | do_encode({int, _} = Int, TopXMLNS) ->
120 | encode_int(Int, TopXMLNS);
121 | do_encode({string, _} = String, TopXMLNS) ->
122 | encode_string(String, TopXMLNS);
123 | do_encode({double, _} = Double, TopXMLNS) ->
124 | encode_double(Double, TopXMLNS);
125 | do_encode({base64, _} = Base64, TopXMLNS) ->
126 | encode_base64(Base64, TopXMLNS);
127 | do_encode({date, _} = Datetime_iso8601, TopXMLNS) ->
128 | encode_dateTime(Datetime_iso8601, TopXMLNS);
129 | do_encode({boolean, _} = Boolean, TopXMLNS) ->
130 | encode_boolean(Boolean, TopXMLNS);
131 | do_encode({array, _} = Array, TopXMLNS) ->
132 | encode_array(Array, TopXMLNS);
133 | do_encode({struct, _} = Struct, TopXMLNS) ->
134 | encode_struct(Struct, TopXMLNS).
135 |
136 | do_get_name({array, _}) -> <<"array">>;
137 | do_get_name({base64, _}) -> <<"base64">>;
138 | do_get_name({boolean, _}) -> <<"boolean">>;
139 | do_get_name({call, _, _}) -> <<"methodCall">>;
140 | do_get_name({date, _}) -> <<"dateTime.iso8601">>;
141 | do_get_name({double, _}) -> <<"double">>;
142 | do_get_name({fault, _}) -> <<"fault">>;
143 | do_get_name({i4, _}) -> <<"i4">>;
144 | do_get_name({int, _}) -> <<"int">>;
145 | do_get_name({response, _}) -> <<"methodResponse">>;
146 | do_get_name({string, _}) -> <<"string">>;
147 | do_get_name({struct, _}) -> <<"struct">>.
148 |
149 | do_get_ns({array, _}) -> <<"xmlrpc">>;
150 | do_get_ns({base64, _}) -> <<"xmlrpc">>;
151 | do_get_ns({boolean, _}) -> <<"xmlrpc">>;
152 | do_get_ns({call, _, _}) -> <<"xmlrpc">>;
153 | do_get_ns({date, _}) -> <<"xmlrpc">>;
154 | do_get_ns({double, _}) -> <<"xmlrpc">>;
155 | do_get_ns({fault, _}) -> <<"xmlrpc">>;
156 | do_get_ns({i4, _}) -> <<"xmlrpc">>;
157 | do_get_ns({int, _}) -> <<"xmlrpc">>;
158 | do_get_ns({response, _}) -> <<"xmlrpc">>;
159 | do_get_ns({string, _}) -> <<"xmlrpc">>;
160 | do_get_ns({struct, _}) -> <<"xmlrpc">>.
161 |
162 | register_module(Mod) ->
163 | register_module(Mod, fxmlrpc_codec_external).
164 |
165 | unregister_module(Mod) ->
166 | unregister_module(Mod, fxmlrpc_codec_external).
167 |
168 | format_error({bad_attr_value, Attr, Tag, XMLNS}) ->
169 | <<"Bad value of attribute '", Attr/binary, "' in tag <",
170 | Tag/binary, "/> qualified by namespace '", XMLNS/binary,
171 | "'">>;
172 | format_error({bad_cdata_value, <<>>, Tag, XMLNS}) ->
173 | <<"Bad value of cdata in tag <", Tag/binary,
174 | "/> qualified by namespace '", XMLNS/binary, "'">>;
175 | format_error({missing_tag, Tag, XMLNS}) ->
176 | <<"Missing tag <", Tag/binary,
177 | "/> qualified by namespace '", XMLNS/binary, "'">>;
178 | format_error({missing_attr, Attr, Tag, XMLNS}) ->
179 | <<"Missing attribute '", Attr/binary, "' in tag <",
180 | Tag/binary, "/> qualified by namespace '", XMLNS/binary,
181 | "'">>;
182 | format_error({missing_cdata, <<>>, Tag, XMLNS}) ->
183 | <<"Missing cdata in tag <", Tag/binary,
184 | "/> qualified by namespace '", XMLNS/binary, "'">>;
185 | format_error({unknown_tag, Tag, XMLNS}) ->
186 | <<"Unknown tag <", Tag/binary,
187 | "/> qualified by namespace '", XMLNS/binary, "'">>;
188 | format_error({missing_tag_xmlns, Tag}) ->
189 | <<"Missing namespace for tag <", Tag/binary, "/>">>.
190 |
191 | io_format_error({bad_attr_value, Attr, Tag, XMLNS}) ->
192 | {<<"Bad value of attribute '~s' in tag <~s/> "
193 | "qualified by namespace '~s'">>,
194 | [Attr, Tag, XMLNS]};
195 | io_format_error({bad_cdata_value, <<>>, Tag, XMLNS}) ->
196 | {<<"Bad value of cdata in tag <~s/> qualified "
197 | "by namespace '~s'">>,
198 | [Tag, XMLNS]};
199 | io_format_error({missing_tag, Tag, XMLNS}) ->
200 | {<<"Missing tag <~s/> qualified by namespace "
201 | "'~s'">>,
202 | [Tag, XMLNS]};
203 | io_format_error({missing_attr, Attr, Tag, XMLNS}) ->
204 | {<<"Missing attribute '~s' in tag <~s/> "
205 | "qualified by namespace '~s'">>,
206 | [Attr, Tag, XMLNS]};
207 | io_format_error({missing_cdata, <<>>, Tag, XMLNS}) ->
208 | {<<"Missing cdata in tag <~s/> qualified "
209 | "by namespace '~s'">>,
210 | [Tag, XMLNS]};
211 | io_format_error({unknown_tag, Tag, XMLNS}) ->
212 | {<<"Unknown tag <~s/> qualified by namespace "
213 | "'~s'">>,
214 | [Tag, XMLNS]};
215 | io_format_error({missing_tag_xmlns, Tag}) ->
216 | {<<"Missing namespace for tag <~s/>">>, [Tag]}.
217 |
218 | get_attr(Attr, Attrs, Default) ->
219 | case lists:keyfind(Attr, 1, Attrs) of
220 | {_, Val} -> Val;
221 | false -> Default
222 | end.
223 |
224 | enc_xmlns_attrs(XMLNS, XMLNS) -> [];
225 | enc_xmlns_attrs(XMLNS, _) -> [{<<"xmlns">>, XMLNS}].
226 |
227 | choose_top_xmlns(<<>>, NSList, TopXMLNS) ->
228 | case lists:member(TopXMLNS, NSList) of
229 | true -> TopXMLNS;
230 | false -> hd(NSList)
231 | end;
232 | choose_top_xmlns(XMLNS, _, _) -> XMLNS.
233 |
234 | register_module(Mod, ResolverMod) ->
235 | MD5Sum = try Mod:module_info(md5) of
236 | Val -> Val
237 | catch
238 | error:badarg ->
239 | {ok, {Mod, Val}} = beam_lib:md5(code:which(Mod)), Val
240 | end,
241 | case orddict:find(Mod, ResolverMod:modules()) of
242 | {ok, MD5Sum} -> ok;
243 | _ ->
244 | Mods = orddict:store(Mod, MD5Sum,
245 | ResolverMod:modules()),
246 | recompile_resolver(Mods, ResolverMod)
247 | end.
248 |
249 | unregister_module(Mod, ResolverMod) ->
250 | case orddict:find(Mod, ResolverMod:modules()) of
251 | {ok, _} ->
252 | Mods = orddict:erase(Mod, ResolverMod:modules()),
253 | recompile_resolver(Mods, ResolverMod);
254 | error -> ok
255 | end.
256 |
257 | recompile_resolver(Mods, ResolverMod) ->
258 | Tags = lists:flatmap(fun (M) ->
259 | [{Name, XMLNS, M} || {Name, XMLNS} <- M:tags()]
260 | end,
261 | orddict:fetch_keys(Mods)),
262 | Records = lists:flatmap(fun (M) ->
263 | [{RecName, RecSize, M}
264 | || {RecName, RecSize} <- M:records()]
265 | end,
266 | orddict:fetch_keys(Mods)),
267 | Lookup1 = string:join(lists:map(fun ({RecName, RecSize,
268 | M}) ->
269 | io_lib:format("lookup({~s}) -> '~s'",
270 | [string:join([io_lib:format("'~s'",
271 | [RecName])
272 | | ["_"
273 | || _
274 | <- lists:seq(1,
275 | RecSize)]],
276 | ","),
277 | M])
278 | end,
279 | Records)
280 | ++
281 | ["lookup(Term) -> erlang:error(badarg, "
282 | "[Term])."],
283 | ";" ++ io_lib:nl()),
284 | Lookup2 = string:join(lists:map(fun ({Name, XMLNS,
285 | M}) ->
286 | io_lib:format("lookup(~w, ~w) -> '~s'",
287 | [Name, XMLNS, M])
288 | end,
289 | Tags)
290 | ++ ["lookup(_, _) -> undefined."],
291 | ";" ++ io_lib:nl()),
292 | Modules = io_lib:format("modules() -> [~s].",
293 | [string:join([io_lib:format("{'~s', ~w}", [M, S])
294 | || {M, S} <- Mods],
295 | ",")]),
296 | Module = io_lib:format("-module(~s).", [ResolverMod]),
297 | Compile = "-compile(export_all).",
298 | Forms = lists:map(fun (Expr) ->
299 | {ok, Tokens, _} =
300 | erl_scan:string(lists:flatten(Expr)),
301 | {ok, Form} = erl_parse:parse_form(Tokens),
302 | Form
303 | end,
304 | [Module, Compile, Modules, Lookup1, Lookup2]),
305 | {ok, Code} = case compile:forms(Forms, []) of
306 | {ok, ResolverMod, Bin} -> {ok, Bin};
307 | {ok, ResolverMod, Bin, _Warnings} -> {ok, Bin};
308 | Error -> Error
309 | end,
310 | {module, ResolverMod} = code:load_binary(ResolverMod,
311 | "nofile", Code),
312 | ok.
313 |
314 | dec_bool(<<"false">>) -> false;
315 | dec_bool(<<"0">>) -> false;
316 | dec_bool(<<"true">>) -> true;
317 | dec_bool(<<"1">>) -> true.
318 |
319 | enc_bool(false) -> <<"0">>;
320 | enc_bool(true) -> <<"1">>.
321 |
322 | pp(call, 2) -> [name, params];
323 | pp(response, 1) -> [payload];
324 | pp(fault, 1) -> [value];
325 | pp(i4, 1) -> [cdata];
326 | pp(int, 1) -> [cdata];
327 | pp(string, 1) -> [cdata];
328 | pp(double, 1) -> [cdata];
329 | pp(base64, 1) -> [cdata];
330 | pp(date, 1) -> [cdata];
331 | pp(boolean, 1) -> [cdata];
332 | pp(array, 1) -> [data];
333 | pp(struct, 1) -> [members];
334 | pp(xmlel, 3) -> [name, attrs, children];
335 | pp(Name, Arity) ->
336 | try get_mod(erlang:make_tuple(Arity + 1, undefined,
337 | [{1, Name}]))
338 | of
339 | Mod -> Mod:pp(Name, Arity)
340 | catch
341 | error:badarg -> no
342 | end.
343 |
344 | records() ->
345 | [{call, 2}, {response, 1}, {fault, 1}, {i4, 1},
346 | {int, 1}, {string, 1}, {double, 1}, {base64, 1},
347 | {date, 1}, {boolean, 1}, {array, 1}, {struct, 1}].
348 |
349 | get_mod(<<"int">>, <<"xmlrpc">>) -> fxmlrpc_codec;
350 | get_mod(<<"array">>, <<"xmlrpc">>) -> fxmlrpc_codec;
351 | get_mod(<<"dateTime.iso8601">>, <<"xmlrpc">>) ->
352 | fxmlrpc_codec;
353 | get_mod(<<"methodResponse">>, <<"xmlrpc">>) ->
354 | fxmlrpc_codec;
355 | get_mod(<<"nil">>, <<"xmlrpc">>) -> fxmlrpc_codec;
356 | get_mod(<<"value">>, <<"xmlrpc">>) -> fxmlrpc_codec;
357 | get_mod(<<"methodCall">>, <<"xmlrpc">>) ->
358 | fxmlrpc_codec;
359 | get_mod(<<"string">>, <<"xmlrpc">>) -> fxmlrpc_codec;
360 | get_mod(<<"boolean">>, <<"xmlrpc">>) -> fxmlrpc_codec;
361 | get_mod(<<"name">>, <<"xmlrpc">>) -> fxmlrpc_codec;
362 | get_mod(<<"param">>, <<"xmlrpc">>) -> fxmlrpc_codec;
363 | get_mod(<<"data">>, <<"xmlrpc">>) -> fxmlrpc_codec;
364 | get_mod(<<"i4">>, <<"xmlrpc">>) -> fxmlrpc_codec;
365 | get_mod(<<"base64">>, <<"xmlrpc">>) -> fxmlrpc_codec;
366 | get_mod(<<"member">>, <<"xmlrpc">>) -> fxmlrpc_codec;
367 | get_mod(<<"fault">>, <<"xmlrpc">>) -> fxmlrpc_codec;
368 | get_mod(<<"methodName">>, <<"xmlrpc">>) ->
369 | fxmlrpc_codec;
370 | get_mod(<<"params">>, <<"xmlrpc">>) -> fxmlrpc_codec;
371 | get_mod(<<"double">>, <<"xmlrpc">>) -> fxmlrpc_codec;
372 | get_mod(<<"struct">>, <<"xmlrpc">>) -> fxmlrpc_codec;
373 | get_mod(Name, XMLNS) ->
374 | fxmlrpc_codec_external:lookup(Name, XMLNS).
375 |
376 | get_mod({boolean, _}) -> fxmlrpc_codec;
377 | get_mod({fault, _}) -> fxmlrpc_codec;
378 | get_mod({i4, _}) -> fxmlrpc_codec;
379 | get_mod({int, _}) -> fxmlrpc_codec;
380 | get_mod({base64, _}) -> fxmlrpc_codec;
381 | get_mod({struct, _}) -> fxmlrpc_codec;
382 | get_mod({call, _, _}) -> fxmlrpc_codec;
383 | get_mod({response, _}) -> fxmlrpc_codec;
384 | get_mod({double, _}) -> fxmlrpc_codec;
385 | get_mod({date, _}) -> fxmlrpc_codec;
386 | get_mod({string, _}) -> fxmlrpc_codec;
387 | get_mod({array, _}) -> fxmlrpc_codec;
388 | get_mod(Record) ->
389 | fxmlrpc_codec_external:lookup(Record).
390 |
391 | decode_name(__TopXMLNS, __Opts,
392 | {xmlel, <<"name">>, _attrs, _els}) ->
393 | Cdata = decode_name_els(__TopXMLNS, __Opts, _els, <<>>),
394 | Cdata.
395 |
396 | decode_name_els(__TopXMLNS, __Opts, [], Cdata) ->
397 | decode_name_cdata(__TopXMLNS, Cdata);
398 | decode_name_els(__TopXMLNS, __Opts,
399 | [{xmlcdata, _data} | _els], Cdata) ->
400 | decode_name_els(__TopXMLNS, __Opts, _els,
401 | <>);
402 | decode_name_els(__TopXMLNS, __Opts, [_ | _els],
403 | Cdata) ->
404 | decode_name_els(__TopXMLNS, __Opts, _els, Cdata).
405 |
406 | encode_name(Cdata, __TopXMLNS) ->
407 | __NewTopXMLNS =
408 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
409 | __TopXMLNS),
410 | _els = encode_name_cdata(Cdata, []),
411 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
412 | __TopXMLNS),
413 | {xmlel, <<"name">>, _attrs, _els}.
414 |
415 | decode_name_cdata(__TopXMLNS, <<>>) ->
416 | erlang:error({fxmlrpc_codec,
417 | {missing_cdata, <<>>, <<"name">>, __TopXMLNS}});
418 | decode_name_cdata(__TopXMLNS, _val) ->
419 | case catch erlang:binary_to_atom(_val, utf8) of
420 | {'EXIT', _} ->
421 | erlang:error({fxmlrpc_codec,
422 | {bad_cdata_value, <<>>, <<"name">>, __TopXMLNS}});
423 | _res -> _res
424 | end.
425 |
426 | encode_name_cdata(_val, _acc) ->
427 | [{xmlcdata, erlang:atom_to_binary(_val, utf8)} | _acc].
428 |
429 | decode_member(__TopXMLNS, __Opts,
430 | {xmlel, <<"member">>, _attrs, _els}) ->
431 | {Value, Name} = decode_member_els(__TopXMLNS, __Opts,
432 | _els, error, error),
433 | {Name, Value}.
434 |
435 | decode_member_els(__TopXMLNS, __Opts, [], Value,
436 | Name) ->
437 | {case Value of
438 | error ->
439 | erlang:error({fxmlrpc_codec,
440 | {missing_tag, <<"value">>, __TopXMLNS}});
441 | {value, Value1} -> Value1
442 | end,
443 | case Name of
444 | error ->
445 | erlang:error({fxmlrpc_codec,
446 | {missing_tag, <<"name">>, __TopXMLNS}});
447 | {value, Name1} -> Name1
448 | end};
449 | decode_member_els(__TopXMLNS, __Opts,
450 | [{xmlel, <<"name">>, _attrs, _} = _el | _els], Value,
451 | Name) ->
452 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
453 | __TopXMLNS)
454 | of
455 | <<"xmlrpc">> ->
456 | decode_member_els(__TopXMLNS, __Opts, _els, Value,
457 | {value, decode_name(<<"xmlrpc">>, __Opts, _el)});
458 | _ ->
459 | decode_member_els(__TopXMLNS, __Opts, _els, Value, Name)
460 | end;
461 | decode_member_els(__TopXMLNS, __Opts,
462 | [{xmlel, <<"value">>, _attrs, _} = _el | _els], Value,
463 | Name) ->
464 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
465 | __TopXMLNS)
466 | of
467 | <<"xmlrpc">> ->
468 | decode_member_els(__TopXMLNS, __Opts, _els,
469 | {value, decode_value(<<"xmlrpc">>, __Opts, _el)},
470 | Name);
471 | _ ->
472 | decode_member_els(__TopXMLNS, __Opts, _els, Value, Name)
473 | end;
474 | decode_member_els(__TopXMLNS, __Opts, [_ | _els], Value,
475 | Name) ->
476 | decode_member_els(__TopXMLNS, __Opts, _els, Value,
477 | Name).
478 |
479 | encode_member({Name, Value}, __TopXMLNS) ->
480 | __NewTopXMLNS =
481 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
482 | __TopXMLNS),
483 | _els = lists:reverse('encode_member_$value'(Value,
484 | __NewTopXMLNS,
485 | 'encode_member_$name'(Name,
486 | __NewTopXMLNS,
487 | []))),
488 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
489 | __TopXMLNS),
490 | {xmlel, <<"member">>, _attrs, _els}.
491 |
492 | 'encode_member_$value'(Value, __TopXMLNS, _acc) ->
493 | [encode_value(Value, __TopXMLNS) | _acc].
494 |
495 | 'encode_member_$name'(Name, __TopXMLNS, _acc) ->
496 | [encode_name(Name, __TopXMLNS) | _acc].
497 |
498 | decode_struct(__TopXMLNS, __Opts,
499 | {xmlel, <<"struct">>, _attrs, _els}) ->
500 | Members = decode_struct_els(__TopXMLNS, __Opts, _els,
501 | []),
502 | {struct, Members}.
503 |
504 | decode_struct_els(__TopXMLNS, __Opts, [], Members) ->
505 | lists:reverse(Members);
506 | decode_struct_els(__TopXMLNS, __Opts,
507 | [{xmlel, <<"member">>, _attrs, _} = _el | _els],
508 | Members) ->
509 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
510 | __TopXMLNS)
511 | of
512 | <<"xmlrpc">> ->
513 | decode_struct_els(__TopXMLNS, __Opts, _els,
514 | [decode_member(<<"xmlrpc">>, __Opts, _el)
515 | | Members]);
516 | _ ->
517 | decode_struct_els(__TopXMLNS, __Opts, _els, Members)
518 | end;
519 | decode_struct_els(__TopXMLNS, __Opts, [_ | _els],
520 | Members) ->
521 | decode_struct_els(__TopXMLNS, __Opts, _els, Members).
522 |
523 | encode_struct({struct, Members}, __TopXMLNS) ->
524 | __NewTopXMLNS =
525 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
526 | __TopXMLNS),
527 | _els = lists:reverse('encode_struct_$members'(Members,
528 | __NewTopXMLNS, [])),
529 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
530 | __TopXMLNS),
531 | {xmlel, <<"struct">>, _attrs, _els}.
532 |
533 | 'encode_struct_$members'([], __TopXMLNS, _acc) -> _acc;
534 | 'encode_struct_$members'([Members | _els], __TopXMLNS,
535 | _acc) ->
536 | 'encode_struct_$members'(_els, __TopXMLNS,
537 | [encode_member(Members, __TopXMLNS) | _acc]).
538 |
539 | decode_nil(__TopXMLNS, __Opts,
540 | {xmlel, <<"nil">>, _attrs, _els}) ->
541 | nil.
542 |
543 | encode_nil(nil, __TopXMLNS) ->
544 | __NewTopXMLNS =
545 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
546 | __TopXMLNS),
547 | _els = [],
548 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
549 | __TopXMLNS),
550 | {xmlel, <<"nil">>, _attrs, _els}.
551 |
552 | decode_data(__TopXMLNS, __Opts,
553 | {xmlel, <<"data">>, _attrs, _els}) ->
554 | V = decode_data_els(__TopXMLNS, __Opts, _els, []), V.
555 |
556 | decode_data_els(__TopXMLNS, __Opts, [], V) ->
557 | lists:reverse(V);
558 | decode_data_els(__TopXMLNS, __Opts,
559 | [{xmlel, <<"value">>, _attrs, _} = _el | _els], V) ->
560 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
561 | __TopXMLNS)
562 | of
563 | <<"xmlrpc">> ->
564 | decode_data_els(__TopXMLNS, __Opts, _els,
565 | [decode_value(<<"xmlrpc">>, __Opts, _el) | V]);
566 | _ -> decode_data_els(__TopXMLNS, __Opts, _els, V)
567 | end;
568 | decode_data_els(__TopXMLNS, __Opts, [_ | _els], V) ->
569 | decode_data_els(__TopXMLNS, __Opts, _els, V).
570 |
571 | encode_data(V, __TopXMLNS) ->
572 | __NewTopXMLNS =
573 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
574 | __TopXMLNS),
575 | _els = lists:reverse('encode_data_$v'(V, __NewTopXMLNS,
576 | [])),
577 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
578 | __TopXMLNS),
579 | {xmlel, <<"data">>, _attrs, _els}.
580 |
581 | 'encode_data_$v'([], __TopXMLNS, _acc) -> _acc;
582 | 'encode_data_$v'([V | _els], __TopXMLNS, _acc) ->
583 | 'encode_data_$v'(_els, __TopXMLNS,
584 | [encode_value(V, __TopXMLNS) | _acc]).
585 |
586 | decode_array(__TopXMLNS, __Opts,
587 | {xmlel, <<"array">>, _attrs, _els}) ->
588 | Data = decode_array_els(__TopXMLNS, __Opts, _els,
589 | error),
590 | {array, Data}.
591 |
592 | decode_array_els(__TopXMLNS, __Opts, [], Data) ->
593 | case Data of
594 | error ->
595 | erlang:error({fxmlrpc_codec,
596 | {missing_tag, <<"data">>, __TopXMLNS}});
597 | {value, Data1} -> Data1
598 | end;
599 | decode_array_els(__TopXMLNS, __Opts,
600 | [{xmlel, <<"data">>, _attrs, _} = _el | _els], Data) ->
601 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
602 | __TopXMLNS)
603 | of
604 | <<"xmlrpc">> ->
605 | decode_array_els(__TopXMLNS, __Opts, _els,
606 | {value, decode_data(<<"xmlrpc">>, __Opts, _el)});
607 | _ -> decode_array_els(__TopXMLNS, __Opts, _els, Data)
608 | end;
609 | decode_array_els(__TopXMLNS, __Opts, [_ | _els],
610 | Data) ->
611 | decode_array_els(__TopXMLNS, __Opts, _els, Data).
612 |
613 | encode_array({array, Data}, __TopXMLNS) ->
614 | __NewTopXMLNS =
615 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
616 | __TopXMLNS),
617 | _els = lists:reverse('encode_array_$data'(Data,
618 | __NewTopXMLNS, [])),
619 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
620 | __TopXMLNS),
621 | {xmlel, <<"array">>, _attrs, _els}.
622 |
623 | 'encode_array_$data'(Data, __TopXMLNS, _acc) ->
624 | [encode_data(Data, __TopXMLNS) | _acc].
625 |
626 | decode_boolean(__TopXMLNS, __Opts,
627 | {xmlel, <<"boolean">>, _attrs, _els}) ->
628 | Cdata = decode_boolean_els(__TopXMLNS, __Opts, _els,
629 | <<>>),
630 | {boolean, Cdata}.
631 |
632 | decode_boolean_els(__TopXMLNS, __Opts, [], Cdata) ->
633 | decode_boolean_cdata(__TopXMLNS, Cdata);
634 | decode_boolean_els(__TopXMLNS, __Opts,
635 | [{xmlcdata, _data} | _els], Cdata) ->
636 | decode_boolean_els(__TopXMLNS, __Opts, _els,
637 | <>);
638 | decode_boolean_els(__TopXMLNS, __Opts, [_ | _els],
639 | Cdata) ->
640 | decode_boolean_els(__TopXMLNS, __Opts, _els, Cdata).
641 |
642 | encode_boolean({boolean, Cdata}, __TopXMLNS) ->
643 | __NewTopXMLNS =
644 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
645 | __TopXMLNS),
646 | _els = encode_boolean_cdata(Cdata, []),
647 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
648 | __TopXMLNS),
649 | {xmlel, <<"boolean">>, _attrs, _els}.
650 |
651 | decode_boolean_cdata(__TopXMLNS, <<>>) ->
652 | erlang:error({fxmlrpc_codec,
653 | {missing_cdata, <<>>, <<"boolean">>, __TopXMLNS}});
654 | decode_boolean_cdata(__TopXMLNS, _val) ->
655 | case catch dec_bool(_val) of
656 | {'EXIT', _} ->
657 | erlang:error({fxmlrpc_codec,
658 | {bad_cdata_value, <<>>, <<"boolean">>, __TopXMLNS}});
659 | _res -> _res
660 | end.
661 |
662 | encode_boolean_cdata(_val, _acc) ->
663 | [{xmlcdata, enc_bool(_val)} | _acc].
664 |
665 | decode_dateTime(__TopXMLNS, __Opts,
666 | {xmlel, <<"dateTime.iso8601">>, _attrs, _els}) ->
667 | Cdata = decode_dateTime_els(__TopXMLNS, __Opts, _els,
668 | <<>>),
669 | {date, Cdata}.
670 |
671 | decode_dateTime_els(__TopXMLNS, __Opts, [], Cdata) ->
672 | decode_dateTime_cdata(__TopXMLNS, Cdata);
673 | decode_dateTime_els(__TopXMLNS, __Opts,
674 | [{xmlcdata, _data} | _els], Cdata) ->
675 | decode_dateTime_els(__TopXMLNS, __Opts, _els,
676 | <>);
677 | decode_dateTime_els(__TopXMLNS, __Opts, [_ | _els],
678 | Cdata) ->
679 | decode_dateTime_els(__TopXMLNS, __Opts, _els, Cdata).
680 |
681 | encode_dateTime({date, Cdata}, __TopXMLNS) ->
682 | __NewTopXMLNS =
683 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
684 | __TopXMLNS),
685 | _els = encode_dateTime_cdata(Cdata, []),
686 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
687 | __TopXMLNS),
688 | {xmlel, <<"dateTime.iso8601">>, _attrs, _els}.
689 |
690 | decode_dateTime_cdata(__TopXMLNS, <<>>) ->
691 | erlang:error({fxmlrpc_codec,
692 | {missing_cdata, <<>>, <<"dateTime.iso8601">>,
693 | __TopXMLNS}});
694 | decode_dateTime_cdata(__TopXMLNS, _val) -> _val.
695 |
696 | encode_dateTime_cdata(_val, _acc) ->
697 | [{xmlcdata, _val} | _acc].
698 |
699 | decode_base64(__TopXMLNS, __Opts,
700 | {xmlel, <<"base64">>, _attrs, _els}) ->
701 | Cdata = decode_base64_els(__TopXMLNS, __Opts, _els,
702 | <<>>),
703 | {base64, Cdata}.
704 |
705 | decode_base64_els(__TopXMLNS, __Opts, [], Cdata) ->
706 | decode_base64_cdata(__TopXMLNS, Cdata);
707 | decode_base64_els(__TopXMLNS, __Opts,
708 | [{xmlcdata, _data} | _els], Cdata) ->
709 | decode_base64_els(__TopXMLNS, __Opts, _els,
710 | <>);
711 | decode_base64_els(__TopXMLNS, __Opts, [_ | _els],
712 | Cdata) ->
713 | decode_base64_els(__TopXMLNS, __Opts, _els, Cdata).
714 |
715 | encode_base64({base64, Cdata}, __TopXMLNS) ->
716 | __NewTopXMLNS =
717 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
718 | __TopXMLNS),
719 | _els = encode_base64_cdata(Cdata, []),
720 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
721 | __TopXMLNS),
722 | {xmlel, <<"base64">>, _attrs, _els}.
723 |
724 | decode_base64_cdata(__TopXMLNS, <<>>) ->
725 | erlang:error({fxmlrpc_codec,
726 | {missing_cdata, <<>>, <<"base64">>, __TopXMLNS}});
727 | decode_base64_cdata(__TopXMLNS, _val) -> _val.
728 |
729 | encode_base64_cdata(_val, _acc) ->
730 | [{xmlcdata, _val} | _acc].
731 |
732 | decode_double(__TopXMLNS, __Opts,
733 | {xmlel, <<"double">>, _attrs, _els}) ->
734 | Cdata = decode_double_els(__TopXMLNS, __Opts, _els,
735 | <<>>),
736 | {double, Cdata}.
737 |
738 | decode_double_els(__TopXMLNS, __Opts, [], Cdata) ->
739 | decode_double_cdata(__TopXMLNS, Cdata);
740 | decode_double_els(__TopXMLNS, __Opts,
741 | [{xmlcdata, _data} | _els], Cdata) ->
742 | decode_double_els(__TopXMLNS, __Opts, _els,
743 | <>);
744 | decode_double_els(__TopXMLNS, __Opts, [_ | _els],
745 | Cdata) ->
746 | decode_double_els(__TopXMLNS, __Opts, _els, Cdata).
747 |
748 | encode_double({double, Cdata}, __TopXMLNS) ->
749 | __NewTopXMLNS =
750 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
751 | __TopXMLNS),
752 | _els = encode_double_cdata(Cdata, []),
753 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
754 | __TopXMLNS),
755 | {xmlel, <<"double">>, _attrs, _els}.
756 |
757 | decode_double_cdata(__TopXMLNS, <<>>) ->
758 | erlang:error({fxmlrpc_codec,
759 | {missing_cdata, <<>>, <<"double">>, __TopXMLNS}});
760 | decode_double_cdata(__TopXMLNS, _val) ->
761 | case catch erlang:binary_to_float(_val) of
762 | {'EXIT', _} ->
763 | erlang:error({fxmlrpc_codec,
764 | {bad_cdata_value, <<>>, <<"double">>, __TopXMLNS}});
765 | _res -> _res
766 | end.
767 |
768 | encode_double_cdata(_val, _acc) ->
769 | [{xmlcdata, erlang:float_to_binary(_val)} | _acc].
770 |
771 | decode_string(__TopXMLNS, __Opts,
772 | {xmlel, <<"string">>, _attrs, _els}) ->
773 | Cdata = decode_string_els(__TopXMLNS, __Opts, _els,
774 | <<>>),
775 | {string, Cdata}.
776 |
777 | decode_string_els(__TopXMLNS, __Opts, [], Cdata) ->
778 | decode_string_cdata(__TopXMLNS, Cdata);
779 | decode_string_els(__TopXMLNS, __Opts,
780 | [{xmlcdata, _data} | _els], Cdata) ->
781 | decode_string_els(__TopXMLNS, __Opts, _els,
782 | <>);
783 | decode_string_els(__TopXMLNS, __Opts, [_ | _els],
784 | Cdata) ->
785 | decode_string_els(__TopXMLNS, __Opts, _els, Cdata).
786 |
787 | encode_string({string, Cdata}, __TopXMLNS) ->
788 | __NewTopXMLNS =
789 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
790 | __TopXMLNS),
791 | _els = encode_string_cdata(Cdata, []),
792 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
793 | __TopXMLNS),
794 | {xmlel, <<"string">>, _attrs, _els}.
795 |
796 | decode_string_cdata(__TopXMLNS, <<>>) -> <<>>;
797 | decode_string_cdata(__TopXMLNS, _val) -> _val.
798 |
799 | encode_string_cdata(<<>>, _acc) -> _acc;
800 | encode_string_cdata(_val, _acc) ->
801 | [{xmlcdata, _val} | _acc].
802 |
803 | decode_int(__TopXMLNS, __Opts,
804 | {xmlel, <<"int">>, _attrs, _els}) ->
805 | Cdata = decode_int_els(__TopXMLNS, __Opts, _els, <<>>),
806 | {int, Cdata}.
807 |
808 | decode_int_els(__TopXMLNS, __Opts, [], Cdata) ->
809 | decode_int_cdata(__TopXMLNS, Cdata);
810 | decode_int_els(__TopXMLNS, __Opts,
811 | [{xmlcdata, _data} | _els], Cdata) ->
812 | decode_int_els(__TopXMLNS, __Opts, _els,
813 | <>);
814 | decode_int_els(__TopXMLNS, __Opts, [_ | _els], Cdata) ->
815 | decode_int_els(__TopXMLNS, __Opts, _els, Cdata).
816 |
817 | encode_int({int, Cdata}, __TopXMLNS) ->
818 | __NewTopXMLNS =
819 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
820 | __TopXMLNS),
821 | _els = encode_int_cdata(Cdata, []),
822 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
823 | __TopXMLNS),
824 | {xmlel, <<"int">>, _attrs, _els}.
825 |
826 | decode_int_cdata(__TopXMLNS, <<>>) ->
827 | erlang:error({fxmlrpc_codec,
828 | {missing_cdata, <<>>, <<"int">>, __TopXMLNS}});
829 | decode_int_cdata(__TopXMLNS, _val) ->
830 | case catch erlang:binary_to_integer(_val) of
831 | {'EXIT', _} ->
832 | erlang:error({fxmlrpc_codec,
833 | {bad_cdata_value, <<>>, <<"int">>, __TopXMLNS}});
834 | _res -> _res
835 | end.
836 |
837 | encode_int_cdata(_val, _acc) ->
838 | [{xmlcdata, erlang:integer_to_binary(_val)} | _acc].
839 |
840 | decode_i4(__TopXMLNS, __Opts,
841 | {xmlel, <<"i4">>, _attrs, _els}) ->
842 | Cdata = decode_i4_els(__TopXMLNS, __Opts, _els, <<>>),
843 | {i4, Cdata}.
844 |
845 | decode_i4_els(__TopXMLNS, __Opts, [], Cdata) ->
846 | decode_i4_cdata(__TopXMLNS, Cdata);
847 | decode_i4_els(__TopXMLNS, __Opts,
848 | [{xmlcdata, _data} | _els], Cdata) ->
849 | decode_i4_els(__TopXMLNS, __Opts, _els,
850 | <>);
851 | decode_i4_els(__TopXMLNS, __Opts, [_ | _els], Cdata) ->
852 | decode_i4_els(__TopXMLNS, __Opts, _els, Cdata).
853 |
854 | encode_i4({i4, Cdata}, __TopXMLNS) ->
855 | __NewTopXMLNS =
856 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
857 | __TopXMLNS),
858 | _els = encode_i4_cdata(Cdata, []),
859 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
860 | __TopXMLNS),
861 | {xmlel, <<"i4">>, _attrs, _els}.
862 |
863 | decode_i4_cdata(__TopXMLNS, <<>>) ->
864 | erlang:error({fxmlrpc_codec,
865 | {missing_cdata, <<>>, <<"i4">>, __TopXMLNS}});
866 | decode_i4_cdata(__TopXMLNS, _val) ->
867 | case catch erlang:binary_to_integer(_val) of
868 | {'EXIT', _} ->
869 | erlang:error({fxmlrpc_codec,
870 | {bad_cdata_value, <<>>, <<"i4">>, __TopXMLNS}});
871 | _res -> _res
872 | end.
873 |
874 | encode_i4_cdata(_val, _acc) ->
875 | [{xmlcdata, erlang:integer_to_binary(_val)} | _acc].
876 |
877 | decode_value(__TopXMLNS, __Opts,
878 | {xmlel, <<"value">>, _attrs, _els}) ->
879 | {Cdata, Val} = decode_value_els(__TopXMLNS, __Opts,
880 | _els, <<>>, undefined),
881 | {Val, Cdata}.
882 |
883 | decode_value_els(__TopXMLNS, __Opts, [], Cdata, Val) ->
884 | {decode_value_cdata(__TopXMLNS, Cdata), Val};
885 | decode_value_els(__TopXMLNS, __Opts,
886 | [{xmlcdata, _data} | _els], Cdata, Val) ->
887 | decode_value_els(__TopXMLNS, __Opts, _els,
888 | <>, Val);
889 | decode_value_els(__TopXMLNS, __Opts,
890 | [{xmlel, <<"i4">>, _attrs, _} = _el | _els], Cdata,
891 | Val) ->
892 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
893 | __TopXMLNS)
894 | of
895 | <<"xmlrpc">> ->
896 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
897 | decode_i4(<<"xmlrpc">>, __Opts, _el));
898 | _ ->
899 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
900 | end;
901 | decode_value_els(__TopXMLNS, __Opts,
902 | [{xmlel, <<"int">>, _attrs, _} = _el | _els], Cdata,
903 | Val) ->
904 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
905 | __TopXMLNS)
906 | of
907 | <<"xmlrpc">> ->
908 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
909 | decode_int(<<"xmlrpc">>, __Opts, _el));
910 | _ ->
911 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
912 | end;
913 | decode_value_els(__TopXMLNS, __Opts,
914 | [{xmlel, <<"string">>, _attrs, _} = _el | _els], Cdata,
915 | Val) ->
916 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
917 | __TopXMLNS)
918 | of
919 | <<"xmlrpc">> ->
920 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
921 | decode_string(<<"xmlrpc">>, __Opts, _el));
922 | _ ->
923 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
924 | end;
925 | decode_value_els(__TopXMLNS, __Opts,
926 | [{xmlel, <<"double">>, _attrs, _} = _el | _els], Cdata,
927 | Val) ->
928 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
929 | __TopXMLNS)
930 | of
931 | <<"xmlrpc">> ->
932 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
933 | decode_double(<<"xmlrpc">>, __Opts, _el));
934 | _ ->
935 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
936 | end;
937 | decode_value_els(__TopXMLNS, __Opts,
938 | [{xmlel, <<"base64">>, _attrs, _} = _el | _els], Cdata,
939 | Val) ->
940 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
941 | __TopXMLNS)
942 | of
943 | <<"xmlrpc">> ->
944 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
945 | decode_base64(<<"xmlrpc">>, __Opts, _el));
946 | _ ->
947 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
948 | end;
949 | decode_value_els(__TopXMLNS, __Opts,
950 | [{xmlel, <<"boolean">>, _attrs, _} = _el | _els], Cdata,
951 | Val) ->
952 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
953 | __TopXMLNS)
954 | of
955 | <<"xmlrpc">> ->
956 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
957 | decode_boolean(<<"xmlrpc">>, __Opts, _el));
958 | _ ->
959 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
960 | end;
961 | decode_value_els(__TopXMLNS, __Opts,
962 | [{xmlel, <<"array">>, _attrs, _} = _el | _els], Cdata,
963 | Val) ->
964 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
965 | __TopXMLNS)
966 | of
967 | <<"xmlrpc">> ->
968 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
969 | decode_array(<<"xmlrpc">>, __Opts, _el));
970 | _ ->
971 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
972 | end;
973 | decode_value_els(__TopXMLNS, __Opts,
974 | [{xmlel, <<"nil">>, _attrs, _} = _el | _els], Cdata,
975 | Val) ->
976 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
977 | __TopXMLNS)
978 | of
979 | <<"xmlrpc">> ->
980 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
981 | decode_nil(<<"xmlrpc">>, __Opts, _el));
982 | _ ->
983 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
984 | end;
985 | decode_value_els(__TopXMLNS, __Opts,
986 | [{xmlel, <<"struct">>, _attrs, _} = _el | _els], Cdata,
987 | Val) ->
988 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
989 | __TopXMLNS)
990 | of
991 | <<"xmlrpc">> ->
992 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
993 | decode_struct(<<"xmlrpc">>, __Opts, _el));
994 | _ ->
995 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
996 | end;
997 | decode_value_els(__TopXMLNS, __Opts,
998 | [{xmlel, <<"dateTime.iso8601">>, _attrs, _} = _el
999 | | _els],
1000 | Cdata, Val) ->
1001 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1002 | __TopXMLNS)
1003 | of
1004 | <<"xmlrpc">> ->
1005 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata,
1006 | decode_dateTime(<<"xmlrpc">>, __Opts, _el));
1007 | _ ->
1008 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val)
1009 | end;
1010 | decode_value_els(__TopXMLNS, __Opts, [_ | _els], Cdata,
1011 | Val) ->
1012 | decode_value_els(__TopXMLNS, __Opts, _els, Cdata, Val).
1013 |
1014 | encode_value({Val, Cdata}, __TopXMLNS) ->
1015 | __NewTopXMLNS =
1016 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1017 | __TopXMLNS),
1018 | _els = lists:reverse(encode_value_cdata(Cdata,
1019 | 'encode_value_$val'(Val,
1020 | __NewTopXMLNS,
1021 | []))),
1022 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1023 | __TopXMLNS),
1024 | {xmlel, <<"value">>, _attrs, _els}.
1025 |
1026 | 'encode_value_$val'(undefined, __TopXMLNS, _acc) ->
1027 | _acc;
1028 | 'encode_value_$val'({i4, _} = Val, __TopXMLNS, _acc) ->
1029 | [encode_i4(Val, __TopXMLNS) | _acc];
1030 | 'encode_value_$val'({int, _} = Val, __TopXMLNS, _acc) ->
1031 | [encode_int(Val, __TopXMLNS) | _acc];
1032 | 'encode_value_$val'({string, _} = Val, __TopXMLNS,
1033 | _acc) ->
1034 | [encode_string(Val, __TopXMLNS) | _acc];
1035 | 'encode_value_$val'({double, _} = Val, __TopXMLNS,
1036 | _acc) ->
1037 | [encode_double(Val, __TopXMLNS) | _acc];
1038 | 'encode_value_$val'({base64, _} = Val, __TopXMLNS,
1039 | _acc) ->
1040 | [encode_base64(Val, __TopXMLNS) | _acc];
1041 | 'encode_value_$val'({boolean, _} = Val, __TopXMLNS,
1042 | _acc) ->
1043 | [encode_boolean(Val, __TopXMLNS) | _acc];
1044 | 'encode_value_$val'({array, _} = Val, __TopXMLNS,
1045 | _acc) ->
1046 | [encode_array(Val, __TopXMLNS) | _acc];
1047 | 'encode_value_$val'(nil = Val, __TopXMLNS, _acc) ->
1048 | [encode_nil(Val, __TopXMLNS) | _acc];
1049 | 'encode_value_$val'({struct, _} = Val, __TopXMLNS,
1050 | _acc) ->
1051 | [encode_struct(Val, __TopXMLNS) | _acc];
1052 | 'encode_value_$val'({date, _} = Val, __TopXMLNS,
1053 | _acc) ->
1054 | [encode_dateTime(Val, __TopXMLNS) | _acc].
1055 |
1056 | decode_value_cdata(__TopXMLNS, <<>>) -> undefined;
1057 | decode_value_cdata(__TopXMLNS, _val) -> _val.
1058 |
1059 | encode_value_cdata(undefined, _acc) -> _acc;
1060 | encode_value_cdata(_val, _acc) ->
1061 | [{xmlcdata, _val} | _acc].
1062 |
1063 | decode_param(__TopXMLNS, __Opts,
1064 | {xmlel, <<"param">>, _attrs, _els}) ->
1065 | Value = decode_param_els(__TopXMLNS, __Opts, _els,
1066 | error),
1067 | Value.
1068 |
1069 | decode_param_els(__TopXMLNS, __Opts, [], Value) ->
1070 | case Value of
1071 | error ->
1072 | erlang:error({fxmlrpc_codec,
1073 | {missing_tag, <<"value">>, __TopXMLNS}});
1074 | {value, Value1} -> Value1
1075 | end;
1076 | decode_param_els(__TopXMLNS, __Opts,
1077 | [{xmlel, <<"value">>, _attrs, _} = _el | _els],
1078 | Value) ->
1079 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1080 | __TopXMLNS)
1081 | of
1082 | <<"xmlrpc">> ->
1083 | decode_param_els(__TopXMLNS, __Opts, _els,
1084 | {value, decode_value(<<"xmlrpc">>, __Opts, _el)});
1085 | _ -> decode_param_els(__TopXMLNS, __Opts, _els, Value)
1086 | end;
1087 | decode_param_els(__TopXMLNS, __Opts, [_ | _els],
1088 | Value) ->
1089 | decode_param_els(__TopXMLNS, __Opts, _els, Value).
1090 |
1091 | encode_param(Value, __TopXMLNS) ->
1092 | __NewTopXMLNS =
1093 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1094 | __TopXMLNS),
1095 | _els = lists:reverse('encode_param_$value'(Value,
1096 | __NewTopXMLNS, [])),
1097 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1098 | __TopXMLNS),
1099 | {xmlel, <<"param">>, _attrs, _els}.
1100 |
1101 | 'encode_param_$value'(Value, __TopXMLNS, _acc) ->
1102 | [encode_value(Value, __TopXMLNS) | _acc].
1103 |
1104 | decode_params(__TopXMLNS, __Opts,
1105 | {xmlel, <<"params">>, _attrs, _els}) ->
1106 | Params = decode_params_els(__TopXMLNS, __Opts, _els,
1107 | []),
1108 | Params.
1109 |
1110 | decode_params_els(__TopXMLNS, __Opts, [], Params) ->
1111 | lists:reverse(Params);
1112 | decode_params_els(__TopXMLNS, __Opts,
1113 | [{xmlel, <<"param">>, _attrs, _} = _el | _els],
1114 | Params) ->
1115 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1116 | __TopXMLNS)
1117 | of
1118 | <<"xmlrpc">> ->
1119 | decode_params_els(__TopXMLNS, __Opts, _els,
1120 | [decode_param(<<"xmlrpc">>, __Opts, _el) | Params]);
1121 | _ -> decode_params_els(__TopXMLNS, __Opts, _els, Params)
1122 | end;
1123 | decode_params_els(__TopXMLNS, __Opts, [_ | _els],
1124 | Params) ->
1125 | decode_params_els(__TopXMLNS, __Opts, _els, Params).
1126 |
1127 | encode_params(Params, __TopXMLNS) ->
1128 | __NewTopXMLNS =
1129 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1130 | __TopXMLNS),
1131 | _els = lists:reverse('encode_params_$params'(Params,
1132 | __NewTopXMLNS, [])),
1133 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1134 | __TopXMLNS),
1135 | {xmlel, <<"params">>, _attrs, _els}.
1136 |
1137 | 'encode_params_$params'([], __TopXMLNS, _acc) -> _acc;
1138 | 'encode_params_$params'([Params | _els], __TopXMLNS,
1139 | _acc) ->
1140 | 'encode_params_$params'(_els, __TopXMLNS,
1141 | [encode_param(Params, __TopXMLNS) | _acc]).
1142 |
1143 | decode_methodName(__TopXMLNS, __Opts,
1144 | {xmlel, <<"methodName">>, _attrs, _els}) ->
1145 | Cdata = decode_methodName_els(__TopXMLNS, __Opts, _els,
1146 | <<>>),
1147 | Cdata.
1148 |
1149 | decode_methodName_els(__TopXMLNS, __Opts, [], Cdata) ->
1150 | decode_methodName_cdata(__TopXMLNS, Cdata);
1151 | decode_methodName_els(__TopXMLNS, __Opts,
1152 | [{xmlcdata, _data} | _els], Cdata) ->
1153 | decode_methodName_els(__TopXMLNS, __Opts, _els,
1154 | <>);
1155 | decode_methodName_els(__TopXMLNS, __Opts, [_ | _els],
1156 | Cdata) ->
1157 | decode_methodName_els(__TopXMLNS, __Opts, _els, Cdata).
1158 |
1159 | encode_methodName(Cdata, __TopXMLNS) ->
1160 | __NewTopXMLNS =
1161 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1162 | __TopXMLNS),
1163 | _els = encode_methodName_cdata(Cdata, []),
1164 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1165 | __TopXMLNS),
1166 | {xmlel, <<"methodName">>, _attrs, _els}.
1167 |
1168 | decode_methodName_cdata(__TopXMLNS, <<>>) ->
1169 | erlang:error({fxmlrpc_codec,
1170 | {missing_cdata, <<>>, <<"methodName">>, __TopXMLNS}});
1171 | decode_methodName_cdata(__TopXMLNS, _val) ->
1172 | case catch erlang:binary_to_atom(_val, utf8) of
1173 | {'EXIT', _} ->
1174 | erlang:error({fxmlrpc_codec,
1175 | {bad_cdata_value, <<>>, <<"methodName">>, __TopXMLNS}});
1176 | _res -> _res
1177 | end.
1178 |
1179 | encode_methodName_cdata(_val, _acc) ->
1180 | [{xmlcdata, erlang:atom_to_binary(_val, utf8)} | _acc].
1181 |
1182 | decode_fault(__TopXMLNS, __Opts,
1183 | {xmlel, <<"fault">>, _attrs, _els}) ->
1184 | Value = decode_fault_els(__TopXMLNS, __Opts, _els,
1185 | error),
1186 | {fault, Value}.
1187 |
1188 | decode_fault_els(__TopXMLNS, __Opts, [], Value) ->
1189 | case Value of
1190 | error ->
1191 | erlang:error({fxmlrpc_codec,
1192 | {missing_tag, <<"value">>, __TopXMLNS}});
1193 | {value, Value1} -> Value1
1194 | end;
1195 | decode_fault_els(__TopXMLNS, __Opts,
1196 | [{xmlel, <<"value">>, _attrs, _} = _el | _els],
1197 | Value) ->
1198 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1199 | __TopXMLNS)
1200 | of
1201 | <<"xmlrpc">> ->
1202 | decode_fault_els(__TopXMLNS, __Opts, _els,
1203 | {value, decode_value(<<"xmlrpc">>, __Opts, _el)});
1204 | _ -> decode_fault_els(__TopXMLNS, __Opts, _els, Value)
1205 | end;
1206 | decode_fault_els(__TopXMLNS, __Opts, [_ | _els],
1207 | Value) ->
1208 | decode_fault_els(__TopXMLNS, __Opts, _els, Value).
1209 |
1210 | encode_fault({fault, Value}, __TopXMLNS) ->
1211 | __NewTopXMLNS =
1212 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1213 | __TopXMLNS),
1214 | _els = lists:reverse('encode_fault_$value'(Value,
1215 | __NewTopXMLNS, [])),
1216 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1217 | __TopXMLNS),
1218 | {xmlel, <<"fault">>, _attrs, _els}.
1219 |
1220 | 'encode_fault_$value'(Value, __TopXMLNS, _acc) ->
1221 | [encode_value(Value, __TopXMLNS) | _acc].
1222 |
1223 | decode_methodResponse(__TopXMLNS, __Opts,
1224 | {xmlel, <<"methodResponse">>, _attrs, _els}) ->
1225 | Payload = decode_methodResponse_els(__TopXMLNS, __Opts,
1226 | _els, []),
1227 | {response, Payload}.
1228 |
1229 | decode_methodResponse_els(__TopXMLNS, __Opts, [],
1230 | Payload) ->
1231 | Payload;
1232 | decode_methodResponse_els(__TopXMLNS, __Opts,
1233 | [{xmlel, <<"fault">>, _attrs, _} = _el | _els],
1234 | Payload) ->
1235 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1236 | __TopXMLNS)
1237 | of
1238 | <<"xmlrpc">> ->
1239 | decode_methodResponse_els(__TopXMLNS, __Opts, _els,
1240 | decode_fault(<<"xmlrpc">>, __Opts, _el));
1241 | _ ->
1242 | decode_methodResponse_els(__TopXMLNS, __Opts, _els,
1243 | Payload)
1244 | end;
1245 | decode_methodResponse_els(__TopXMLNS, __Opts,
1246 | [{xmlel, <<"params">>, _attrs, _} = _el | _els],
1247 | Payload) ->
1248 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1249 | __TopXMLNS)
1250 | of
1251 | <<"xmlrpc">> ->
1252 | decode_methodResponse_els(__TopXMLNS, __Opts, _els,
1253 | decode_params(<<"xmlrpc">>, __Opts, _el));
1254 | _ ->
1255 | decode_methodResponse_els(__TopXMLNS, __Opts, _els,
1256 | Payload)
1257 | end;
1258 | decode_methodResponse_els(__TopXMLNS, __Opts,
1259 | [_ | _els], Payload) ->
1260 | decode_methodResponse_els(__TopXMLNS, __Opts, _els,
1261 | Payload).
1262 |
1263 | encode_methodResponse({response, Payload},
1264 | __TopXMLNS) ->
1265 | __NewTopXMLNS =
1266 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1267 | __TopXMLNS),
1268 | _els =
1269 | lists:reverse('encode_methodResponse_$payload'(Payload,
1270 | __NewTopXMLNS, [])),
1271 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1272 | __TopXMLNS),
1273 | {xmlel, <<"methodResponse">>, _attrs, _els}.
1274 |
1275 | 'encode_methodResponse_$payload'([], __TopXMLNS,
1276 | _acc) ->
1277 | _acc;
1278 | 'encode_methodResponse_$payload'({fault, _} = Payload,
1279 | __TopXMLNS, _acc) ->
1280 | [encode_fault(Payload, __TopXMLNS) | _acc];
1281 | 'encode_methodResponse_$payload'(_ = Payload,
1282 | __TopXMLNS, _acc) ->
1283 | [encode_params(Payload, __TopXMLNS) | _acc].
1284 |
1285 | decode_methodCall(__TopXMLNS, __Opts,
1286 | {xmlel, <<"methodCall">>, _attrs, _els}) ->
1287 | {Params, Name} = decode_methodCall_els(__TopXMLNS,
1288 | __Opts, _els, [], error),
1289 | {call, Name, Params}.
1290 |
1291 | decode_methodCall_els(__TopXMLNS, __Opts, [], Params,
1292 | Name) ->
1293 | {Params,
1294 | case Name of
1295 | error ->
1296 | erlang:error({fxmlrpc_codec,
1297 | {missing_tag, <<"methodName">>, __TopXMLNS}});
1298 | {value, Name1} -> Name1
1299 | end};
1300 | decode_methodCall_els(__TopXMLNS, __Opts,
1301 | [{xmlel, <<"methodName">>, _attrs, _} = _el | _els],
1302 | Params, Name) ->
1303 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1304 | __TopXMLNS)
1305 | of
1306 | <<"xmlrpc">> ->
1307 | decode_methodCall_els(__TopXMLNS, __Opts, _els, Params,
1308 | {value,
1309 | decode_methodName(<<"xmlrpc">>, __Opts, _el)});
1310 | _ ->
1311 | decode_methodCall_els(__TopXMLNS, __Opts, _els, Params,
1312 | Name)
1313 | end;
1314 | decode_methodCall_els(__TopXMLNS, __Opts,
1315 | [{xmlel, <<"params">>, _attrs, _} = _el | _els], Params,
1316 | Name) ->
1317 | case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs,
1318 | __TopXMLNS)
1319 | of
1320 | <<"xmlrpc">> ->
1321 | decode_methodCall_els(__TopXMLNS, __Opts, _els,
1322 | decode_params(<<"xmlrpc">>, __Opts, _el), Name);
1323 | _ ->
1324 | decode_methodCall_els(__TopXMLNS, __Opts, _els, Params,
1325 | Name)
1326 | end;
1327 | decode_methodCall_els(__TopXMLNS, __Opts, [_ | _els],
1328 | Params, Name) ->
1329 | decode_methodCall_els(__TopXMLNS, __Opts, _els, Params,
1330 | Name).
1331 |
1332 | encode_methodCall({call, Name, Params}, __TopXMLNS) ->
1333 | __NewTopXMLNS =
1334 | fxmlrpc_codec:choose_top_xmlns(<<"xmlrpc">>, [],
1335 | __TopXMLNS),
1336 | _els = lists:reverse('encode_methodCall_$params'(Params,
1337 | __NewTopXMLNS,
1338 | 'encode_methodCall_$name'(Name,
1339 | __NewTopXMLNS,
1340 | []))),
1341 | _attrs = fxmlrpc_codec:enc_xmlns_attrs(__NewTopXMLNS,
1342 | __TopXMLNS),
1343 | {xmlel, <<"methodCall">>, _attrs, _els}.
1344 |
1345 | 'encode_methodCall_$params'([], __TopXMLNS, _acc) ->
1346 | _acc;
1347 | 'encode_methodCall_$params'(Params, __TopXMLNS, _acc) ->
1348 | [encode_params(Params, __TopXMLNS) | _acc].
1349 |
1350 | 'encode_methodCall_$name'(Name, __TopXMLNS, _acc) ->
1351 | [encode_methodName(Name, __TopXMLNS) | _acc].
1352 |
--------------------------------------------------------------------------------
/src/fxmlrpc_codec_external.erl:
--------------------------------------------------------------------------------
1 | %% Created automatically by XML generator (fxml_gen.erl)
2 | %% Source: fxmlrpc_codec.spec
3 |
4 | -module(fxmlrpc_codec_external).
5 |
6 | -compile(export_all).
7 |
8 | modules() -> [].
9 |
10 | lookup(_, _) -> undefined.
11 |
12 | lookup(Term) -> erlang:error(badarg, [Term]).
13 |
--------------------------------------------------------------------------------
/test/fxml_test.erl:
--------------------------------------------------------------------------------
1 | %%%----------------------------------------------------------------------
2 | %%% File : fxml_test.erl
3 | %%% Author : Evgeniy Khramtsov
4 | %%% Purpose : xml module testing
5 | %%% Created : 17 Dec 2013 by Evgeny Khramtsov
6 | %%%
7 | %%%
8 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved.
9 | %%%
10 | %%% Licensed under the Apache License, Version 2.0 (the "License");
11 | %%% you may not use this file except in compliance with the License.
12 | %%% You may obtain a copy of the License at
13 | %%%
14 | %%% http://www.apache.org/licenses/LICENSE-2.0
15 | %%%
16 | %%% Unless required by applicable law or agreed to in writing, software
17 | %%% distributed under the License is distributed on an "AS IS" BASIS,
18 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | %%% See the License for the specific language governing permissions and
20 | %%% limitations under the License.
21 | %%%
22 | %%%----------------------------------------------------------------------
23 |
24 | -module(fxml_test).
25 |
26 | -compile(export_all).
27 |
28 | -include_lib("eunit/include/eunit.hrl").
29 | -include("fxml.hrl").
30 |
31 | new() ->
32 | new(self()).
33 | new(Pid) ->
34 | new(Pid, infinity).
35 | new(Pid, MaxSize) ->
36 | fxml_stream:new(Pid, MaxSize).
37 |
38 | close(State) ->
39 | ?assertEqual(true, fxml_stream:close(State)).
40 |
41 | start_test() ->
42 | ?assertMatch({ok, _}, application:ensure_all_started(fast_xml)),
43 | ?assertMatch(ok, application:stop(fast_xml)),
44 | ?assertMatch({ok, _}, application:ensure_all_started(fast_xml)),
45 | ?assertMatch(ok, application:stop(fast_xml)).
46 |
47 | tag_test() ->
48 | ?assertEqual(#xmlel{name = <<"root">>},
49 | fxml_stream:parse_element(<<"">>)).
50 |
51 | cve_2022_25236_test() ->
52 | ?assertEqual({error, {2, <<"syntax error">>}},
53 | fxml_stream:parse_element(<<"">>)).
54 |
55 | empty_tag_test() ->
56 | ?assertEqual(#xmlel{name = <<"root">>},
57 | fxml_stream:parse_element(<<"">>)).
58 |
59 | empty_tag_with_ns_test() ->
60 | ?assertEqual(#xmlel{name = <<"root">>, attrs = [{<<"xmlns">>, <<"ns">>}]},
61 | fxml_stream:parse_element(<<"">>)).
62 |
63 | tag_with_cdata_test() ->
64 | ?assertEqual(#xmlel{name = <<"root">>,
65 | children = [{xmlcdata, <<"cdata">>}]},
66 | fxml_stream:parse_element(<<"cdata">>)).
67 |
68 | tag_with_attrs_test() ->
69 | ?assertEqual(#xmlel{name = <<"root">>,
70 | attrs = [{<<"a">>, <<"1">>}, {<<"b">>, <<"2">>}]},
71 | fxml_stream:parse_element(<<"">>)).
72 |
73 | tag_with_empty_attr_test() ->
74 | ?assertEqual(#xmlel{name = <<"root">>, attrs = [{<<"a">>, <<>>}]},
75 | fxml_stream:parse_element(<<"">>)).
76 |
77 | tag_with_prefix_test() ->
78 | ?assertEqual(#xmlel{name = <<"prefix:root">>,
79 | attrs = [{<<"xmlns:prefix">>, <<"ns">>}]},
80 | fxml_stream:parse_element(<<"">>)).
81 |
82 | tag_with_prefix_children1_test() ->
83 | ?assertEqual(#xmlel{name = <<"prefix:root">>,
84 | attrs = [{<<"xmlns:prefix">>, <<"ns">>}],
85 | children = [#xmlel{name = <<"prefix:a">>}]},
86 | fxml_stream:parse_element(<<"">>)).
87 |
88 | tag_with_prefix_children2_test() ->
89 | ?assertEqual(#xmlel{name = <<"prefix:root">>,
90 | attrs = [{<<"xmlns:prefix">>, <<"ns">>}],
91 | children = [#xmlel{name = <<"a">>, attrs=[{<<"xmlns">>, <<"ns2">>}]}]},
92 | fxml_stream:parse_element(<<"">>)).
93 |
94 | tag_with_prefix_children3_test() ->
95 | ?assertEqual(#xmlel{name = <<"prefix:root">>,
96 | attrs = [{<<"xmlns:prefix">>, <<"ns">>}],
97 | children = [#xmlel{name = <<"zed:a">>, attrs=[{<<"xmlns:zed">>, <<"ns2">>}]}]},
98 | fxml_stream:parse_element(<<"">>)).
99 |
100 | tag_with_prefix_children4_test() ->
101 | ?assertEqual(#xmlel{name = <<"prefix:root">>,
102 | attrs = [{<<"xmlns:prefix">>, <<"ns">>}],
103 | children = [#xmlel{name = <<"a">>, attrs=[{<<"xmlns">>, <<"ns">>}]}]},
104 | fxml_stream:parse_element(<<"">>)).
105 |
106 | tag_with_attr_with_prefix_test() ->
107 | ?assertEqual(#xmlel{name = <<"root">>,
108 | attrs = [{<<"xmlns:prefix1">>, <<"ns1">>},
109 | {<<"xmlns:prefix2">>, <<"ns2">>},
110 | {<<"prefix1:a">>, <<"1">>},
111 | {<<"prefix2:b">>, <<"2">>}]},
112 | fxml_stream:parse_element(<<
113 | "">>)).
115 |
116 | tag_with_tags_test() ->
117 | ?assertEqual(#xmlel{name = <<"root">>,
118 | children = [#xmlel{name = <<"a">>},
119 | {xmlcdata, <<"cdata1">>},
120 | #xmlel{name = <<"b">>},
121 | {xmlcdata, <<"cdata2">>}]},
122 | fxml_stream:parse_element(<<"cdata1cdata2">>)).
123 |
124 | use_maps_test() ->
125 | ?assertEqual(#{'__struct__' => 'Elixir.FastXML.El', name => <<"root">>, attrs => #{}, children => []}, fxml_stream:parse_element(<<"">>, [use_maps])).
126 |
127 | receiver(Acc) ->
128 | receive
129 | {'$gen_event', Msg} ->
130 | receiver([Msg|Acc]);
131 | {get, Parent} ->
132 | Parent ! lists:reverse(Acc),
133 | receiver([]);
134 | {close, Parent} ->
135 | Parent ! closed
136 | end.
137 |
138 | collect_events(Pid) ->
139 | Pid ! {get, self()},
140 | receive
141 | Events ->
142 | Events
143 | end.
144 |
145 | close_collector(Pid) ->
146 | Pid ! {close, self()},
147 | receive
148 | closed ->
149 | closed
150 | end.
151 |
152 | parser_loop(TestData) ->
153 | parser_loop(infinity, [], TestData).
154 |
155 | parser_loop(MaxSize, Options, TestData) ->
156 | CollectorPid = spawn_link(fun() -> receiver([]) end),
157 | parser_loop_int(TestData, fxml_stream:new(CollectorPid, MaxSize, Options), CollectorPid).
158 |
159 | parser_loop_int([reset | Tail], Handle, CollectorPid) ->
160 | parser_loop_int(Tail, fxml_stream:reset(Handle), CollectorPid);
161 | parser_loop_int([{Input, Output} | Tail], Handle, CollectorPid) ->
162 | NHandle = fxml_stream:parse(Handle, Input),
163 | ?assertEqual(Output, collect_events(CollectorPid)),
164 | parser_loop_int(Tail, NHandle, CollectorPid);
165 | parser_loop_int([], Handle, CollectorPid) ->
166 | close(Handle),
167 | close_collector(CollectorPid).
168 |
169 | stream_test() ->
170 | CallbackPid = spawn_link(fun() -> receiver([]) end),
171 | Stream0 = new(CallbackPid),
172 | Data = [<<"">>,
173 | <<"junk1">>, <<"">>, <<"junk2">>, <<"cdata">>,
174 | <<"junk3">>, <<"">>],
175 | StreamN = lists:foldl(
176 | fun(Chunk, Stream) ->
177 | fxml_stream:parse(Stream, Chunk)
178 | end, Stream0, Data),
179 | close(StreamN),
180 | ?assertEqual(
181 | [{xmlstreamstart, <<"prefix:root">>, [{<<"xmlns:prefix">>, <<"ns">>},
182 | {<<"prefix:r">>, <<"1">>}]},
183 | {xmlstreamelement, #xmlel{name = <<"a">>}},
184 | {xmlstreamelement, #xmlel{name = <<"b">>,
185 | children = [{xmlcdata, <<"cdata">>}]}},
186 | {xmlstreamend, <<"prefix:root">>}],
187 | collect_events(CallbackPid)).
188 |
189 | stream_normalized_ns_test() ->
190 | CallbackPid = spawn_link(fun() -> receiver([]) end),
191 | Stream0 = new(CallbackPid),
192 | Data = [<<"">>,
193 | <<"junk1">>, <<"">>,
194 | <<"">>,
195 | <<"">>, <<"">>,
196 | <<"">>, <<"">>,
197 | <<"">>, <<"">>,
198 | <<"">>,
199 | <<"junk2">>, <<"cdata">>,
200 | <<"junk3">>, <<"">>],
201 | StreamN = lists:foldl(
202 | fun(Chunk, Stream) ->
203 | fxml_stream:parse(Stream, Chunk)
204 | end, Stream0, Data),
205 | close(StreamN),
206 | ?assertEqual(
207 | [{xmlstreamstart, <<"prefix:root">>, [{<<"xmlns">>, <<"ns0">>},
208 | {<<"xmlns:t">>, <<"ns1">>},
209 | {<<"xmlns:prefix">>, <<"ns">>},
210 | {<<"prefix:r">>, <<"1">>}]},
211 | {xmlstreamelement, #xmlel{name = <<"a">>}},
212 | {xmlstreamelement, #xmlel{name = <<"t:a">>,
213 | children=[#xmlel{name = <<"b">>,
214 | attrs=[{<<"xmlns">>, <<"ns2">>}, {<<"xmlns:s">>, <<"ns2">>}],
215 | children=[#xmlel{name = <<"c">>,
216 | attrs=[{<<"xmlns">>, <<"ns0">>}]}]}]}},
217 | {xmlstreamelement, #xmlel{name = <<"prefix:n1">>}},
218 | {xmlstreamelement, #xmlel{name = <<"prefix:n1">>}},
219 | {xmlstreamelement, #xmlel{name = <<"a">>, attrs=[{<<"xmlns">>, <<"ns5">>}],
220 | children=[#xmlel{name = <<"b">>}, #xmlel{name = <<"t:c">>}]}},
221 | {xmlstreamelement, #xmlel{name = <<"n2">>, attrs=[{<<"xmlns">>, <<"2">>}]}},
222 | {xmlstreamelement, #xmlel{name = <<"t:n3">>}},
223 | {xmlstreamelement, #xmlel{name = <<"n4">>, attrs=[{<<"xmlns">>, <<"3">>}, {<<"xmlns:v">>, <<"3">>}]}},
224 | {xmlstreamelement, #xmlel{name = <<"n5">>, attrs=[{<<"xmlns">>, <<"n4">>}, {<<"xmlns:prefix">>, <<"n4">>}],
225 | children=[#xmlel{name = <<"e1">>, attrs=[{<<"xmlns">>, <<"ns0">>}]},
226 | #xmlel{name = <<"e2">>}]}},
227 | {xmlstreamelement, #xmlel{name = <<"b">>,
228 | children = [{xmlcdata, <<"cdata">>}]}},
229 | {xmlstreamend, <<"prefix:root">>}],
230 | collect_events(CallbackPid)).
231 |
232 | stream_reset_test() ->
233 | parser_loop([{<<"">>,
234 | [{xmlstreamstart, <<"a">>, [{<<"xmlns">>, <<"ns1">>}]},
235 | {xmlstreamelement, #xmlel{name = <<"b">>}}]},
236 | reset,
237 | {<<"">>,
238 | [{xmlstreamstart, <<"a">>, []},
239 | {xmlstreamelement, #xmlel{name = <<"b">>}},
240 | {xmlstreamend, <<"a">>}]}
241 | ]).
242 |
243 | stream_error_test() ->
244 | CallbackPid = spawn_link(fun() -> receiver([]) end),
245 | S0 = new(CallbackPid),
246 | S1 = fxml_stream:parse(S0, <<"a">>),
247 | close(S1),
248 | ?assertEqual(
249 | [{xmlstreamstart, <<"a">>, []},
250 | {xmlstreamerror, {7, <<"mismatched tag">>}}],
251 | collect_events(CallbackPid)).
252 |
253 | stream_with_joined_cdata_test() ->
254 | CallbackPid = spawn_link(fun() -> receiver([]) end),
255 | Stream0 = new(CallbackPid),
256 | Data = [<<"">>, <<"">>, <<"1">>, <<"2">>, <<"3">>,
257 | <<"">>, <<"">>],
258 | StreamN = lists:foldl(
259 | fun(Chunk, Stream) ->
260 | fxml_stream:parse(Stream, Chunk)
261 | end, Stream0, Data),
262 | close(StreamN),
263 | ?assertEqual(
264 | [{xmlstreamstart, <<"root">>, []},
265 | {xmlstreamelement, #xmlel{name = <<"a">>,
266 | children = [{xmlcdata, <<"123">>}]}},
267 | {xmlstreamend, <<"root">>}],
268 | collect_events(CallbackPid)).
269 |
270 | splitted_stream_test() ->
271 | CallbackPid = spawn_link(fun() -> receiver([]) end),
272 | Stream0 = new(CallbackPid),
273 | Stream1 = fxml_stream:parse(Stream0, <<">),
274 | ?assertEqual([], collect_events(CallbackPid)),
275 | Stream2 = fxml_stream:parse(Stream1, <<">">>),
276 | ?assertEqual([{xmlstreamstart, <<"root">>, []}],
277 | collect_events(CallbackPid)),
278 | Stream3 = fxml_stream:parse(Stream2, <<">}},
280 | {xmlstreamelement, #xmlel{name = <<"b">>}}],
281 | collect_events(CallbackPid)),
282 | Stream4 = fxml_stream:parse(Stream3, <<"'1'>">>),
283 | ?assertEqual([{xmlstreamelement, #xmlel{name = <<"c">>,
284 | attrs = [{<<"attr">>, <<"1">>}]}}],
285 | collect_events(CallbackPid)),
286 | Stream5 = fxml_stream:parse(Stream4, <<"">>),
287 | ?assertEqual([], collect_events(CallbackPid)),
288 | Stream6 = fxml_stream:parse(Stream5, <<"">>),
289 | ?assertEqual([{xmlstreamend, <<"root">>}], collect_events(CallbackPid)),
290 | close(Stream6).
291 |
292 | map_stream_test() ->
293 | parser_loop(infinity, [use_maps], [
294 | {<<"">>,
295 | [#{'__struct__' => 'Elixir.FastXML.StreamStart',
296 | attrs => #{},
297 | name => <<"a">>},
298 | #{'__struct__' => 'Elixir.FastXML.El',
299 | attrs => #{},
300 | children => [],
301 | name => <<"b">>}]},
302 | reset,
303 | {<<"">>,
304 | [#{'__struct__' => 'Elixir.FastXML.StreamStart',
305 | attrs => #{},
306 | name => <<"a">>},
307 | #{'__struct__' => 'Elixir.FastXML.El',
308 | attrs => #{<<"c">> => <<"1">>},
309 | children => [],
310 | name => <<"b">>},
311 | #{'__struct__' => 'Elixir.FastXML.StreamEnd',
312 | name => <<"a">>}]}
313 | ]).
314 |
315 | streaming_mismatched_tags_error_test() ->
316 | parser_loop([{<<">, []},
317 | {<<">">>, [{xmlstreamstart, <<"root">>, []}]},
318 | {<<">}},
320 | {xmlstreamelement, #xmlel{name = <<"b">>}}]},
321 | {<<"'1'>">>,
322 | [{xmlstreamelement, #xmlel{name = <<"c">>,
323 | attrs = [{<<"attr">>, <<"1">>}]}}]},
324 | {<<"">>,
325 | [{xmlstreamerror,{7,<<"mismatched tag">>}}]}
326 | ]).
327 |
328 | streaming_invalid_attribute_error_test() ->
329 | parser_loop([{<<">, []},
330 | {<<">">>, [{xmlstreamstart, <<"root">>, []}]},
331 | {<<">}},
333 | {xmlstreamelement, #xmlel{name = <<"b">>}}]},
334 | {<<"'1>">>,
335 | [{xmlstreamerror,{4,<<"not well-formed (invalid token)">>}}]}
336 | ]).
337 |
338 | xmpp_stream_test() ->
339 | parser_loop([{<<"",
340 | "">>,
343 | [{xmlstreamstart,<<"stream:stream">>,
344 | [{<<"xmlns:stream">>,<<"http://etherx.jabber.org/streams">>},
345 | {<<"to">>,<<"server.com">>},
346 | {<<"version">>,<<"1.0">>},
347 | {<<"xml:lang">>, <<"pl">>}]}]},
348 | {<<"",
349 | "",
350 | "">>,
351 | [{xmlstreamelement,
352 | {xmlel,<<"stream:features">>,
353 | [],
354 | [{xmlel,<<"register">>,
355 | [{<<"xmlns">>,<<"http://jabber.org/features/iq-register">>}],
356 | []}]}}]},
357 | {<<"">>,
358 | [{xmlstreamelement,
359 | {xmlel,<<"a">>,
360 | [{<<"xmlns">>,<<"b">>}, {<<"xmlns:a">>,<<"b">>}],
361 | [{xmlel,<<"c">>,[], []}]}}]},
362 | reset,
363 | {<<"",
364 | "">>,
367 | [{xmlstreamstart,<<"stream:stream">>,
368 | [{<<"xmlns">>,<<"jabber:client">>},
369 | {<<"xmlns:stream">>,<<"http://etherx.jabber.org/streams">>},
370 | {<<"to">>,<<"server.com">>},
371 | {<<"version">>,<<"1.0">>},
372 | {<<"xml:lang">>, <<"pl">>}]}]},
373 | reset,
374 | {<<"",
375 | "">>,
378 | [{xmlstreamstart, <<"stream:stream">>,
379 | [{<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>},
380 | {<<"xmlns">>, <<"jabber:client">>},
381 | {<<"to">>, <<"server.com">>},
382 | {<<"version">>, <<"1.0">>},
383 | {<<"xml:lang">>, <<"pl">>}]}]}
384 | ]).
385 |
386 | too_big_test() ->
387 | CallbackPid = spawn_link(fun() -> receiver([]) end),
388 | Stream0 = new(CallbackPid, 5),
389 | Stream1 = fxml_stream:parse(Stream0, <<"">>),
390 | Stream2 = fxml_stream:parse(Stream1, <<"">>),
391 | Stream3 = fxml_stream:parse(Stream2, <<"">>),
392 | Stream4 = fxml_stream:parse(Stream3, <<"">>),
393 | ?assertEqual([{xmlstreamstart, <<"a">>, []},
394 | {xmlstreamelement, #xmlel{name = <<"b">>}},
395 | {xmlstreamelement, #xmlel{name = <<"c">>}},
396 | {xmlstreamerror, <<"XML stanza is too big">>}],
397 | collect_events(CallbackPid)),
398 | close(Stream4).
399 |
400 | too_big_with_data_after_test() ->
401 | parser_loop(30, [],
402 | [{
403 | <<"">>,
404 | [{xmlstreamstart, <<"start">>, []}]
405 | },
406 | {
407 | <<">,
408 | []
409 | },
410 | {
411 | <<"<1234567890123456790123456789012345678901234567890/>">>,
412 | [{xmlstreamerror, <<"XML stanza is too big">>}]
413 | },
414 | {
415 | <<"z='1'/>">>,
416 | [{xmlstreamerror, <<"XML stanza is too big">>}]
417 | },
418 | {
419 | <<"">>,
420 | [{xmlstreamerror, <<"XML stanza is too big">>}]
421 | }]).
422 |
423 | close_close_test() ->
424 | Stream = new(),
425 | close(Stream),
426 | ?assertError(badarg, fxml_stream:close(Stream)).
427 |
428 | close_parse_test() ->
429 | Stream = new(),
430 | close(Stream),
431 | ?assertError(badarg, fxml_stream:parse(Stream, <<"junk">>)).
432 |
433 | close_change_callback_pid_test() ->
434 | Stream = new(),
435 | close(Stream),
436 | ?assertError(badarg, fxml_stream:change_callback_pid(Stream, self())).
437 |
438 | change_callback_pid_test() ->
439 | Pid1 = spawn_link(fun() -> receiver([]) end),
440 | Pid2 = spawn_link(fun() -> receiver([]) end),
441 | Stream0 = new(Pid1),
442 | Stream1 = fxml_stream:parse(Stream0, <<"">>),
443 | ?assertEqual([{xmlstreamstart, <<"root">>, []}],
444 | collect_events(Pid1)),
445 | Stream2 = fxml_stream:change_callback_pid(Stream1, Pid2),
446 | Stream3 = fxml_stream:parse(Stream2, <<"">>),
447 | ?assertEqual([{xmlstreamend, <<"root">>}],
448 | collect_events(Pid2)),
449 | close(Stream3).
450 |
451 | badarg_new_test() ->
452 | ?assertError(badarg, fxml_stream:new(1)),
453 | ?assertError(badarg, fxml_stream:new(self(), unlimited)),
454 | ?assertError(badarg, fxml_stream:new(foo, fun() -> ok end)).
455 |
456 | badarg_parse_test() ->
457 | Stream = new(),
458 | ?assertError(badarg, fxml_stream:parse(1, <<"">>)),
459 | ?assertError(badarg, fxml_stream:parse(<<>>, "")),
460 | ?assertError(badarg, fxml_stream:parse(Stream, blah)),
461 | ?assertError(badarg, fxml_stream:parse(foo, fun() -> ok end)),
462 | close(Stream).
463 |
464 | badarg_change_callback_pid_test() ->
465 | Stream = new(),
466 | ?assertError(badarg, fxml_stream:change_callback_pid(1, self())),
467 | ?assertError(badarg, fxml_stream:change_callback_pid(<<>>, self())),
468 | ?assertError(badarg, fxml_stream:change_callback_pid(Stream, foo)),
469 | ?assertError(badarg, fxml_stream:change_callback_pid(foo, fun() -> ok end)),
470 | close(Stream).
471 |
472 | badarg_close_test() ->
473 | Stream = new(),
474 | ?assertError(badarg, fxml_stream:close(1)),
475 | ?assertError(badarg, fxml_stream:close(<<>>)),
476 | close(Stream).
477 |
478 | badarg_parse_element_test() ->
479 | ?assertError(badarg, fxml_stream:parse_element(1)).
480 |
481 | parse_error_test() ->
482 | L = ["<", "<>", ">", ">", "/>",
483 | "", "x/>", "junk",
484 | "", "", "",
485 | "", ""],
486 | lists:foreach(
487 | fun(S) ->
488 | ?assertMatch({error, _}, fxml_stream:parse_element(list_to_binary(S)))
489 | end, L).
490 |
491 | dead_pid_test() ->
492 | CallbackPid = spawn(fun() -> receiver([]) end),
493 | ?assertEqual(true, exit(CallbackPid, kill)),
494 | ?assertEqual(false, is_process_alive(CallbackPid)),
495 | Stream0 = new(CallbackPid),
496 | Stream1 = fxml_stream:parse(Stream0, <<"">>),
497 | close(Stream1).
498 |
499 | huge_element_test_() ->
500 | {timeout, 60,
501 | fun() ->
502 | Tags = [list_to_binary(["a", integer_to_list(I)])
503 | || I <- lists:seq(1, 100000)],
504 | Data = ["", [[$<, Tag, "/>"] || Tag <- Tags], ""],
505 | Els = #xmlel{name = <<"root">>,
506 | children = [#xmlel{name = Tag} || Tag <- Tags]},
507 | ?assertEqual(Els, fxml_stream:parse_element(iolist_to_binary(Data)))
508 | end}.
509 |
510 | many_stream_elements_test_() ->
511 | {timeout, 60,
512 | fun() ->
513 | CallbackPid = spawn(fun() -> receiver([]) end),
514 | Stream0 = new(CallbackPid),
515 | Stream1 = fxml_stream:parse(Stream0, <<"">>),
516 | ?assertEqual([{xmlstreamstart, <<"root">>, []}],
517 | collect_events(CallbackPid)),
518 | Stream2 = lists:foldl(
519 | fun(I, Stream) ->
520 | Tag = list_to_binary(["a", integer_to_list(I)]),
521 | NewStream = fxml_stream:parse(Stream, iolist_to_binary([$<, Tag, "/>"])),
522 | ?assertEqual([{xmlstreamelement, #xmlel{name = Tag}}],
523 | collect_events(CallbackPid)),
524 | NewStream
525 | end, Stream1, lists:seq(1, 100000)),
526 | close(Stream2)
527 | end}.
528 |
529 | billionlaughs_test() ->
530 | Data = <<
531 | " ",
532 | " ",
533 | " ",
534 | " ",
535 | " ",
536 | " ",
537 | " ",
538 | " ",
539 | " ",
540 | " ",
541 | "] >\n">>,
543 | CallbackPid = spawn_link(fun() -> receiver([]) end),
544 | Stream0 = new(CallbackPid),
545 | Stream1 = fxml_stream:parse(Stream0, Data),
546 | close(Stream1),
547 | ?assertMatch([{xmlstreamerror, _}], collect_events(CallbackPid)).
548 |
549 | element_to_binary_entities_test() ->
550 | S = <<"a<>&"'b">>,
551 | E = #xmlel{name = <<"a">>,
552 | attrs = [{<<"b">>, <<"a\t\n\r<>&\"'b">>}],
553 | children = [{xmlcdata, <<"a<>&\"'b">>}]},
554 | R = fxml:element_to_binary(E),
555 | ?assertEqual(S, R),
556 | ?assertEqual(E, fxml_stream:parse_element(R)).
557 |
558 | element_to_binary_resize_test() ->
559 | A = #xmlel{
560 | name = <<"a">>,
561 | children = [{xmlcdata, <<"1234567890123456790123456789012345678901234567890">>}]
562 | },
563 |
564 | ?assertEqual(
565 | <<"",
566 | "1234567890123456790123456789012345678901234567890"
567 | "1234567890123456790123456789012345678901234567890"
568 | "1234567890123456790123456789012345678901234567890"
569 | "1234567890123456790123456789012345678901234567890"
570 | "1234567890123456790123456789012345678901234567890"
571 | "1234567890123456790123456789012345678901234567890"
572 | "1234567890123456790123456789012345678901234567890"
573 | "1234567890123456790123456789012345678901234567890"
574 | "1234567890123456790123456789012345678901234567890"
575 | "1234567890123456790123456789012345678901234567890"
576 | "1234567890123456790123456789012345678901234567890"
577 | "1234567890123456790123456789012345678901234567890"
578 | "1234567890123456790123456789012345678901234567890"
579 | "1234567890123456790123456789012345678901234567890"
580 | "1234567890123456790123456789012345678901234567890"
581 | "1234567890123456790123456789012345678901234567890"
582 | "1234567890123456790123456789012345678901234567890"
583 | "1234567890123456790123456789012345678901234567890"
584 | "1234567890123456790123456789012345678901234567890"
585 | "1234567890123456790123456789012345678901234567890"
586 | ""
587 | >>,
588 | fxml:element_to_binary(
589 | #xmlel{name = <<"t">>,
590 | children = [A, A, A, A, A,
591 | A, A, A, A, A,
592 | A, A, A, A, A,
593 | A, A, A, A, A]
594 | })).
595 |
596 | element_to_binary_test() ->
597 | ?assertEqual(
598 | <<""
600 | "">>,
601 | fxml:element_to_binary(
602 | #xmlel{name = <<"iq">>,
603 | attrs = [{<<"from">>,<<"hag66@shakespeare.lit/pda">>},
604 | {<<"id">>,<<"ik3vs715">>},
605 | {<<"to">>,<<"coven@chat.shakespeare.lit">>},
606 | {<<"type">>,<<"get">>}],
607 | children = [#xmlel{name = <<"query">>,
608 | attrs = [{<<"xmlns">>,
609 | <<"http://jabber.org/protocol/disco#info">>}],
610 | children = []}]})).
611 |
612 | element_to_header_test() ->
613 | ?assertEqual(
614 | <<"">>,
618 | fxml:element_to_header(
619 | #xmlel{name = <<"stream:stream">>,
620 | attrs = [{<<"xmlns:stream">>,<<"http://etherx.jabber.org/streams">>},
621 | {<<"xmlns">>,<<"jabber:server">>},
622 | {<<"xmlns:db">>,<<"jabber:server:dialback">>},
623 | {<<"id">>,<<"4774242664715222330">>},
624 | {<<"version">>,<<"1.0">>}]})).
625 |
626 | element_to_header_xmlcdata_test() ->
627 | ?assertError(badarg, fxml:element_to_header({xmlcdata, <<"">>})).
628 |
629 | crypt_test() ->
630 | ?assertEqual(
631 | <<"a&b<c>d"e'f">>,
632 | fxml:crypt(<<"a&bd\"e\'f">>)).
633 |
634 | remove_cdata_test() ->
635 | ?assertEqual(
636 | [#xmlel{name = <<"b">>}],
637 | fxml:remove_cdata(
638 | [{xmlcdata, <<"x">>},
639 | {xmlcdata, <<"y">>},
640 | #xmlel{name = <<"b">>},
641 | {xmlcdata, <<"z">>}])).
642 |
643 | remove_subtags_test() ->
644 | ?assertMatch(
645 | #xmlel{name = <<"root">>,
646 | children = [#xmlel{name = <<"2">>,
647 | attrs = [{<<"n1">>, <<"v1">>}]},
648 | #xmlel{name = <<"1">>,
649 | attrs = [{<<"n1">>, <<"v2">>}]},
650 | #xmlel{name = <<"1">>,
651 | attrs = [{<<"n2">>, <<"v1">>}]},
652 | #xmlel{name = <<"3">>}]},
653 | fxml:remove_subtags(
654 | #xmlel{name = <<"root">>,
655 | children = [#xmlel{name = <<"1">>,
656 | attrs = [{<<"n1">>, <<"v1">>}]},
657 | #xmlel{name = <<"2">>,
658 | attrs = [{<<"n1">>, <<"v1">>}]},
659 | #xmlel{name = <<"1">>,
660 | attrs = [{<<"n1">>, <<"v2">>}]},
661 | #xmlel{name = <<"1">>,
662 | attrs = [{<<"n2">>, <<"v1">>}]},
663 | #xmlel{name = <<"1">>,
664 | attrs = [{<<"n1">>, <<"v1">>}]},
665 | #xmlel{name = <<"3">>}]},
666 | <<"1">>, {<<"n1">>, <<"v1">>})).
667 |
668 | get_cdata_test() ->
669 | ?assertEqual(
670 | <<"xyz">>,
671 | fxml:get_cdata(
672 | [{xmlcdata, <<"x">>},
673 | {xmlcdata, <<"y">>},
674 | #xmlel{name = <<"b">>},
675 | {xmlcdata, <<"z">>}])).
676 |
677 | get_tag_cdata_test() ->
678 | ?assertEqual(
679 | <<"xyz">>,
680 | fxml:get_tag_cdata(
681 | #xmlel{name = <<"a">>,
682 | children = [{xmlcdata, <<"x">>},
683 | {xmlcdata, <<"y">>},
684 | #xmlel{name = <<"b">>},
685 | {xmlcdata, <<"z">>}]})).
686 |
687 | get_attr_test() ->
688 | ?assertEqual(
689 | {value, <<"2">>},
690 | fxml:get_attr(
691 | <<"y">>,
692 | [{<<"x">>, <<"1">>},
693 | {<<"y">>, <<"2">>},
694 | {<<"z">>, <<"3">>}])).
695 |
696 | get_attr_empty_test() ->
697 | ?assertEqual(
698 | false,
699 | fxml:get_attr(
700 | <<"a">>,
701 | [{<<"x">>, <<"1">>},
702 | {<<"y">>, <<"2">>},
703 | {<<"z">>, <<"3">>}])).
704 |
705 | get_attr_s_test() ->
706 | ?assertEqual(
707 | <<"2">>,
708 | fxml:get_attr_s(
709 | <<"y">>,
710 | [{<<"x">>, <<"1">>},
711 | {<<"y">>, <<"2">>},
712 | {<<"z">>, <<"3">>}])).
713 |
714 | get_attr_s_empty_test() ->
715 | ?assertEqual(
716 | <<"">>,
717 | fxml:get_attr_s(
718 | <<"a">>,
719 | [{<<"x">>, <<"1">>},
720 | {<<"y">>, <<"2">>},
721 | {<<"z">>, <<"3">>}])).
722 |
723 | get_tag_attr_test() ->
724 | ?assertEqual(
725 | {value, <<"2">>},
726 | fxml:get_tag_attr(
727 | <<"y">>,
728 | #xmlel{name = <<"foo">>,
729 | attrs = [{<<"x">>, <<"1">>},
730 | {<<"y">>, <<"2">>},
731 | {<<"z">>, <<"3">>}]})).
732 |
733 | get_tag_attr_empty_test() ->
734 | ?assertEqual(
735 | false,
736 | fxml:get_tag_attr(
737 | <<"a">>,
738 | #xmlel{name = <<"foo">>,
739 | attrs = [{<<"x">>, <<"1">>},
740 | {<<"y">>, <<"2">>},
741 | {<<"z">>, <<"3">>}]})).
742 |
743 | get_tag_attr_s_test() ->
744 | ?assertEqual(
745 | <<"2">>,
746 | fxml:get_tag_attr_s(
747 | <<"y">>,
748 | #xmlel{name = <<"foo">>,
749 | attrs = [{<<"x">>, <<"1">>},
750 | {<<"y">>, <<"2">>},
751 | {<<"z">>, <<"3">>}]})).
752 |
753 | get_tag_attr_s_empty_test() ->
754 | ?assertEqual(
755 | <<"">>,
756 | fxml:get_tag_attr_s(
757 | <<"a">>,
758 | #xmlel{name = <<"foo">>,
759 | attrs = [{<<"x">>, <<"1">>},
760 | {<<"y">>, <<"2">>},
761 | {<<"z">>, <<"3">>}]})).
762 |
763 | get_subtag_test() ->
764 | ?assertMatch(
765 | #xmlel{name = <<"2">>},
766 | fxml:get_subtag(
767 | #xmlel{name = <<"root">>,
768 | children = [#xmlel{name = <<"1">>},
769 | #xmlel{name = <<"2">>},
770 | #xmlel{name = <<"3">>}]},
771 | <<"2">>)).
772 |
773 | get_subtag_false_test() ->
774 | ?assertMatch(
775 | false,
776 | fxml:get_subtag(
777 | #xmlel{name = <<"root">>,
778 | children = [#xmlel{name = <<"1">>},
779 | #xmlel{name = <<"2">>},
780 | #xmlel{name = <<"3">>}]},
781 | <<"4">>)).
782 |
783 | get_subtags_test() ->
784 | ?assertMatch(
785 | [#xmlel{name = <<"1">>, attrs = [{<<"a">>, <<"b">>}]},
786 | #xmlel{name = <<"1">>, attrs = [{<<"x">>, <<"y">>}]}],
787 | fxml:get_subtags(
788 | #xmlel{name = <<"root">>,
789 | children = [#xmlel{name = <<"1">>,
790 | attrs = [{<<"a">>, <<"b">>}]},
791 | #xmlel{name = <<"2">>},
792 | #xmlel{name = <<"3">>},
793 | #xmlel{name = <<"1">>,
794 | attrs = [{<<"x">>, <<"y">>}]}]},
795 | <<"1">>)).
796 |
797 | get_subtags_empty_test() ->
798 | ?assertEqual(
799 | [],
800 | fxml:get_subtags(
801 | #xmlel{name = <<"root">>,
802 | children = [#xmlel{name = <<"1">>},
803 | #xmlel{name = <<"2">>},
804 | #xmlel{name = <<"3">>}]},
805 | <<"4">>)).
806 |
807 | get_subtag_with_xmlns_test() ->
808 | ?assertMatch(
809 | #xmlel{name = <<"2">>,
810 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
811 | fxml:get_subtag_with_xmlns(
812 | #xmlel{name = <<"root">>,
813 | children = [#xmlel{name = <<"1">>,
814 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
815 | #xmlel{name = <<"2">>,
816 | attrs = [{<<"xmlns">>, <<"ns2">>}]},
817 | #xmlel{name = <<"2">>,
818 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
819 | #xmlel{name = <<"3">>,
820 | attrs = [{<<"xmlns">>, <<"ns2">>}]}]},
821 | <<"2">>, <<"ns1">>)).
822 |
823 | get_subtag_with_xmlns_empty_test() ->
824 | ?assertMatch(
825 | false,
826 | fxml:get_subtag_with_xmlns(
827 | #xmlel{name = <<"root">>,
828 | children = [#xmlel{name = <<"1">>,
829 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
830 | #xmlel{name = <<"2">>,
831 | attrs = [{<<"xmlns">>, <<"ns2">>}]},
832 | #xmlel{name = <<"2">>,
833 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
834 | #xmlel{name = <<"3">>,
835 | attrs = [{<<"xmlns">>, <<"ns2">>}]}]},
836 | <<"4">>, <<"ns2">>)).
837 |
838 | get_subtags_with_xmlns_test() ->
839 | ?assertMatch(
840 | [#xmlel{name = <<"2">>,
841 | attrs = [{<<"xmlns">>, <<"ns1">>}],
842 | children = [{xmlcdata, <<"foo">>}]},
843 | #xmlel{name = <<"2">>,
844 | attrs = [{<<"xmlns">>, <<"ns1">>}],
845 | children = [{xmlcdata, <<"bar">>}]}],
846 | fxml:get_subtags_with_xmlns(
847 | #xmlel{name = <<"root">>,
848 | children = [#xmlel{name = <<"1">>,
849 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
850 | #xmlel{name = <<"2">>,
851 | children = [{xmlcdata, <<"foo">>}],
852 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
853 | #xmlel{name = <<"2">>,
854 | attrs = [{<<"xmlns">>, <<"ns2">>}]},
855 | #xmlel{name = <<"2">>,
856 | children = [{xmlcdata, <<"bar">>}],
857 | attrs = [{<<"xmlns">>, <<"ns1">>}]},
858 | #xmlel{name = <<"3">>,
859 | attrs = [{<<"xmlns">>, <<"ns2">>}]}]},
860 | <<"2">>, <<"ns1">>)).
861 |
862 | get_subtag_cdata_test() ->
863 | ?assertEqual(
864 | <<"ab">>,
865 | fxml:get_subtag_cdata(
866 | #xmlel{name = <<"root">>,
867 | children = [#xmlel{name = <<"1">>,
868 | children = [{xmlcdata, <<"a">>},
869 | #xmlel{name = <<"3">>},
870 | {xmlcdata, <<"b">>}]},
871 | #xmlel{name = <<"2">>}]},
872 | <<"1">>)).
873 |
874 | get_subtag_cdata_empty_test() ->
875 | ?assertEqual(
876 | <<"">>,
877 | fxml:get_subtag_cdata(
878 | #xmlel{name = <<"root">>,
879 | children = [#xmlel{name = <<"2">>}]},
880 | <<"1">>)).
881 |
882 | append_subtags_test() ->
883 | ?assertMatch(
884 | #xmlel{name = <<"root">>,
885 | children = [#xmlel{name = <<"1">>},
886 | #xmlel{name = <<"2">>},
887 | #xmlel{name = <<"3">>}]},
888 | fxml:append_subtags(
889 | #xmlel{name = <<"root">>,
890 | children = [#xmlel{name = <<"1">>}]},
891 | [#xmlel{name = <<"2">>}, #xmlel{name = <<"3">>}])).
892 |
893 | get_path_s_tag_test() ->
894 | ?assertMatch(
895 | #xmlel{name = <<"2">>},
896 | fxml:get_path_s(
897 | #xmlel{name = <<"root">>,
898 | children = [#xmlel{name = <<"1">>},
899 | #xmlel{name = <<"2">>}]},
900 | [{elem, <<"2">>}])).
901 |
902 | get_path_s_empty_tag_test() ->
903 | ?assertEqual(
904 | <<"">>,
905 | fxml:get_path_s(
906 | #xmlel{name = <<"root">>,
907 | children = [#xmlel{name = <<"1">>},
908 | #xmlel{name = <<"2">>}]},
909 | [{elem, <<"3">>}])).
910 |
911 | get_path_s_attr_test() ->
912 | ?assertEqual(
913 | <<"v1">>,
914 | fxml:get_path_s(
915 | #xmlel{name = <<"root">>,
916 | children = [#xmlel{name = <<"a">>,
917 | children =
918 | [#xmlel{name = <<"a1">>,
919 | attrs = [{<<"x">>, <<"y">>},
920 | {<<"n1">>, <<"v1">>}]},
921 | #xmlel{name = <<"b">>}]},
922 | #xmlel{name = <<"b">>}]},
923 | [{elem, <<"a">>}, {elem, <<"a1">>}, {attr, <<"n1">>}])).
924 |
925 | get_path_s_cdata_test() ->
926 | ?assertEqual(
927 | <<"d1">>,
928 | fxml:get_path_s(
929 | #xmlel{name = <<"root">>,
930 | children = [#xmlel{name = <<"a">>,
931 | children = [#xmlel{name = <<"a1">>},
932 | {xmlcdata, <<"d1">>}]},
933 | #xmlel{name = <<"b">>}]},
934 | [{elem, <<"a">>}, cdata])).
935 |
936 | replace_tag_attr_test() ->
937 | ?assertMatch(
938 | #xmlel{name = <<"foo">>,
939 | attrs = [{<<"2">>, <<"d">>},
940 | {<<"1">>, <<"a">>},
941 | {<<"2">>, <<"c">>}]},
942 | fxml:replace_tag_attr(
943 | <<"2">>, <<"d">>,
944 | #xmlel{name = <<"foo">>,
945 | attrs = [{<<"1">>, <<"a">>},
946 | {<<"2">>, <<"b">>},
947 | {<<"2">>, <<"c">>}]})).
948 |
949 | replace_subtag_test() ->
950 | ?assertMatch(
951 | #xmlel{name = <<"root">>,
952 | children = [#xmlel{name = <<"2">>, children = []},
953 | #xmlel{name = <<"1">>},
954 | #xmlel{name = <<"2">>,
955 | children = [{xmlcdata, <<"b">>}]}]},
956 | fxml:replace_subtag(
957 | #xmlel{name = <<"2">>},
958 | #xmlel{name = <<"root">>,
959 | children = [#xmlel{name = <<"1">>},
960 | #xmlel{name = <<"2">>,
961 | children = [{xmlcdata, <<"a">>}]},
962 | #xmlel{name = <<"2">>,
963 | children =[{xmlcdata, <<"b">>}]}]})).
964 |
965 | to_xmlel_test() ->
966 | ?assertEqual(
967 | #xmlel{name = <<"foo">>,
968 | attrs = [{<<"a">>, <<"b">>}],
969 | children = [{xmlcdata, <<"xyz">>}]},
970 | fxml:to_xmlel({xmlelement, "foo", [{"a", "b"}], [{xmlcdata, "xyz"}]})).
971 |
972 | rpc_fault_test() ->
973 | Fault = {xmlel,<<"methodResponse">>,[],
974 | [{xmlel,<<"fault">>,[],
975 | [{xmlel,<<"value">>,[],
976 | [{xmlel,<<"struct">>,[],
977 | [{xmlel,<<"member">>,[],
978 | [{xmlel,<<"name">>,[],[{xmlcdata,<<"faultCode">>}]},
979 | {xmlel,<<"value">>,[],
980 | [{xmlel,<<"int">>,[],[{xmlcdata,<<"4">>}]}]}]},
981 | {xmlel,<<"member">>,[],
982 | [{xmlel,<<"name">>,[],[{xmlcdata,<<"faultString">>}]},
983 | {xmlel,<<"value">>,[],
984 | [{xmlel,<<"string">>,[],
985 | [{xmlcdata,<<"Too many parameters.">>}]}]}]}]}]}]}]},
986 | Result = {response, {fault, 4, <<"Too many parameters.">>}},
987 | ?assertEqual({ok, Result}, fxmlrpc:decode(Fault)),
988 | ?assertEqual(Fault, fxmlrpc:encode(Result)).
989 |
990 | rpc_call_test() ->
991 | Call = {xmlel,<<"methodCall">>,[],
992 | [{xmlel,<<"methodName">>,[],[{xmlcdata,<<"examples.getStateName">>}]},
993 | {xmlel,<<"params">>,[],
994 | [{xmlel,<<"param">>,[],
995 | [{xmlel,<<"value">>,[],
996 | [{xmlel,<<"int">>,[],[{xmlcdata,<<"40">>}]}]}]},
997 | {xmlel,<<"param">>,[],
998 | [{xmlel,<<"value">>,[],
999 | [{xmlel,<<"int">>,[],[{xmlcdata,<<"30">>}]}]}]},
1000 | {xmlel,<<"param">>,[],
1001 | [{xmlel,<<"value">>,[],
1002 | [{xmlel,<<"boolean">>,[],[{xmlcdata,<<"0">>}]}]}]},
1003 | {xmlel,<<"param">>,[],
1004 | [{xmlel,<<"value">>,[],
1005 | [{xmlel,<<"base64">>,[],
1006 | [{xmlcdata,<<"eW91IGNhbid0IHJlYWQgdGhpcyE=">>}]}]}]},
1007 | {xmlel,<<"param">>,[],
1008 | [{xmlel,<<"value">>,[],
1009 | [{xmlel,<<"dateTime.iso8601">>,[],
1010 | [{xmlcdata,<<"19980717T14:08:55">>}]}]}]},
1011 | {xmlel,<<"param">>,[],
1012 | [{xmlel,<<"value">>,[],
1013 | [{xmlel,<<"string">>,[],
1014 | [{xmlcdata,<<"Hello world!">>}]}]}]},
1015 | {xmlel,<<"param">>,[],
1016 | [{xmlel,<<"value">>,[],[{xmlel,<<"nil">>,[],[]}]}]},
1017 | {xmlel,<<"param">>,[],
1018 | [{xmlel,<<"value">>,[],
1019 | [{xmlel,<<"array">>,[],
1020 | [{xmlel,<<"data">>,[],
1021 | [{xmlel,<<"value">>,[],
1022 | [{xmlel,<<"int">>,[],
1023 | [{xmlcdata,<<"1404">>}]}]},
1024 | {xmlel,<<"value">>,[],
1025 | [{xmlel,<<"string">>,[],
1026 | [{xmlcdata,<<"Something here">>}]}]},
1027 | {xmlel,<<"value">>,[],
1028 | [{xmlel,<<"int">>,[],
1029 | [{xmlcdata,<<"1">>}]}]}]}]}]}]},
1030 | {xmlel,<<"param">>,[],
1031 | [{xmlel,<<"value">>,[],
1032 | [{xmlel,<<"struct">>,[],
1033 | [{xmlel,<<"member">>,[],
1034 | [{xmlel,<<"name">>,[],[{xmlcdata,<<"foo">>}]},
1035 | {xmlel,<<"value">>,[],
1036 | [{xmlel,<<"int">>,[],
1037 | [{xmlcdata,<<"1">>}]}]}]},
1038 | {xmlel,<<"member">>,[],
1039 | [{xmlel,<<"name">>,[],[{xmlcdata,<<"bar">>}]},
1040 | {xmlel,<<"value">>,[],
1041 | [{xmlel,<<"int">>,[],
1042 | [{xmlcdata,<<"2">>}]}]}]}]}]}]}]}]},
1043 | Result = {call,'examples.getStateName',
1044 | [40,30,false,
1045 | {base64,<<"eW91IGNhbid0IHJlYWQgdGhpcyE=">>},
1046 | {date,<<"19980717T14:08:55">>},
1047 | <<"Hello world!">>,nil,
1048 | {array,[1404,<<"Something here">>,1]},
1049 | {struct,[{foo,1},{bar,2}]}]},
1050 | ?assertEqual({ok, Result}, fxmlrpc:decode(Call)),
1051 | ?assertEqual(Call, fxmlrpc:encode(Result)).
1052 |
1053 | response_test() ->
1054 | Response = {xmlel,<<"methodResponse">>, [],
1055 | [{xmlel,<<"params">>,[],
1056 | [{xmlel,<<"param">>,[],
1057 | [{xmlel,<<"value">>,[],
1058 | [{xmlel,<<"string">>,[],
1059 | [{xmlcdata,<<"South Dakota">>}]}]}]}]}]},
1060 | Result = {response,[<<"South Dakota">>]},
1061 | ?assertEqual({ok, Result}, fxmlrpc:decode(Response)),
1062 | ?assertEqual(Response, fxmlrpc:encode(Result)).
1063 |
1064 | rpc_empty_call_test() ->
1065 | Call = {xmlel,<<"methodCall">>,[],
1066 | [{xmlel,<<"methodName">>,[],
1067 | [{xmlcdata,<<"some_method">>}]}]},
1068 | Result = {call, some_method, []},
1069 | ?assertEqual({ok, Result}, fxmlrpc:decode(Call)),
1070 | ?assertEqual(Call, fxmlrpc:encode(Result)).
1071 |
1072 | rpc_empty_response_test() ->
1073 | Response = {xmlel,<<"methodResponse">>, [], []},
1074 | Result = {response, []},
1075 | ?assertEqual({ok, Result}, fxmlrpc:decode(Response)),
1076 | ?assertEqual(Response, fxmlrpc:encode(Result)).
1077 |
--------------------------------------------------------------------------------
/vars.config.in:
--------------------------------------------------------------------------------
1 | {cflags, "@CFLAGS@"}.
2 | {ldflags, "@LDFLAGS@ @LIBS@"}.
3 | {with_gcov, "@gcov@"}.
4 |
5 | %% Local Variables:
6 | %% mode: erlang
7 | %% End:
8 | %% vim: set filetype=erlang tabstop=8:
9 |
--------------------------------------------------------------------------------