├── .editorconfig ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── docs ├── CHANGELOG.md ├── Exceptions.md ├── TODO.md └── UPGRADE-v2-v3.md └── src ├── Exceptions ├── NamespaceNotFoundInSchemas.php ├── SchemaLocationPartsNotEvenException.php ├── ValidationFailException.php ├── XmlContentIsEmptyException.php ├── XmlContentIsInvalidException.php └── XmlSchemaValidatorException.php ├── Internal └── LibXmlException.php ├── Schema.php ├── SchemaValidator.php └── Schemas.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome. We accept pull requests on [GitHub](https://github.com/eclipxe13/XmlSchemaValidator). 4 | 5 | This project adheres to a 6 | [Contributor Code of Conduct](https://github.com/eclipxe13/XmlSchemaValidator/blob/main/CODE_OF_CONDUCT.md). 7 | By participating in this project and its community, you are expected to uphold this code. 8 | 9 | ## Team members 10 | 11 | * [Carlos C Soto](https://github.com/eclipxe13) - original author and maintainer 12 | * [GitHub constributors](https://github.com/eclipxe13/XmlSchemaValidator/graphs/contributors) 13 | 14 | ## Communication Channels 15 | 16 | You can find help and discussion in the following places: 17 | 18 | * GitHub Issues: 19 | 20 | ## Reporting Bugs 21 | 22 | We track our bugs in our project's [issue tracker](https://github.com/eclipxe13/XmlSchemaValidator/issues). 23 | 24 | When submitting a bug report, please include enough information for us to reproduce the bug. 25 | A good bug report includes the following sections: 26 | 27 | * Expected outcome 28 | * Actual outcome 29 | * Steps to reproduce, including sample code 30 | * Any other information that will help us debug and reproduce the issue, including stack traces, system/environment information, and screenshots 31 | 32 | **Please do not include passwords or any personally identifiable information in your bug report and sample code.** 33 | 34 | ## Fixing Bugs 35 | 36 | We welcome pull requests to fix bugs! 37 | 38 | If you see a bug report that you'd like to fix, please feel free to do so. 39 | Following the directions and guidelines described in the "Adding New Features" 40 | section below, you may create bugfix branches and send us pull requests. 41 | 42 | ## Adding New Features 43 | 44 | If you have an idea for a new feature, it's a good idea to check out our 45 | [issues](https://github.com/eclipxe13/XmlSchemaValidator/issues) or active 46 | [pull requests](https://github.com/eclipxe13/XmlSchemaValidator/pulls) 47 | first to see if we are being working on the feature. 48 | If not, feel free to submit an issue first, asking whether the feature is beneficial to the project. 49 | This will save you from doing a lot of development work only to have your feature rejected. 50 | We don't enjoy rejecting your hard work, but some features just don't fit with the goals of the project. 51 | 52 | When you do begin working on your feature, here are some guidelines to consider: 53 | 54 | * Your pull request description should clearly detail the changes you have made. 55 | * Follow our code style using `squizlabs/php_codesniffer` and `friendsofphp/php-cs-fixer`. 56 | * Please **write tests** for any new features you add. 57 | * Please **ensure that tests pass** before submitting your pull request. Running the tests locally will help save time. 58 | * We have GitHub Actions automatically running tests for pull requests. 59 | * **Use topic/feature branches.** Please do not ask us to pull from your main branch. 60 | * **Submit one feature per pull request.** If you have multiple features you wish to submit, please break them up into separate pull requests. 61 | * **Send coherent history**. Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 62 | 63 | ## Installing development tools 64 | 65 | This project uses different development tools to ensure code style, test and quality (using code analyzers). 66 | 67 | ```shell 68 | # install project direct dependences 69 | composer install 70 | 71 | # install development tools dependences 72 | phive update 73 | ``` 74 | 75 | ## Check the code style 76 | 77 | If you are having issues with coding standars use `php-cs-fixer` and `phpcbf` 78 | 79 | ```shell 80 | # using composer 81 | composer dev:fix-style 82 | 83 | # or using tools individually 84 | tools/php-cs-fixer fix -v 85 | tools/phpcbf -sp 86 | ``` 87 | 88 | ## Running Tests 89 | 90 | The following tests must pass before we will accept a pull request. 91 | If any of these do not pass, it will result in a complete build failure. 92 | Before you can run these, be sure to `composer install` or `composer update`. 93 | 94 | ```shell 95 | # using composer 96 | composer dev:build 97 | 98 | # or using tools individually 99 | tools/phpcs -sp 100 | tools/php-cs-fixer fix -v --dry-run 101 | vendor/bin/phpunit --testdox 102 | tools/phpstan analyze 103 | tools/psalm 104 | phpdbg -qrr tools/infection --show-mutations 105 | ``` 106 | 107 | ## Running GitHub Actions locally 108 | 109 | You can use [`act`](https://github.com/nektos/act) to run your GitHub Actions locally. 110 | As documented in [`actions/setup-php-action`](https://github.com/marketplace/actions/setup-php-action#local-testing-setup) 111 | you will need to execute the command as: 112 | 113 | ```shell 114 | act -P ubuntu-latest=shivammathur/node:latest 115 | ``` 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 - 2025 Carlos C Soto https://eclipxe.com.mx/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eclipxe/XmlSchemaValidator 2 | 3 | [![Source Code][badge-source]][source] 4 | [![Latest Version][badge-release]][release] 5 | [![Software License][badge-license]][license] 6 | [![Build Status][badge-build]][build] 7 | [![Scrutinizer][badge-quality]][quality] 8 | [![Coverage Status][badge-coverage]][coverage] 9 | [![Total Downloads][badge-downloads]][downloads] 10 | 11 | This is a library to validate XML files against multiple XSD Schemas according to its own definitions. 12 | 13 | The way this works is: 14 | 15 | 1. Receive a valid xml string as the content to be evaluated 16 | 2. Scan the file for every schemaLocation 17 | 3. Compose a schema that include all the schemas 18 | 4. Validate the XML against the composed file 19 | 20 | ## Installation 21 | 22 | Use [composer](https://getcomposer.org/), so please run 23 | ```shell 24 | composer require eclipxe/xmlschemavalidator 25 | ``` 26 | 27 | ## Basic usage 28 | 29 | ```php 30 | validate()) { 38 | echo 'Found error: ' . $validator->getLastError(); 39 | } 40 | ``` 41 | 42 | ## Advanced usage 43 | 44 | ```php 45 | load('example.xml'); 55 | $validator = new SchemaValidator($document); 56 | 57 | // change schemas collection to override the schema location of a specific namespace 58 | $schemas = $validator->buildSchemas(); 59 | $schemas->create('http://example.org/schemas/x1', './local-schemas/x1.xsd'); 60 | 61 | // validateWithSchemas does not return boolean, it throws an exception 62 | try { 63 | $validator->validateWithSchemas($schemas); 64 | } catch (ValidationFailException $ex) { 65 | echo 'Found error: ' . $ex->getMessage(); 66 | $previous = $ex->getPrevious(); 67 | if ($previous instanceof LibXmlException) { 68 | foreach ($previous->getErrors() as $libXmlError) { 69 | echo $libXmlError->message, PHP_EOL, 70 | 'File: ', $libXmlError->file, ':', $libXmlError->line, ',', $libXmlError->column, PHP_EOL; 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | On previous example, the class `LibXmlException` is *internal*. 77 | It will be *public (not internal)* on next major release. 78 | 79 | ## Exceptions 80 | 81 | This library creates its own specific exceptions and all of them implements `XmlSchemaValidatorException`. 82 | Check the [exceptions' documentation](docs/Exceptions.md) for more information. 83 | 84 | When this library uses LibXML functions, it captures the errors and throw its own exception. 85 | 86 | ## Version 1.x is deprecated 87 | 88 | Version 1.x is no longer on development. It has a problem of concerns, the same library try to solve two different 89 | issues: Validate an XML file and store locally a copy of the XSD files. 90 | Version 2.x breaks this problem and give this library only one propose: 91 | Validate an XML file against its multiple XSD files, it does not matter where are located. 92 | 93 | ## Version 2.x is deprecated 94 | 95 | [Migration changes between version 2 and version 3](docs/UPGRADE-v2-v3.md) 96 | 97 | Version 2.x was compatible with PHP 7 and was deprecated on 2020-04-05. 98 | 99 | A branch `2.x` has been created, it might be installable using `composer require eclipxe/xmlschemavalidator:2.x-dev`, 100 | but it will not be active maintained. You should change your dependency as soon as possible. 101 | 102 | ## Contributing 103 | 104 | Contributions are welcome! Please read [CONTRIBUTING][] for details 105 | and don't forget to take a look in [TODO][] and [CHANGELOG][] files. 106 | 107 | ## Copyright and License 108 | 109 | The `eclipxe/XmlSchemaValidator` library is copyright © [Carlos C Soto](https://eclipxe.com.mx/) 110 | and licensed for use under the MIT License (MIT). Please see [LICENSE][] for more information. 111 | 112 | [contributing]: https://github.com/eclipxe13/XmlSchemaValidator/blob/main/CONTRIBUTING.md 113 | [changelog]: https://github.com/eclipxe13/XmlSchemaValidator/blob/main/docs/CHANGELOG.md 114 | [todo]: https://github.com/eclipxe13/XmlSchemaValidator/blob/main/docs/TODO.md 115 | 116 | [source]: https://github.com/eclipxe13/XmlSchemaValidator 117 | [release]: https://github.com/eclipxe13/XmlSchemaValidator/releases 118 | [license]: https://github.com/eclipxe13/XmlSchemaValidator/blob/main/LICENSE 119 | [build]: https://github.com/eclipxe13/XmlSchemaValidator/actions/workflows/build.yml?query=branch:main 120 | [quality]: https://scrutinizer-ci.com/g/eclipxe13/XmlSchemaValidator/ 121 | [coverage]: https://scrutinizer-ci.com/g/eclipxe13/XmlSchemaValidator/code-structure/main 122 | [downloads]: https://packagist.org/packages/eclipxe/xmlschemavalidator 123 | 124 | [badge-source]: https://img.shields.io/badge/source-eclipxe13/XmlSchemaValidator-blue.svg?style=flat-square 125 | [badge-release]: https://img.shields.io/github/release/eclipxe13/XmlSchemaValidator.svg?style=flat-square 126 | [badge-license]: https://img.shields.io/github/license/eclipxe13/XmlSchemaValidator.svg?style=flat-square 127 | [badge-build]: https://img.shields.io/github/actions/workflow/status/eclipxe13/XmlSchemaValidator/build.yml?branch=main&style=flat-square 128 | [badge-quality]: https://img.shields.io/scrutinizer/g/eclipxe13/XmlSchemaValidator/main.svg?style=flat-square 129 | [badge-coverage]: https://img.shields.io/scrutinizer/coverage/g/eclipxe13/XmlSchemaValidator/main.svg?style=flat-square 130 | [badge-downloads]: https://img.shields.io/packagist/dt/eclipxe/xmlschemavalidator.svg?style=flat-square 131 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eclipxe/xmlschemavalidator", 3 | "description": "PHP Library for XML Schema Validations", 4 | "keywords": ["xml", "xsd", "validation", "xmlschema"], 5 | "homepage": "https://github.com/eclipxe13/XmlSchemaValidator", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Carlos C Soto", 10 | "email": "eclipxe13@gmail.com", 11 | "homepage": "https://eclipxe.com.mx/" 12 | } 13 | ], 14 | "support": { 15 | "source": "https://github.com/eclipxe13/XmlSchemaValidator", 16 | "issues": "https://github.com/eclipxe13/XmlSchemaValidator/issues" 17 | }, 18 | "require": { 19 | "php": ">=7.3", 20 | "ext-dom": "*", 21 | "ext-libxml": "*" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^9.5.5" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Eclipxe\\XmlSchemaValidator\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Eclipxe\\XmlSchemaValidator\\Tests\\": "tests/" 34 | } 35 | }, 36 | "scripts": { 37 | "dev:build": ["@dev:fix-style", "@dev:test"], 38 | "dev:check-style": [ 39 | "@php tools/php-cs-fixer fix --dry-run --verbose", 40 | "@php tools/phpcs --colors -sp" 41 | ], 42 | "dev:fix-style": [ 43 | "@php tools/php-cs-fixer fix --verbose", 44 | "@php tools/phpcbf --colors -sp" 45 | ], 46 | "dev:test": [ 47 | "@dev:check-style", 48 | "@php vendor/bin/phpunit --testdox --verbose --stop-on-failure", 49 | "@php tools/phpstan analyse --no-progress", 50 | "@dev:infection" 51 | ], 52 | "dev:coverage": [ 53 | "@php -dzend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit --verbose --coverage-html build/coverage/html/" 54 | ], 55 | "dev:infection": [ 56 | "@php tools/infection --initial-tests-php-options='-dzend_extension=xdebug.so -dxdebug.mode=coverage' --show-mutations --no-progress" 57 | ] 58 | }, 59 | "scripts-descriptions": { 60 | "dev:build": "DEV: run dev:fix-style dev:tests and dev:docs, run before pull request", 61 | "dev:check-style": "DEV: search for code style errors using php-cs-fixer and phpcs", 62 | "dev:fix-style": "DEV: fix code style errors using php-cs-fixer and phpcbf", 63 | "dev:test": "DEV: run dev:fix-style, dev:check-style, phpunit, phpstan and infection", 64 | "dev:coverage": "DEV: run phpunit with xdebug and storage coverage in build/coverage/html/", 65 | "dev:infection": "DEV: run mutation tests using infection" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | This library follows [SEMVER 2.0.0](https://semver.org/spec/v2.0.0.html) convention. 4 | 5 | Notice: Classes with tag `@internal` are only for internal use, you should not create instances of these 6 | classes. The library will not export any of these objects outside its own scope. 7 | 8 | ## Unreleased changes 9 | 10 | Unreleased changes will be listed here. 11 | 12 | ## Version 3.0.5 2025-02-18 13 | 14 | - Fix `ValidationFailException` signature for compatibility with PHP 8.4. 15 | - Add example on how to load `libxml` errors. 16 | - Update license year to 2025. 17 | - Remove Psalm, only use PHPStan. 18 | - Add PHP 8.4 to test matrix. 19 | - Run `phpcs` and `phpstan` on PHP 8.4. 20 | - Update development tools. 21 | 22 | ## Version 3.0.4 2024-03-08 23 | 24 | - Fix falsy comparisons (Psalm). 25 | - Update license year to 2024. 26 | - Update coding standards. 27 | - Improve GitHub workflow: 28 | - Add PHP 8.3 to test matrix. 29 | - Run jobs using PHP 8.3. 30 | - Update GitHub actions to version 4. 31 | - Update code analysis titles. 32 | - Rename matrix variable name php-version (singular). 33 | - Update development tools. 34 | 35 | ## Version 3.0.3 2022-12-19 36 | 37 | When split the content of a *schema location* value, must reindex the list of values. 38 | The following code wasn't interpreted correctly: 39 | 40 | ```xml 41 | 42 | 46 | ``` 47 | 48 | See . Thanks `@brankopetric`. 49 | 50 | ### Development changes 51 | 52 | - Add `fully_qualified_strict_types` rule to `php-cs-fixer` tool. 53 | 54 | ### Unreleased 2022-12-08 55 | 56 | This is a maintenance update that fixes continuous integration. 57 | 58 | - Fix Psalm analysis when evaluate that `DOMXPath::query()` return *falsy*. 59 | It actually cannot return `false`, the expression is never malformed or the contextNode is never invalid. 60 | - Maintenance to GitHub workflow for continuous integration. 61 | - Add PHP 8.2 to phpunit job matrix 62 | - Update GitHub actions to version 3 63 | - Run jobs on PHP 8.1 64 | - Replace `echo ::set-output` deprecated instruction 65 | - Remove composer installation where is not required 66 | - Show Psalm version (it was not visible) 67 | - Update development tools. 68 | - Exclude linguist detection on `tests/_files`. 69 | - Update code styles rules as other projects. 70 | - Implement `dependency_paths` on Scrutinizer-CI. 71 | 72 | ### Unreleased 2022-07-18 73 | 74 | This is a maintenance update. 75 | 76 | - Fix code style. 77 | - Update `php-cs-fixer` config file to recent rules. 78 | - Update development tools. 79 | - Update Scrutinizer CI to run on PHP 7.4. 80 | 81 | ## Version 3.0.2 2022-03-08 82 | 83 | Change return type on `Schemas::getIterator()` to include `Traversable`. This avoids compatibility issues with PHP 8.1. 84 | 85 | Check `DOMAttr::nodeValue` can be `null`. Remove Psalm ignore. 86 | 87 | Fix build, PHPStan ^1.4.7 does not recognize `DOMNodeList` element types. Change type to `iterable`. 88 | 89 | Update development tools to recent versions. 90 | 91 | This release includes Previous unreleased changes: 92 | 93 | - 2022-02-09: Fix broken CI. 94 | 95 | Remove unused code on test. 96 | 97 | - 2022-02-09: Maintenance. 98 | 99 | Update license year. Happy 2022. 100 | Add PHP 8.1 to test matrix. 101 | Update `psalm` config file and type annotations. 102 | Update `.gitattributes` with project structure. 103 | Improve internal web server start up for testing. 104 | 105 | - 2021-11-20: Fix broken CI. 106 | 107 | Split Continuous Integration steps into jobs. 108 | Fix issues reported by recent version of PHPStan. 109 | 110 | - 2021-09-26: Fix broken CI. 111 | 112 | Run Continuous Integration on PHP 8.0. Mutation testing was failing when running on PHP 7.4. 113 | 114 | - 2021-07-05: GitHub Actions has been failing on testing step. 115 | 116 | `SchemaValidatorTest` now is more verbose on validations, hopping this messages let me know what the problem is. 117 | This problem was unable to reproduce on local or `act`. 118 | 119 | ## Version 3.0.1 2020-06-18 120 | 121 | Source Code: 122 | 123 | Fix bug when `schemaLocation` contains `CR` or `LF`. 124 | 125 | Development environment: 126 | 127 | - Change default branch name from `master` to `main`. 128 | - Update development instructions, see *Contrib* file. 129 | - Update to Contributor Covenant Code of Conduct version 2. 130 | - Update composer scripts. 131 | - Update License year, happy new year on june. 132 | - PHP Code Sniffer: configure paths in config file. 133 | - PHPStan: configure paths in config file. 134 | - PHPUnit: upgrade to version 9.5 config file and remove verbose by default. 135 | - Psalm: ignore `UnnecessaryVarAnnotation` since it is not using PHP correct types. 136 | - Include `infection` (Mutation Testing) to build pipeline. 137 | - Migrate from Travis-CI to GitHub Actions. Thanks Travis-CI! 138 | - Scrutinizer just receive code coverage. 139 | 140 | ## Version 3.0.0 2020-04-08 141 | 142 | - Lot of breaking changes has been made, see [upgrade from version `2.x` to `3.x`](UPGRADE-v2-v3.md). 143 | - Namespace change from `\XmlSchemaValidator` to `\Eclipxe\XmlSchemaValidator`. 144 | - Now uses named exceptions, see [exceptions documentation](Exceptions.md). 145 | - Minimal PHP version is PHP 7.3. 146 | - `LibXmlException` is not `@internal`. Do not use it outside this project. 147 | - `SchemaValidator` constructor uses `DOMDocument`. 148 | To create it from an XML content use `SchemaValidator::createFromString`. 149 | 150 | ## Version 2.1.2 2020-04-05 151 | 152 | - Internal change to split namespace and xsd location using `preg_split`. 153 | - Introduce deprecation notice on version `2.x`. 154 | - Update travis badges and link. 155 | 156 | ## Version 2.1.1 2020-01-08 157 | 158 | - Improve testing, 100% code coverage, each test class uses cover related class. 159 | - Improve Travis-CI, do not create code coverage. 160 | - Improve Scrutinizer-CI, create code coverage. 161 | - Change development dependence from `phpstan/phpstan-shim` to `phpstan/phpstan`. 162 | - Remove development dependence `overtrue/phplint`. 163 | - Remove SensioLabs Insight. 164 | - Update documentation, licence, changelog, etc. 165 | 166 | ## Version 2.1.0 167 | 168 | - Allow to create a `SchemaValidator` instance using `DOMDocument` 169 | - Run PHPUnit 7 on PHP >= 7.1 170 | - Run PHPStan 0.10/0.11 on PHP >= 7.1 171 | 172 | ## Version 2.0.2 173 | 174 | - Fix bug when running on PHP >= 7.1 and warning was raised when call `DOMDocument::schemaValidateSource` 175 | making impossible to obtain errors from `libxml_clear_errors` and throw a new `LibXmlException` 176 | - Add a new test `SchemaValidatorTest::testValidateWithEmptySchema` to make sure that 177 | a `LibXmlException` exception is raised 178 | 179 | ## Version 2.0.1 180 | 181 | - Fix bug when using windows path (backslashes), it does not validate 182 | - Add docblock to buildSchemas 183 | - Improve building, add PHPStan 184 | - Use PHPLint instead of php-parallel-lint 185 | - Update dependencies using composer-require-checker 186 | 187 | ## Version 2.0.0 188 | 189 | - This version does not include `Locator` nor `DownloaderInterface` implementations. 190 | That functionality is actually outside the scope of this library and that is the reason 191 | why it was removed. A new library was created to implement this, take a look in 192 | `eclipxe/xmlresourceretriever` https://github.com/eclipxe13/XmlResourceRetriever/ 193 | - Constructor of `SchemaValidator` and `Schemas` changed. 194 | - Add new method `SchemaValidator::validateWithSchemas` that do the same 195 | thing as `SchemaValidator::validate` but you must provide the `Schemas` collection 196 | - Change from `protected` to `public` the method `SchemaValidator::buildSchemas`, 197 | it's useful when used with `SchemaValidator::validateWithSchemas` to change 198 | XSD remote locations to local or other places. 199 | - Add `XmlSchemaValidator::LibXmlException`. It contains a method to exec a callable 200 | isolating the use internal errors setting and other to collect libxml errors 201 | and throw it like an exception. 202 | - Rename `Schemas::getXsd` to `Schemas::getImporterXsd` 203 | - Remove compatibility with PHP 5.6, minimum version is now PHP 7.0 204 | - Add scalar type declarations 205 | - Remove test assets from Mexican SAT 206 | - Tests: Move files served by php built-in web server to from assets to public 207 | 208 | # Version 1.1.4 209 | 210 | - Fix implementation of libxml use internal errors on `SchemaValidator::validate` 211 | - When creating the dom document avoid warnings (fix using the correct constant) 212 | - Avoid using versions `@stable` in `composer.json` 213 | - Install scrutinizer/ocular only on travis and PHP 7.1 214 | 215 | ## Version 1.1.3 216 | 217 | - Fix test were failing on php 7.0 and 7.1 218 | - class PHPUnit_Framework_TestCase is deprecated 219 | - wait for 0.5 seconds after run the php server 220 | 221 | ## Version 1.1.2 222 | 223 | - Fix project name in README.md 224 | - Add composer.json tag xmlschema 225 | 226 | ## Version 1.1.1 227 | 228 | - Remove typo on .travis.yml 229 | 230 | ## Version 1.1.0 231 | 232 | - This change does not introduce any break with previous versions but add a new interface and objects 233 | to perform the download 234 | - Library 235 | - Add the interface `XmlSchemaValidator\Downloader\DownloaderInterface` and implementations 236 | `XmlSchemaValidator\Downloader\CurlDownloader`, 237 | `XmlSchemaValidator\Downloader\NullDownloader` and 238 | `XmlSchemaValidator\Downloader\PhpDownloader`. 239 | - Make `XmlSchemaValidator\Locator` use the `DownloaderInterface` 240 | - Add tests for the Locator constructor and downloader getter. 241 | - Tests 242 | - Add tests for the Locator constructor and downloader getter. 243 | - Add tests for `XmlSchemaValidator\Downloader` 244 | - Start php internal server to run tests on downloader (bootstrap.php) 245 | - Default tests for locator uses a faker test to avoid external downloads 246 | - Continuous Integration 247 | - Add 7.1 248 | - Drop hhvm 249 | - Standardization 250 | - Rename folder `sources` to `src` 251 | - Rename `.php_cs` to `.php_cs.dist` require dev `friendsofphp/php-cs-fixer` 252 | - Add `phpcs.xml.dist` 253 | - Apply code style fixes from `phpcbf` and `php-cs-fixer` 254 | - Documentation 255 | - Add basic usage to the validator 256 | - Add `CHANGELOG.md`, `TODO.md`, `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md` 257 | - Fix badges 258 | - Drop coveralls 259 | 260 | ## Version 1.0.0 261 | 262 | - Follow recommendations from SensioLabs 263 | - Project does not depend on zip extension 264 | - Include SensioLabs Insight 265 | -------------------------------------------------------------------------------- /docs/Exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | The class throws its own exceptions, all the exceptions implements `XmlSchemaValidatorException`. 4 | 5 | ## List of exceptions 6 | 7 | ### `XmlContentIsEmptyException` 8 | 9 | This exception extends of `InvalidArgumentException` and is thrown from `SchemaValidator::createFromString` 10 | when you pass an empty string, to avoid this exception validate previously that the xml content is not empty. 11 | 12 | ### `XmlContentIsInvalidException` 13 | 14 | This exception extends of `InvalidArgumentException` and is thrown from `SchemaValidator::createFromString` 15 | when you pass a string that was not able to load as xmlbecause of malformed xml or any other libxml error. 16 | 17 | ### `ValidationFailException` 18 | 19 | These exceptions extend of `RuntimeException` and is thrown when the validation didn't pass on method 20 | `SchemaValidator::validateWithSchemas()`. 21 | 22 | ### `SchemaLocationPartsNotEvenException` 23 | 24 | These exceptions extend of `RuntimeException` and is thrown when have to build a schema collection based on 25 | the current `schemaLocation` atrributes but one of them have an odd number of elements. 26 | This can happend using `SchemaValidator::buildSchemas` or `SchemaValidator::buildSchemasFromSchemaLocationValue`. 27 | 28 | ### `NamespaceNotFoundInSchemas` 29 | 30 | This exception extends of `OutOfRangeException` and is thrown when you call `Schemas::item()` with a namespace 31 | that does not exist, verify that the namespace is registered using `Schemas::exists()`. 32 | 33 | ## Named constructors 34 | 35 | The exceptions on this library cannot be created using new, all of them have static constructors. 36 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # eclipxe/XmlSchemaValidator To Do 2 | 3 | - Add support for `xsi:noNamespaceSchemaLocation`, 4 | see 5 | and 6 | 7 | - `LibXmlException` should not be internal and move to `Exceptions`. 8 | 9 | - `ValidationFailException` could have a method to access `LibXmlError` list of previous `LibXmlException`. 10 | 11 | ## Completed on version 3.0 12 | 13 | - [X] Move sources to namespace `Eclipxe\XmlSchemaValidator` 14 | - [X] Move tests to namespace `Eclipxe\XmlSchemaValidator\Tests` 15 | - [X] `SchemaValidator` should be constructed using a `DOMDocument` 16 | - [X] `SchemaValidator` should offer a new method `createFromString` 17 | - [X] PHP Minimal version to 7.2 (or 7.1?) 18 | - Build on Scrutinizer-CI removes squizlabs/php_codesniffer, friendsofphp/php-cs-fixer, vimeo/psalm & phpstan/phpstan 19 | - Build on Travis-CI on 7.0 removes vimeo/psalm & phpstan/phpstan, else runs psalm & phpstan 20 | - [X] Use strict types 21 | - [X] Review all docblocks, remove or justify 22 | - [X] Change file locations to library standards 23 | 24 | ## Completed 25 | 26 | - [X] Deprecate PHP 5.6 to PHP 7.0 and phpunit from ^5.7 to ^6.3 27 | - [X] Move from standard exceptions to library exceptions 28 | - [X] Use better XSD samples, currently is heavely related to Mexico SAT CFDI v 3.2 29 | - [X] Create a downloader object to separate responsabilities of Locator object 30 | - [X] Include contribute and CoC 31 | - [X] ~~Full coverage~~ Coverage over 90% 32 | -------------------------------------------------------------------------------- /docs/UPGRADE-v2-v3.md: -------------------------------------------------------------------------------- 1 | # Upgrade from version `2.x` to `3.x` 2 | 3 | ## Notable changes 4 | 5 | ### SchemaValidator construct 6 | 7 | Now `SchemaValidator` must be created using a `DOMDocument` as parameter, to create an instance using 8 | an XML string use the static method `SchemaValidator::createFromString`. 9 | 10 | ### Namespace 11 | 12 | The namespace changes from `\XmlSchemaValidator` to `\Eclipxe\XmlSchemaValidator`. 13 | 14 | This is because the project was not following the `vendor\product` convention. 15 | 16 | ### Exceptions 17 | 18 | The library now uses own exceptions, check the [exceptions documentation](Exceptions.md). 19 | 20 | All exceptions are annotated on `phpdoc` blocks. 21 | 22 | ### PHP minimal version 23 | 24 | Minimal version changes from `7.0` to `7.3`. 25 | 26 | As of 2020-04-05 versions `7.0` and `7.1` were on *End of life*; 27 | version `7.2` is on security fixes only until 2020-11-30; 28 | version `7.3` has active support and has security fixes until 2021-12-06. 29 | 30 | ## Internal changes 31 | 32 | The following changes are about the library, not about your implementation. 33 | 34 | ### Internal `LibXmlException` 35 | 36 | `LibXmlException` is now `@internal`, it is not to be used from outside library scope. It can ce exposed 37 | by a named exception as previous, but it is fine since is just a `Throwable`. 38 | 39 | ### Strict mode 40 | 41 | Strict type declaration `declare(strict_types=1);` has been set to all files. 42 | 43 | Functions that does not return are defined as `void`. 44 | 45 | ### File locations 46 | 47 | - `Eclipxe\XmlSchemaValidator => src/` 48 | - `Eclipxe\XmlSchemaValidator\Tests => tests/` 49 | 50 | ### Development tools 51 | 52 | Development tools (except `PHPUnit`) are installed into `tools/` directory using the tool 53 | [`phive`](https://phar.io/). 54 | 55 | This helps to memory usage on IDE like `PhpStorm` and to have a light development dependencies on composer. 56 | -------------------------------------------------------------------------------- /src/Exceptions/NamespaceNotFoundInSchemas.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 18 | } 19 | 20 | public static function create(string $namespace): self 21 | { 22 | return new self("Namespace $namespace does not exists in the schemas", $namespace); 23 | } 24 | 25 | public function getNamespace(): string 26 | { 27 | return $this->namespace; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Exceptions/SchemaLocationPartsNotEvenException.php: -------------------------------------------------------------------------------- 1 | parts = $parts; 24 | } 25 | 26 | /** 27 | * @param string[] $parts 28 | * @return self 29 | */ 30 | public static function create(array $parts): self 31 | { 32 | return new self('The schemaLocation attribute does not have even parts', $parts); 33 | } 34 | 35 | /** 36 | * Return the parts found on the schemaLocations attribute 37 | * 38 | * @return string[] 39 | */ 40 | public function getParts(): array 41 | { 42 | return $this->parts; 43 | } 44 | 45 | /** 46 | * Return the parts found on the schemaLocations attribute separated by a space 47 | * 48 | * @return string 49 | */ 50 | public function getPartsAsString(): string 51 | { 52 | return implode(' ', $this->parts); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Exceptions/ValidationFailException.php: -------------------------------------------------------------------------------- 1 | getMessage(), $previous); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/XmlContentIsEmptyException.php: -------------------------------------------------------------------------------- 1 | getMessage(), $previous); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/XmlSchemaValidatorException.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 32 | } 33 | 34 | /** 35 | * Create an instance with the provided information 36 | * 37 | * @param string $message 38 | * @param LibXMLError[] $errors 39 | * @return LibXmlException 40 | * @throws InvalidArgumentException when an error item is not a LibXmlError 41 | */ 42 | public static function create(string $message, array $errors): self 43 | { 44 | /** @var mixed $error psalm found this validation contradictory since $errors is defined as LibXMLError[] */ 45 | foreach ($errors as $index => $error) { 46 | if (! $error instanceof LibXMLError) { 47 | throw new InvalidArgumentException("Error index $index is not a LibXmlError"); 48 | } 49 | } 50 | 51 | return new self($message, $errors); 52 | } 53 | 54 | /** 55 | * List of libxml errors 56 | * 57 | * @return LibXMLError[] 58 | */ 59 | public function getErrors(): array 60 | { 61 | return $this->errors; 62 | } 63 | 64 | /** 65 | * Create a LibXmlException based on errors in libxml. 66 | * If found, clear the errors and chain all the error messages. 67 | * 68 | * @return LibXmlException|null 69 | */ 70 | public static function createFromLibXml(): ?self 71 | { 72 | $errors = libxml_get_errors(); 73 | if (! count($errors)) { 74 | return null; 75 | } 76 | libxml_clear_errors(); 77 | $error = end($errors); 78 | return self::create($error->message, $errors); 79 | } 80 | 81 | /** 82 | * Execute a callable ensuring that the execution will occur inside an environment 83 | * where libxml use internal errors is true. 84 | * 85 | * After executing the callable the value of libxml use internal errors is set to 86 | * previous value. 87 | * 88 | * @param callable $callable 89 | * @return mixed 90 | * 91 | * @throws LibXmlException if some error inside libxml was found 92 | */ 93 | public static function useInternalErrors(callable $callable) 94 | { 95 | // capture current error reporting level and set to no-errors 96 | $previousErrorReporting = error_reporting(self::ERROR_LEVEL_SUPPRESS_ALL); 97 | 98 | // capture current libxml use internal errors and set to true 99 | $previousLibXmlUseInternalErrors = libxml_use_internal_errors(true); 100 | if ($previousLibXmlUseInternalErrors) { 101 | libxml_clear_errors(); 102 | } 103 | 104 | // run callable and throw libxml error as exception and always restore previous status 105 | try { 106 | /** @psalm-var mixed $return */ 107 | $return = $callable(); 108 | $exception = static::createFromLibXml(); 109 | if (null !== $exception) { 110 | throw $exception; 111 | } 112 | return $return; 113 | } finally { 114 | // restore error reporting level and libxml use internal errors 115 | error_reporting($previousErrorReporting); 116 | libxml_use_internal_errors($previousLibXmlUseInternalErrors); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Schema.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 21 | $this->location = $location; 22 | } 23 | 24 | public function getNamespace(): string 25 | { 26 | return $this->namespace; 27 | } 28 | 29 | public function getLocation(): string 30 | { 31 | return $this->location; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SchemaValidator.php: -------------------------------------------------------------------------------- 1 | document = $document; 37 | } 38 | 39 | /** 40 | * Create a SchemaValidator instance based on an XML string 41 | * 42 | * @param string $contents 43 | * @return self 44 | * @throws XmlContentIsEmptyException when the xml contents is an empty string 45 | * @throws XmlContentIsInvalidException when the xml contents cannot be loaded 46 | */ 47 | public static function createFromString(string $contents): self 48 | { 49 | // do not allow empty string 50 | if ('' === $contents) { 51 | throw XmlContentIsEmptyException::create(); 52 | } 53 | 54 | // create and load contents throwing specific exception 55 | try { 56 | /** @var DOMDocument $document */ 57 | $document = LibXmlException::useInternalErrors( 58 | function () use ($contents): DOMDocument { 59 | $document = new DOMDocument(); 60 | $document->loadXML($contents); 61 | return $document; 62 | } 63 | ); 64 | } catch (LibXmlException $exception) { 65 | throw XmlContentIsInvalidException::create($exception); 66 | } 67 | 68 | return new self($document); 69 | } 70 | 71 | /** 72 | * Validate the content by: 73 | * - Create the Schemas collection from the document 74 | * - Validate using validateWithSchemas 75 | * - Populate the error property 76 | * 77 | * @return bool 78 | * @see validateWithSchemas 79 | */ 80 | public function validate(): bool 81 | { 82 | $this->lastError = ''; 83 | try { 84 | // create the schemas collection 85 | $schemas = $this->buildSchemas(); 86 | // validate the document using the schema collection 87 | $this->validateWithSchemas($schemas); 88 | } catch (XmlSchemaValidatorException $ex) { 89 | $this->lastError = $ex->getMessage(); 90 | return false; 91 | } 92 | return true; 93 | } 94 | 95 | /** 96 | * Retrieve the last error message captured on the last validate operation 97 | * 98 | * @return string 99 | */ 100 | public function getLastError(): string 101 | { 102 | return $this->lastError; 103 | } 104 | 105 | /** 106 | * Validate against a list of schemas (if any) 107 | * 108 | * @param Schemas $schemas 109 | * @return void 110 | * 111 | * @throws ValidationFailException when schema validation fails 112 | */ 113 | public function validateWithSchemas(Schemas $schemas): void 114 | { 115 | // early exit, do not validate if schemas collection is empty 116 | if (0 === $schemas->count()) { 117 | return; 118 | } 119 | 120 | // build the unique importing schema 121 | $xsd = $schemas->getImporterXsd(); 122 | 123 | // validate and trap LibXmlException 124 | try { 125 | LibXmlException::useInternalErrors(function () use ($xsd): void { 126 | $this->document->schemaValidateSource($xsd); 127 | }); 128 | } catch (LibXmlException $exception) { 129 | throw ValidationFailException::create($exception); 130 | } 131 | } 132 | 133 | /** 134 | * Retrieve a list of namespaces based on the schemaLocation attributes 135 | * 136 | * @return Schemas 137 | * @throws SchemaLocationPartsNotEvenException when the schemaLocation attribute does not have even parts 138 | */ 139 | public function buildSchemas(): Schemas 140 | { 141 | $schemas = new Schemas(); 142 | $xpath = new DOMXPath($this->document); 143 | 144 | // get the http://www.w3.org/2001/XMLSchema-instance namespace (it could not be 'xsi') 145 | $xsi = strval($this->document->lookupPrefix('http://www.w3.org/2001/XMLSchema-instance')); 146 | if ('' === $xsi) { // the namespace is not registered, no need to continue 147 | return $schemas; 148 | } 149 | 150 | // get all the xsi:schemaLocation attributes in the document 151 | /** @var iterable $schemasList */ 152 | $schemasList = $xpath->query("//@$xsi:schemaLocation"); 153 | 154 | // process every schemaLocation and import them into schemas 155 | foreach ($schemasList as $schemaAttribute) { 156 | $schemaValue = $schemaAttribute->nodeValue; 157 | if (null !== $schemaValue) { 158 | $schemas->import($this->buildSchemasFromSchemaLocationValue($schemaValue)); 159 | } 160 | } 161 | 162 | return $schemas; 163 | } 164 | 165 | /** 166 | * Create a schemas collection from the content of a schema location 167 | * 168 | * @param string $content 169 | * @return Schemas 170 | * @throws SchemaLocationPartsNotEvenException when the schemaLocation attribute does not have even parts 171 | */ 172 | public function buildSchemasFromSchemaLocationValue(string $content): Schemas 173 | { 174 | // get parts without inner spaces 175 | $parts = array_values(array_filter(preg_split('/\s+/', $content) ?: [])); 176 | $partsCount = count($parts); 177 | 178 | // check that the list count is an even number 179 | if (0 !== $partsCount % 2) { 180 | throw SchemaLocationPartsNotEvenException::create($parts); 181 | } 182 | 183 | // insert the uris pairs into the schemas 184 | $schemas = new Schemas(); 185 | for ($k = 0; $k < $partsCount; $k = $k + 2) { 186 | $schemas->create($parts[$k], $parts[$k + 1]); 187 | } 188 | return $schemas; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Schemas.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Schemas implements IteratorAggregate, Countable 21 | { 22 | /** @var array internal collection of schemas */ 23 | private $schemas = []; 24 | 25 | /** 26 | * Return the XML of a Xsd that includes all the namespaces 27 | * with the local location 28 | * 29 | * @return string 30 | * @noinspection PhpDocMissingThrowsInspection 31 | */ 32 | public function getImporterXsd(): string 33 | { 34 | $xsd = new DOMDocument('1.0', 'UTF-8'); 35 | $xsd->loadXML(''); 36 | /** @var DOMElement $document */ 37 | $document = $xsd->documentElement; 38 | foreach ($this->schemas as $schema) { 39 | /** @noinspection PhpUnhandledExceptionInspection */ 40 | $node = $xsd->createElementNS('http://www.w3.org/2001/XMLSchema', 'import'); 41 | $node->setAttribute('namespace', $schema->getNamespace()); 42 | $node->setAttribute('schemaLocation', str_replace('\\', '/', $schema->getLocation())); 43 | $document->appendChild($node); 44 | } 45 | return strval($xsd->saveXML()); 46 | } 47 | 48 | /** 49 | * Create a new schema and inserts it to the collection 50 | * The returned object is the created schema 51 | * 52 | * @param string $namespace 53 | * @param string $location 54 | * @return Schema 55 | */ 56 | public function create(string $namespace, string $location): Schema 57 | { 58 | return $this->insert(new Schema($namespace, $location)); 59 | } 60 | 61 | /** 62 | * Insert (add or replace) a schema to the collection 63 | * The returned object is the same schema 64 | * 65 | * @param Schema $schema 66 | * @return Schema 67 | */ 68 | public function insert(Schema $schema): Schema 69 | { 70 | $this->schemas[$schema->getNamespace()] = $schema; 71 | return $schema; 72 | } 73 | 74 | /** 75 | * Import the schemas from other schema collection to this collection 76 | * 77 | * @param Schemas $schemas 78 | */ 79 | public function import(self $schemas): void 80 | { 81 | foreach ($schemas->getIterator() as $schema) { 82 | $this->insert($schema); 83 | } 84 | } 85 | 86 | /** 87 | * Remove a schema based on its namespace 88 | * 89 | * @param string $namespace 90 | * @return void 91 | */ 92 | public function remove(string $namespace): void 93 | { 94 | unset($this->schemas[$namespace]); 95 | } 96 | 97 | /** 98 | * Return the complete collection of schemas as an associative array 99 | * 100 | * @return array 101 | */ 102 | public function all(): array 103 | { 104 | return $this->schemas; 105 | } 106 | 107 | /** 108 | * Check if a schema exists by its namespace 109 | * 110 | * @param string $namespace 111 | * @return bool 112 | */ 113 | public function exists(string $namespace): bool 114 | { 115 | return array_key_exists($namespace, $this->schemas); 116 | } 117 | 118 | /** 119 | * Get a schema object by its namespace 120 | * 121 | * @param string $namespace 122 | * @throws NamespaceNotFoundInSchemas when namespace does not exist on schema 123 | * @return Schema 124 | */ 125 | public function item(string $namespace): Schema 126 | { 127 | if (! array_key_exists($namespace, $this->schemas)) { 128 | throw NamespaceNotFoundInSchemas::create($namespace); 129 | } 130 | return $this->schemas[$namespace]; 131 | } 132 | 133 | /** 134 | * Count elements on the collection 135 | * 136 | * @return int 137 | */ 138 | public function count(): int 139 | { 140 | return count($this->schemas); 141 | } 142 | 143 | /** @return Traversable */ 144 | public function getIterator(): Traversable 145 | { 146 | return new ArrayIterator($this->schemas); 147 | } 148 | } 149 | --------------------------------------------------------------------------------