├── .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 | [![CI](https://github.com/processone/fast_xml/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/processone/fast_xml/actions/workflows/ci.yml) 4 | [![Coverage Status](https://coveralls.io/repos/processone/fast_xml/badge.svg?branch=master&service=github)](https://coveralls.io/github/processone/fast_xml?branch=master) 5 | [![Hex version](https://img.shields.io/hexpm/v/fast_xml.svg "Hex version")](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, "'); 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 | --------------------------------------------------------------------------------