├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── testing.yml │ └── validation.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── drafts └── v1.1 │ ├── MetronInfo.xsd │ └── Sample.xml ├── poetry.lock ├── pyproject.toml ├── schema └── v1.0 │ ├── MetronInfo.xsd │ └── Sample.xml └── tests ├── test.py └── test_files └── v1.0 ├── dup_primary_attr.xml └── valid.xml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** Steps to reproduce the behavior: 12 | 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | **Expected behavior** A clear and concise description of what you expected to 19 | happen. 20 | 21 | **Screenshots** If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** A clear and 10 | concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** A clear and concise description of what you 13 | want to happen. 14 | 15 | **Describe alternatives you've considered** A clear and concise description of 16 | any alternative solutions or features you've considered. 17 | 18 | **Additional context** Add any other context or screenshots about the feature 19 | request here. 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: "Testing" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | tox: 13 | name: "Tests" 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: ["3.13"] 18 | os: 19 | - ubuntu-latest 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | #---------------------------------------------- 23 | # check-out repo and set-up python 24 | #---------------------------------------------- 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | - name: Setup python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | #---------------------------------------------- 32 | # ----- install & configure poetry ----- 33 | #---------------------------------------------- 34 | - name: Install Poetry 35 | uses: snok/install-poetry@v1 36 | with: 37 | version: 1.8.4 38 | virtualenvs-create: true 39 | virtualenvs-in-project: true 40 | #---------------------------------------------- 41 | # load cached venv if cache exists 42 | #---------------------------------------------- 43 | - name: Load cached venv 44 | id: cached-poetry-dependencies 45 | uses: actions/cache@v4 46 | with: 47 | path: .venv 48 | key: venv-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} 49 | #---------------------------------------------- 50 | # install dependencies if cache does not exist 51 | #---------------------------------------------- 52 | - name: Install dependencies 53 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 54 | run: poetry install --no-interaction --no-root 55 | #---------------------------------------------- 56 | # install your root project, if required 57 | #---------------------------------------------- 58 | - name: Install library 59 | run: poetry install --no-interaction 60 | #---------------------------------------------- 61 | # Run pytest 62 | #---------------------------------------------- 63 | - name: Test with pytest 64 | run: poetry run pytest 65 | -------------------------------------------------------------------------------- /.github/workflows/validation.yml: -------------------------------------------------------------------------------- 1 | name: XSD Validation 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.xsd' 7 | pull_request: 8 | paths: 9 | - '**.xsd' 10 | 11 | jobs: 12 | validate: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: apt-get update 17 | run: sudo apt-get update -y 18 | - name: Install xmllint 19 | run: sudo apt-get install -y libxml2-utils 20 | - name: Validate XSD Schemas 21 | run: find . -type f -name "*.xsd" -exec xmllint -noout -schema http://www.w3.org/2009/XMLSchema/XMLSchema.xsd {} + 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /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, 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 | bpepple@metron.cloud. 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. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Brian Pepple 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 | # MetronInfo.xml 2 | 3 | ## What is it? 4 | 5 | `MetronInfo.xml` is an attempt to create a new schema for digital comic books that fixes some of the deficiencies that 6 | exist with the `ComicInfo.xml` schema. 7 | 8 | ## Rationale 9 | 10 | The `ComicInfo.xml` schema was designed for the needs of the ComicRack Application, and supports a fairly limited amount 11 | of data. Some benefits of a new schema would include: 12 | 13 | - Additional `Elements` for information. (e.g. Price, Global Trade Item Numbers, Series Type, etc.) 14 | - Better handling of data types. Instead of using delimited strings for list items, we can use Arrays of `Elements`. 15 | - Ability to identify where the data was obtained from. (e.g. Comic Vine, Metron, Grand Comics Database, etc.) 16 | - Add `ID` elements from the Information Source to resources (Characters, Creators, etc.), so items with the same name 17 | are associated correctly if used in a Plex-like Comic Server. 18 | 19 | Since Digital Comics are just are archive files (like .zip) this new XML schema can co-exist with any existing 20 | `ComicInfo.xml` if needed for backward compatibility. 21 | 22 | ## Is the schema only for the Metron Database? 23 | 24 | No, the schema only has *Metron* in the name since almost every other format has *Comic* in the name, and the 25 | originating author hates naming projects, so he went with the simplest choice. 😄 It was designed to be used for any of 26 | the comic resources (Comic Vine, AniList, etc.) 27 | 28 | ## Where can I find the schemas? 29 | 30 | Version 1.0 of the schema is located in [schema](./schema) directory 31 | 32 | ## Is there documentation for it? 33 | 34 | Yes, there is [documentation](https://metron-project.github.io/docs/category/metroninfo) describing the elements usage 35 | and also a Matrix to help with age rating mapping. 36 | 37 | ## How can I validate my XML? 38 | 39 | It's recommended that any software that writes the XML make use of the schema to validate, so consumers of the XML 40 | document can be sure of its data. The schema use XSD 1.1, so you need to make sure your validation code uses that 41 | instead of XSD 1.0. 42 | 43 | For example to validate the XML in python: 44 | 45 | ```python 46 | from pathlib import Path 47 | from xmlschema import XMLSchema11, XMLSchemaValidationError 48 | 49 | xsd = Path("/home/user/MetronInfo.xsd") 50 | xml = Path("/home/user/MetronInfo.xml") 51 | 52 | schema = XMLSchema11(xsd) 53 | try: 54 | schema.validate(xml) 55 | except XMLSchemaValidationError as e: 56 | print(f"Failed to validate XML: {e!r}") 57 | exit(1) 58 | 59 | # Code to write / read the xml file 60 | ``` 61 | 62 | ## What software currently supports it? 63 | 64 | Currently, the following software does: 65 | 66 | - [Metron-Tagger](https://github.com/Metron-Project/metron-tagger) - Commandline tool to tag comic with metadata from 67 | Metron Comic Book Database. 68 | - [Perdoo](https://github.com/Buried-In-Code/Perdoo) - Commandline tool to tag and organize comics from multiple sources 69 | (Metron, Comicvine & Marvel) 70 | - [ComicRack Community Edition](https://github.com/maforget/ComicRackCE) - Revival of the ComicRack software, supports reading metroninfo metadata. 71 | - [metroninfo_ct ](https://github.com/mizaki/metroninfo_ct) - A [Comictagger](https://github.com/comictagger/comictagger) plugin to write MetronInfo.xml metadata. 72 | 73 | If you are a developer that has added support for MetronInfo.xml to your software, please create a PR to update the 74 | README 75 | or [contact me](mailto:bpepple@metron.cloud?subject=MetronInfo%20Support&body=Please%20add%20the%20following%20software%20to%the%20README:%20). 76 | -------------------------------------------------------------------------------- /drafts/v1.1/MetronInfo.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /drafts/v1.1/Sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 290431 5 | 12345 6 | 543 7 | 8b34f37a-0181-4f0b-8ce3-01217e9a602c 8 | 9 | 10 | DC Comics 11 | Vertigo 12 | 13 | 14 | Justice League 15 | Justice League 16 | 2 17 | Single Issue 18 | 1970 19 | 60 20 | 3 21 | 22 | Foo 23 | Hüsker Dü 24 | 25 | 26 | 1 27 | 28 | Justice League, Part One 29 | Justice League, Part Two 30 | 31 | In a universe where superheroes are strange and new, Batman has discovered a dark evil that requires him to unite the World Greatest Heroes! 32 | Nothing really to say. 33 | 34 | 3.99 35 | 1.51 36 | 37 | 2011-10-01 38 | 2011-08-31 39 | 32 40 | 41 | Super-Hero 42 | Crime 43 | Foo Bar 44 | 45 | 46 | Foo 47 | Bar 48 | 49 | 50 | 51 | Origin 52 | 1 53 | 54 | 55 | The New 52! 56 | 57 | 58 | 59 | Aquaman 60 | Batman 61 | Cyborg 62 | Deadman 63 | Barry Allen 64 | Hal Jordan 65 | Hawkman 66 | Mera 67 | Pandora 68 | Ray Palmer 69 | Superman 70 | Wonder Woman 71 | 72 | 73 | Justice League 74 | Parademons 75 | 76 | 77 | 78 | ABC 79 | Earth 25 80 | 81 | 82 | Amalgam 83 | 84 | 85 | 86 | Gotham City 87 | Metropolis 88 | 89 | 90 | 1234567890123 91 | 76194130593600111 92 | 93 | Everyone 94 | 95 | Foo Bar #001 (2002) 96 | Foo Bar #002 (2022) 97 | 98 | 99 | https://comicvine.gamespot.com/justice-league-1-justice-league-part-one/4000-290431/ 100 | https://foo.bar 101 | https://bar.foo 102 | 103 | 104 | 105 | Geoff Johns 106 | 107 | Writer 108 | 109 | 110 | 111 | David Finch 112 | 113 | Cover 114 | 115 | 116 | 117 | Richard Friend 118 | 119 | Cover 120 | 121 | 122 | 123 | Jim Lee 124 | 125 | Penciller 126 | Cover 127 | 128 | 129 | 130 | Scott Williams 131 | 132 | Inker 133 | Cover 134 | 135 | 136 | 137 | Alex Sinclair 138 | 139 | Colorist 140 | Cover 141 | 142 | 143 | 144 | Pat Brosseau 145 | 146 | Letterer 147 | 148 | 149 | 150 | Rex Ogle 151 | 152 | Associate Editor 153 | 154 | 155 | 156 | Eddie Berganza 157 | 158 | Editor 159 | 160 | 161 | 162 | Dan DiDio 163 | 164 | Publisher 165 | 166 | 167 | 168 | 2023-05-31T09:00:46.300882-04:00 169 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | optional = false 8 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 9 | files = [ 10 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 11 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 12 | ] 13 | 14 | [[package]] 15 | name = "elementpath" 16 | version = "4.6.0" 17 | description = "XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml" 18 | optional = false 19 | python-versions = ">=3.8" 20 | files = [ 21 | {file = "elementpath-4.6.0-py3-none-any.whl", hash = "sha256:e578677f19ccc6ff374c4477c687c547ecbaf7b478d98abb951b7b4b45260a17"}, 22 | {file = "elementpath-4.6.0.tar.gz", hash = "sha256:ba46bf07f66774927727ade55022b6c435fac06b2523cb3cd7689a1884d33468"}, 23 | ] 24 | 25 | [package.extras] 26 | dev = ["Sphinx", "coverage", "flake8", "lxml", "lxml-stubs", "memory-profiler", "memray", "mypy", "tox", "xmlschema (>=3.3.2)"] 27 | 28 | [[package]] 29 | name = "iniconfig" 30 | version = "2.0.0" 31 | description = "brain-dead simple config-ini parsing" 32 | optional = false 33 | python-versions = ">=3.7" 34 | files = [ 35 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 36 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 37 | ] 38 | 39 | [[package]] 40 | name = "packaging" 41 | version = "24.1" 42 | description = "Core utilities for Python packages" 43 | optional = false 44 | python-versions = ">=3.8" 45 | files = [ 46 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 47 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 48 | ] 49 | 50 | [[package]] 51 | name = "pluggy" 52 | version = "1.5.0" 53 | description = "plugin and hook calling mechanisms for python" 54 | optional = false 55 | python-versions = ">=3.8" 56 | files = [ 57 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 58 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 59 | ] 60 | 61 | [package.extras] 62 | dev = ["pre-commit", "tox"] 63 | testing = ["pytest", "pytest-benchmark"] 64 | 65 | [[package]] 66 | name = "pytest" 67 | version = "8.3.3" 68 | description = "pytest: simple powerful testing with Python" 69 | optional = false 70 | python-versions = ">=3.8" 71 | files = [ 72 | {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, 73 | {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, 74 | ] 75 | 76 | [package.dependencies] 77 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 78 | iniconfig = "*" 79 | packaging = "*" 80 | pluggy = ">=1.5,<2" 81 | 82 | [package.extras] 83 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 84 | 85 | [[package]] 86 | name = "ruff" 87 | version = "0.7.1" 88 | description = "An extremely fast Python linter and code formatter, written in Rust." 89 | optional = false 90 | python-versions = ">=3.7" 91 | files = [ 92 | {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, 93 | {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, 94 | {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, 95 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, 96 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, 97 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, 98 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, 99 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, 100 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, 101 | {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, 102 | {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, 103 | {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, 104 | {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, 105 | {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, 106 | {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, 107 | {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, 108 | {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, 109 | {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, 110 | ] 111 | 112 | [[package]] 113 | name = "xmlschema" 114 | version = "3.4.2" 115 | description = "An XML Schema validator and decoder" 116 | optional = false 117 | python-versions = ">=3.8" 118 | files = [ 119 | {file = "xmlschema-3.4.2-py3-none-any.whl", hash = "sha256:c6b4de5f8aadeb45e74229f09a2129342b446456efc5e5a27388050afdfedec8"}, 120 | {file = "xmlschema-3.4.2.tar.gz", hash = "sha256:d35023ea504ea46127302d1297b046d023b96fec5fe4b4b690534ea85b5e9bf8"}, 121 | ] 122 | 123 | [package.dependencies] 124 | elementpath = ">=4.4.0,<5.0.0" 125 | 126 | [package.extras] 127 | codegen = ["elementpath (>=4.4.0,<5.0.0)", "jinja2"] 128 | dev = ["Sphinx", "coverage", "elementpath (>=4.4.0,<5.0.0)", "flake8", "jinja2", "lxml", "lxml-stubs", "memory-profiler", "mypy", "sphinx-rtd-theme", "tox"] 129 | docs = ["Sphinx", "elementpath (>=4.4.0,<5.0.0)", "jinja2", "sphinx-rtd-theme"] 130 | 131 | [metadata] 132 | lock-version = "2.0" 133 | python-versions = "^3.12" 134 | content-hash = "4a6a53745c4ade616e0419283d9da59035204046ff1c0f66e3f54b96c848b018" 135 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "metroninfo" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Brian Pepple "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.12" 11 | 12 | [tool.poetry.group.dev.dependencies] 13 | xmlschema = "^3.4.2" 14 | pytest = "^8.3.3" 15 | ruff = "^0.7.1" 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | 21 | [tool.pytest.ini_options] 22 | testpaths = "tests/test*" 23 | -------------------------------------------------------------------------------- /schema/v1.0/MetronInfo.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /schema/v1.0/Sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 290431 5 | 12345 6 | 543 7 | 8b34f37a-0181-4f0b-8ce3-01217e9a602c 8 | 9 | 10 | DC Comics 11 | Vertigo 12 | 13 | 14 | Justice League 15 | Justice League 16 | 2 17 | Single Issue 18 | 1970 19 | 60 20 | 3 21 | 22 | Foo 23 | Hüsker Dü 24 | 25 | 26 | 1 27 | 28 | Justice League, Part One 29 | Justice League, Part Two 30 | 31 | In a universe where superheroes are strange and new, Batman has discovered a dark evil that requires him to unite the World Greatest Heroes! 32 | Nothing really to say. 33 | 34 | 3.99 35 | 1.51 36 | 37 | 2011-10-01 38 | 2011-08-31 39 | 32 40 | 41 | Super-Hero 42 | Crime 43 | Foo Bar 44 | 45 | 46 | Foo 47 | Bar 48 | 49 | 50 | 51 | Origin 52 | 1 53 | 54 | 55 | The New 52! 56 | 57 | 58 | 59 | Aquaman 60 | Batman 61 | Cyborg 62 | Deadman 63 | Barry Allen 64 | Hal Jordan 65 | Hawkman 66 | Mera 67 | Pandora 68 | Ray Palmer 69 | Superman 70 | Wonder Woman 71 | 72 | 73 | Justice League 74 | Parademons 75 | 76 | 77 | 78 | ABC 79 | Earth 25 80 | 81 | 82 | Amalgam 83 | 84 | 85 | 86 | Gotham City 87 | Metropolis 88 | 89 | 90 | 1234567890123 91 | 76194130593600111 92 | 93 | Everyone 94 | 95 | Foo Bar #001 (2002) 96 | Foo Bar #002 (2022) 97 | 98 | 99 | https://comicvine.gamespot.com/justice-league-1-justice-league-part-one/4000-290431/ 100 | https://foo.bar 101 | https://bar.foo 102 | 103 | 104 | 105 | Geoff Johns 106 | 107 | Writer 108 | 109 | 110 | 111 | David Finch 112 | 113 | Cover 114 | 115 | 116 | 117 | Richard Friend 118 | 119 | Cover 120 | 121 | 122 | 123 | Jim Lee 124 | 125 | Penciller 126 | Cover 127 | 128 | 129 | 130 | Scott Williams 131 | 132 | Inker 133 | Cover 134 | 135 | 136 | 137 | Alex Sinclair 138 | 139 | Colorist 140 | Cover 141 | 142 | 143 | 144 | Pat Brosseau 145 | 146 | Letterer 147 | 148 | 149 | 150 | Rex Ogle 151 | 152 | Associate Editor 153 | 154 | 155 | 156 | Eddie Berganza 157 | 158 | Editor 159 | 160 | 161 | 162 | Dan DiDio 163 | 164 | Publisher 165 | 166 | 167 | 168 | 2023-05-31T09:00:46.300882-04:00 169 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from xmlschema import XMLSchema11, XMLSchemaValidationError 5 | 6 | TEST_V10_XSD = Path(__file__).parent.parent / "schema" / "v1.0" / "MetronInfo.xsd" 7 | TEST_V11_XSD = Path(__file__).parent.parent / "drafts" / "v1.1" / "MetronInfo.xsd" 8 | TEST_FILES_PATH = Path(__file__).parent / "test_files" / "v1.0" 9 | 10 | 11 | @pytest.mark.parametrize( 12 | ("xsd", "xml"), 13 | [ 14 | (TEST_V10_XSD, TEST_FILES_PATH / "valid.xml"), 15 | ( 16 | TEST_V10_XSD, 17 | '' 18 | "Foo0", 19 | ), 20 | ( 21 | TEST_V10_XSD, 22 | '' 23 | "Foo0", 24 | ), 25 | (TEST_V11_XSD, TEST_FILES_PATH / "valid.xml"), 26 | ( 27 | TEST_V10_XSD, 28 | '' 29 | "Foo0", 30 | ), 31 | ( 32 | TEST_V10_XSD, 33 | '' 34 | "Foo0", 35 | ), 36 | ], 37 | ids=["valid_xml", "zero_page_count", "volume_zero", "v11_valid_xml", "v11_zero_page_count", "v11_volume_zero"], 38 | ) 39 | def test_valid(xsd: Path, xml: Path | str) -> None: 40 | schema = XMLSchema11(xsd) 41 | schema.validate(xml) 42 | 43 | 44 | @pytest.mark.parametrize( 45 | ("xsd", "xml"), 46 | [ 47 | (TEST_V10_XSD, TEST_FILES_PATH / "dup_primary_attr.xml"), 48 | ( 49 | TEST_V10_XSD, 50 | '' 51 | "Foo-1", 52 | ), 53 | ( 54 | TEST_V10_XSD, 55 | '' 56 | "Foo-1", 57 | ), 58 | (TEST_V11_XSD, TEST_FILES_PATH / "dup_primary_attr.xml"), 59 | ( 60 | TEST_V11_XSD, 61 | '' 62 | "Foo-1", 63 | ), 64 | ( 65 | TEST_V11_XSD, 66 | '' 67 | "Foo-1", 68 | ), 69 | ], 70 | ids=["dup_primary_attr_xml", "negative_page_count", "negative_volume", "v11_dup_primary_attr_xml", "v11_negative_page_count", "v11_negative_volume" ], 71 | ) 72 | def test_invalid(xsd: Path, xml: Path | str) -> None: 73 | schema = XMLSchema11(xsd) 74 | with pytest.raises(XMLSchemaValidationError): 75 | schema.validate(xml) 76 | -------------------------------------------------------------------------------- /tests/test_files/v1.0/dup_primary_attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 290431 5 | 12345 6 | 543 7 | 8 | 9 | DC Comics 10 | Vertigo 11 | 12 | 13 | Justice League 14 | Justice League 15 | 2 16 | Single Issue 17 | 1970 18 | 19 | Foo 20 | Hüsker Dü 21 | 22 | 23 | 1 24 | 25 | Justice League, Part One 26 | Justice League, Part Two 27 | 28 | In a universe where superheroes are strange and new, Batman has discovered a dark evil that requires him to unite the World Greatest Heroes! 29 | Nothing really to say. 30 | 31 | 3.99 32 | 1.51 33 | 34 | 2011-10-01 35 | 2011-08-31 36 | 32 37 | 38 | Super-Hero 39 | Crime 40 | Foo Bar 41 | 42 | 43 | Foo 44 | Bar 45 | 46 | 47 | 48 | Origin 49 | 1 50 | 51 | 52 | The New 52! 53 | 54 | 55 | 56 | Aquaman 57 | Batman 58 | Cyborg 59 | Deadman 60 | Barry Allen 61 | Hal Jordan 62 | Hawkman 63 | Mera 64 | Pandora 65 | Ray Palmer 66 | Superman 67 | Wonder Woman 68 | 69 | 70 | Justice League 71 | Parademons 72 | 73 | 74 | 75 | ABC 76 | Earth 25 77 | 78 | 79 | Amalgam 80 | 81 | 82 | 83 | Gotham City 84 | Metropolis 85 | 86 | 87 | 1234567890123 88 | 76194130593600111 89 | 90 | Everyone 91 | 92 | Foo Bar #001 (2002) 93 | Foo Bar #002 (2022) 94 | 95 | 96 | https://comicvine.gamespot.com/justice-league-1-justice-league-part-one/4000-290431/ 97 | https://foo.bar 98 | https://bar.foo 99 | 100 | 101 | 102 | Geoff Johns 103 | 104 | Writer 105 | 106 | 107 | 108 | David Finch 109 | 110 | Cover 111 | 112 | 113 | 114 | Richard Friend 115 | 116 | Cover 117 | 118 | 119 | 120 | Jim Lee 121 | 122 | Penciller 123 | Cover 124 | 125 | 126 | 127 | Scott Williams 128 | 129 | Inker 130 | Cover 131 | 132 | 133 | 134 | Alex Sinclair 135 | 136 | Colorist 137 | Cover 138 | 139 | 140 | 141 | Pat Brosseau 142 | 143 | Letterer 144 | 145 | 146 | 147 | Rex Ogle 148 | 149 | Associate Editor 150 | 151 | 152 | 153 | Eddie Berganza 154 | 155 | Editor 156 | 157 | 158 | 159 | Dan DiDio 160 | 161 | Publisher 162 | 163 | 164 | 165 | 2023-05-31T09:00:46.300882-04:00 166 | -------------------------------------------------------------------------------- /tests/test_files/v1.0/valid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 290431 5 | 12345 6 | 543 7 | 8 | 9 | DC Comics 10 | Vertigo 11 | 12 | 13 | Justice League 14 | Justice League 15 | 2 16 | Single Issue 17 | 1970 18 | 60 19 | 3 20 | 21 | Foo 22 | Hüsker Dü 23 | 24 | 25 | 1 26 | 27 | Justice League, Part One 28 | Justice League, Part Two 29 | 30 | In a universe where superheroes are strange and new, Batman has discovered a dark evil that requires him to unite the World Greatest Heroes! 31 | Nothing really to say. 32 | 33 | 3.99 34 | 1.51 35 | 36 | 2011-10-01 37 | 2011-08-31 38 | 32 39 | 40 | Super-Hero 41 | Crime 42 | Foo Bar 43 | 44 | 45 | Foo 46 | Bar 47 | 48 | 49 | 50 | Origin 51 | 1 52 | 53 | 54 | The New 52! 55 | 56 | 57 | 58 | Aquaman 59 | Batman 60 | Cyborg 61 | Deadman 62 | Barry Allen 63 | Hal Jordan 64 | Hawkman 65 | Mera 66 | Pandora 67 | Ray Palmer 68 | Superman 69 | Wonder Woman 70 | 71 | 72 | Justice League 73 | Parademons 74 | 75 | 76 | 77 | ABC 78 | Earth 25 79 | 80 | 81 | Amalgam 82 | 83 | 84 | 85 | Gotham City 86 | Metropolis 87 | 88 | 89 | 1234567890123 90 | 76194130593600111 91 | 92 | Everyone 93 | 94 | Foo Bar #001 (2002) 95 | Foo Bar #002 (2022) 96 | 97 | 98 | https://comicvine.gamespot.com/justice-league-1-justice-league-part-one/4000-290431/ 99 | https://foo.bar 100 | https://bar.foo 101 | 102 | 103 | 104 | Geoff Johns 105 | 106 | Writer 107 | 108 | 109 | 110 | David Finch 111 | 112 | Cover 113 | 114 | 115 | 116 | Richard Friend 117 | 118 | Cover 119 | 120 | 121 | 122 | Jim Lee 123 | 124 | Penciller 125 | Cover 126 | 127 | 128 | 129 | Scott Williams 130 | 131 | Inker 132 | Cover 133 | 134 | 135 | 136 | Alex Sinclair 137 | 138 | Colorist 139 | Cover 140 | 141 | 142 | 143 | Pat Brosseau 144 | 145 | Letterer 146 | 147 | 148 | 149 | Rex Ogle 150 | 151 | Associate Editor 152 | 153 | 154 | 155 | Eddie Berganza 156 | 157 | Editor 158 | 159 | 160 | 161 | Dan DiDio 162 | 163 | Publisher 164 | 165 | 166 | 167 | 2023-05-31T09:00:46.300882-04:00 168 | --------------------------------------------------------------------------------