linkml-xsd 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. linkml_xsd-0.2.0/.gitignore +33 -0
  2. linkml_xsd-0.2.0/.gitlab-ci.yml +156 -0
  3. linkml_xsd-0.2.0/.pre-commit-config.yaml +21 -0
  4. linkml_xsd-0.2.0/CITATION.cff +21 -0
  5. linkml_xsd-0.2.0/CONTRIBUTING.md +60 -0
  6. linkml_xsd-0.2.0/Dockerfile +16 -0
  7. linkml_xsd-0.2.0/LICENSE +21 -0
  8. linkml_xsd-0.2.0/PKG-INFO +215 -0
  9. linkml_xsd-0.2.0/README.md +178 -0
  10. linkml_xsd-0.2.0/docs/CONTEXT.md +62 -0
  11. linkml_xsd-0.2.0/docs/LINKML_XSD_MAPPING.md +534 -0
  12. linkml_xsd-0.2.0/docs/TEST_COVERAGE.md +31 -0
  13. linkml_xsd-0.2.0/docs/adr/001-xsd-mapping-approach.md +33 -0
  14. linkml_xsd-0.2.0/docs/adr/002-cli-framework.md +24 -0
  15. linkml_xsd-0.2.0/docs/adr/003-schemaview-foundation.md +52 -0
  16. linkml_xsd-0.2.0/docs/generators/xsd.md +116 -0
  17. linkml_xsd-0.2.0/examples/person.xsd +38 -0
  18. linkml_xsd-0.2.0/examples/person.yaml +43 -0
  19. linkml_xsd-0.2.0/examples/person_example.xml +9 -0
  20. linkml_xsd-0.2.0/pyproject.toml +89 -0
  21. linkml_xsd-0.2.0/setup.cfg +4 -0
  22. linkml_xsd-0.2.0/src/linkml_to_xsd/__init__.py +19 -0
  23. linkml_xsd-0.2.0/src/linkml_to_xsd/cli.py +81 -0
  24. linkml_xsd-0.2.0/src/linkml_to_xsd/config.py +123 -0
  25. linkml_xsd-0.2.0/src/linkml_to_xsd/diagnostics.py +20 -0
  26. linkml_xsd-0.2.0/src/linkml_to_xsd/emit/__init__.py +11 -0
  27. linkml_xsd-0.2.0/src/linkml_to_xsd/emit/generator.py +739 -0
  28. linkml_xsd-0.2.0/src/linkml_to_xsd/emit/typemap.py +80 -0
  29. linkml_xsd-0.2.0/src/linkml_to_xsd/expressions.py +133 -0
  30. linkml_xsd-0.2.0/src/linkml_to_xsd/frontend/__init__.py +14 -0
  31. linkml_xsd-0.2.0/src/linkml_to_xsd/frontend/schema_frontend.py +124 -0
  32. linkml_xsd-0.2.0/src/linkml_to_xsd/i18n.py +44 -0
  33. linkml_xsd-0.2.0/src/linkml_to_xsd/naming.py +25 -0
  34. linkml_xsd-0.2.0/src/linkml_to_xsd/profile/__init__.py +23 -0
  35. linkml_xsd-0.2.0/src/linkml_to_xsd/profile/akn.py +258 -0
  36. linkml_xsd-0.2.0/src/linkml_to_xsd/profile/base.py +117 -0
  37. linkml_xsd-0.2.0/src/linkml_to_xsd/py.typed +0 -0
  38. linkml_xsd-0.2.0/src/linkml_to_xsd/rules.py +85 -0
  39. linkml_xsd-0.2.0/src/linkml_to_xsd/schematron/__init__.py +13 -0
  40. linkml_xsd-0.2.0/src/linkml_to_xsd/schematron/generator.py +147 -0
  41. linkml_xsd-0.2.0/src/linkml_to_xsd/schematron/spec.py +30 -0
  42. linkml_xsd-0.2.0/src/linkml_to_xsd/xml_generator.py +133 -0
  43. linkml_xsd-0.2.0/src/linkml_to_xsd/xsdgen.py +186 -0
  44. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/PKG-INFO +215 -0
  45. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/SOURCES.txt +227 -0
  46. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/dependency_links.txt +1 -0
  47. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/entry_points.txt +3 -0
  48. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/requires.txt +15 -0
  49. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/scm_file_list.json +223 -0
  50. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/scm_version.json +8 -0
  51. linkml_xsd-0.2.0/src/linkml_xsd.egg-info/top_level.txt +1 -0
  52. linkml_xsd-0.2.0/tests/README.md +57 -0
  53. linkml_xsd-0.2.0/tests/__init__.py +0 -0
  54. linkml_xsd-0.2.0/tests/_coverage.py +132 -0
  55. linkml_xsd-0.2.0/tests/_harness.py +140 -0
  56. linkml_xsd-0.2.0/tests/compliance/README.md +66 -0
  57. linkml_xsd-0.2.0/tests/compliance/attribute/case.yaml +4 -0
  58. linkml_xsd-0.2.0/tests/compliance/attribute/expected.xsd +14 -0
  59. linkml_xsd-0.2.0/tests/compliance/attribute/invalid/missing-sku.xml +2 -0
  60. linkml_xsd-0.2.0/tests/compliance/attribute/invalid/unknown-attr.xml +2 -0
  61. linkml_xsd-0.2.0/tests/compliance/attribute/schema.yaml +10 -0
  62. linkml_xsd-0.2.0/tests/compliance/attribute/valid/full.xml +2 -0
  63. linkml_xsd-0.2.0/tests/compliance/attribute/valid/no-note.xml +2 -0
  64. linkml_xsd-0.2.0/tests/compliance/choice-content/case.yaml +3 -0
  65. linkml_xsd-0.2.0/tests/compliance/choice-content/expected.xsd +33 -0
  66. linkml_xsd-0.2.0/tests/compliance/choice-content/invalid/num-after-content.xml +2 -0
  67. linkml_xsd-0.2.0/tests/compliance/choice-content/invalid/section-missing-eid.xml +2 -0
  68. linkml_xsd-0.2.0/tests/compliance/choice-content/invalid/unknown-child.xml +2 -0
  69. linkml_xsd-0.2.0/tests/compliance/choice-content/schema.yaml +31 -0
  70. linkml_xsd-0.2.0/tests/compliance/choice-content/valid/empty.xml +2 -0
  71. linkml_xsd-0.2.0/tests/compliance/choice-content/valid/interleaved.xml +2 -0
  72. linkml_xsd-0.2.0/tests/compliance/element-naming/case.yaml +4 -0
  73. linkml_xsd-0.2.0/tests/compliance/element-naming/expected.xsd +12 -0
  74. linkml_xsd-0.2.0/tests/compliance/element-naming/invalid/class-name-root.xml +2 -0
  75. linkml_xsd-0.2.0/tests/compliance/element-naming/invalid/slot-name-element.xml +2 -0
  76. linkml_xsd-0.2.0/tests/compliance/element-naming/schema.yaml +15 -0
  77. linkml_xsd-0.2.0/tests/compliance/element-naming/valid/ok.xml +2 -0
  78. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/case.yaml +3 -0
  79. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/expected.xsd +12 -0
  80. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/invalid/unqualified-unknown-attr.xml +2 -0
  81. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/schema.yaml +22 -0
  82. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/valid/foreign-attrs.xml +4 -0
  83. linkml_xsd-0.2.0/tests/compliance/foreign-ns-attribute/valid/no-foreign-attrs.xml +2 -0
  84. linkml_xsd-0.2.0/tests/compliance/gen-arrays/case.yaml +1 -0
  85. linkml_xsd-0.2.0/tests/compliance/gen-arrays/expected.xsd +23 -0
  86. linkml_xsd-0.2.0/tests/compliance/gen-arrays/invalid/four-cols.xml +2 -0
  87. linkml_xsd-0.2.0/tests/compliance/gen-arrays/invalid/three-rows.xml +2 -0
  88. linkml_xsd-0.2.0/tests/compliance/gen-arrays/schema.yaml +15 -0
  89. linkml_xsd-0.2.0/tests/compliance/gen-arrays/valid/absent.xml +2 -0
  90. linkml_xsd-0.2.0/tests/compliance/gen-arrays/valid/two-by-n.xml +2 -0
  91. linkml_xsd-0.2.0/tests/compliance/gen-classes/case.yaml +2 -0
  92. linkml_xsd-0.2.0/tests/compliance/gen-classes/expected.xsd +16 -0
  93. linkml_xsd-0.2.0/tests/compliance/gen-classes/invalid/bad-count.xml +2 -0
  94. linkml_xsd-0.2.0/tests/compliance/gen-classes/invalid/missing-name.xml +2 -0
  95. linkml_xsd-0.2.0/tests/compliance/gen-classes/invalid/missing-rank.xml +2 -0
  96. linkml_xsd-0.2.0/tests/compliance/gen-classes/invalid/too-many-rank.xml +2 -0
  97. linkml_xsd-0.2.0/tests/compliance/gen-classes/schema.yaml +13 -0
  98. linkml_xsd-0.2.0/tests/compliance/gen-classes/valid/full.xml +11 -0
  99. linkml_xsd-0.2.0/tests/compliance/gen-classes/valid/minimal.xml +2 -0
  100. linkml_xsd-0.2.0/tests/compliance/gen-default-range/case.yaml +2 -0
  101. linkml_xsd-0.2.0/tests/compliance/gen-default-range/expected.xsd +12 -0
  102. linkml_xsd-0.2.0/tests/compliance/gen-default-range/invalid/bad-count.xml +2 -0
  103. linkml_xsd-0.2.0/tests/compliance/gen-default-range/schema.yaml +9 -0
  104. linkml_xsd-0.2.0/tests/compliance/gen-default-range/valid/ok.xml +2 -0
  105. linkml_xsd-0.2.0/tests/compliance/gen-defaults/case.yaml +1 -0
  106. linkml_xsd-0.2.0/tests/compliance/gen-defaults/expected.xsd +13 -0
  107. linkml_xsd-0.2.0/tests/compliance/gen-defaults/invalid/bad-fixed.xml +2 -0
  108. linkml_xsd-0.2.0/tests/compliance/gen-defaults/invalid/bad-type.xml +2 -0
  109. linkml_xsd-0.2.0/tests/compliance/gen-defaults/schema.yaml +9 -0
  110. linkml_xsd-0.2.0/tests/compliance/gen-defaults/valid/empty.xml +2 -0
  111. linkml_xsd-0.2.0/tests/compliance/gen-defaults/valid/explicit.xml +2 -0
  112. linkml_xsd-0.2.0/tests/compliance/gen-documentation/case.yaml +4 -0
  113. linkml_xsd-0.2.0/tests/compliance/gen-documentation/expected.xsd +33 -0
  114. linkml_xsd-0.2.0/tests/compliance/gen-documentation/invalid/unknown-element.xml +2 -0
  115. linkml_xsd-0.2.0/tests/compliance/gen-documentation/schema.yaml +22 -0
  116. linkml_xsd-0.2.0/tests/compliance/gen-documentation/valid/empty.xml +2 -0
  117. linkml_xsd-0.2.0/tests/compliance/gen-documentation/valid/ok.xml +2 -0
  118. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/case.yaml +4 -0
  119. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/expected.xsd +25 -0
  120. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/invalid/unknown-element.xml +2 -0
  121. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/schema.yaml +23 -0
  122. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/valid/empty.xml +2 -0
  123. linkml_xsd-0.2.0/tests/compliance/gen-documentation-multilingual/valid/ok.xml +2 -0
  124. linkml_xsd-0.2.0/tests/compliance/gen-enum/case.yaml +2 -0
  125. linkml_xsd-0.2.0/tests/compliance/gen-enum/expected.xsd +18 -0
  126. linkml_xsd-0.2.0/tests/compliance/gen-enum/invalid/bad.xml +2 -0
  127. linkml_xsd-0.2.0/tests/compliance/gen-enum/invalid/missing.xml +2 -0
  128. linkml_xsd-0.2.0/tests/compliance/gen-enum/schema.yaml +14 -0
  129. linkml_xsd-0.2.0/tests/compliance/gen-enum/valid/ok.xml +2 -0
  130. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/case.yaml +2 -0
  131. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/expected.xsd +25 -0
  132. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/invalid/minused.xml +2 -0
  133. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/invalid/unknown.xml +2 -0
  134. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/schema.yaml +20 -0
  135. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/valid/inherited.xml +2 -0
  136. linkml_xsd-0.2.0/tests/compliance/gen-enum-inherits/valid/own.xml +2 -0
  137. linkml_xsd-0.2.0/tests/compliance/gen-facets/case.yaml +1 -0
  138. linkml_xsd-0.2.0/tests/compliance/gen-facets/expected.xsd +31 -0
  139. linkml_xsd-0.2.0/tests/compliance/gen-facets/invalid/bad-pattern.xml +2 -0
  140. linkml_xsd-0.2.0/tests/compliance/gen-facets/invalid/pct-out-of-range.xml +2 -0
  141. linkml_xsd-0.2.0/tests/compliance/gen-facets/invalid/score-out-of-range.xml +2 -0
  142. linkml_xsd-0.2.0/tests/compliance/gen-facets/schema.yaml +18 -0
  143. linkml_xsd-0.2.0/tests/compliance/gen-facets/valid/ok.xml +2 -0
  144. linkml_xsd-0.2.0/tests/compliance/gen-identity/case.yaml +1 -0
  145. linkml_xsd-0.2.0/tests/compliance/gen-identity/expected.xsd +44 -0
  146. linkml_xsd-0.2.0/tests/compliance/gen-identity/invalid/dangling-ref.xml +2 -0
  147. linkml_xsd-0.2.0/tests/compliance/gen-identity/invalid/duplicate-id.xml +2 -0
  148. linkml_xsd-0.2.0/tests/compliance/gen-identity/invalid/duplicate-name.xml +2 -0
  149. linkml_xsd-0.2.0/tests/compliance/gen-identity/schema.yaml +21 -0
  150. linkml_xsd-0.2.0/tests/compliance/gen-identity/valid/minimal.xml +2 -0
  151. linkml_xsd-0.2.0/tests/compliance/gen-identity/valid/ok.xml +2 -0
  152. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/case.yaml +2 -0
  153. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/expected.xsd +36 -0
  154. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/invalid/abstract-as-root.xml +2 -0
  155. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/invalid/cat-missing-inherited-soul.xml +2 -0
  156. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/invalid/dog-missing-breed.xml +2 -0
  157. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/schema.yaml +20 -0
  158. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/valid/cat.xml +2 -0
  159. linkml_xsd-0.2.0/tests/compliance/gen-inheritance/valid/dog.xml +2 -0
  160. linkml_xsd-0.2.0/tests/compliance/gen-ncname/case.yaml +1 -0
  161. linkml_xsd-0.2.0/tests/compliance/gen-ncname/expected.xsd +17 -0
  162. linkml_xsd-0.2.0/tests/compliance/gen-ncname/invalid/bad.xml +2 -0
  163. linkml_xsd-0.2.0/tests/compliance/gen-ncname/schema.yaml +15 -0
  164. linkml_xsd-0.2.0/tests/compliance/gen-ncname/valid/spaced-value.xml +2 -0
  165. linkml_xsd-0.2.0/tests/compliance/gen-reference/case.yaml +2 -0
  166. linkml_xsd-0.2.0/tests/compliance/gen-reference/expected.xsd +20 -0
  167. linkml_xsd-0.2.0/tests/compliance/gen-reference/invalid/missing-title.xml +2 -0
  168. linkml_xsd-0.2.0/tests/compliance/gen-reference/invalid/reference-must-not-be-inlined.xml +2 -0
  169. linkml_xsd-0.2.0/tests/compliance/gen-reference/schema.yaml +14 -0
  170. linkml_xsd-0.2.0/tests/compliance/gen-reference/valid/reference-and-inline.xml +7 -0
  171. linkml_xsd-0.2.0/tests/compliance/gen-reference/valid/title-only.xml +2 -0
  172. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/case.yaml +2 -0
  173. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/expected.xsd +17 -0
  174. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/invalid/bad-value.xml +2 -0
  175. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/invalid/missing.xml +2 -0
  176. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/schema.yaml +20 -0
  177. linkml_xsd-0.2.0/tests/compliance/gen-slot-usage/valid/ok.xml +2 -0
  178. linkml_xsd-0.2.0/tests/compliance/gen-union/case.yaml +1 -0
  179. linkml_xsd-0.2.0/tests/compliance/gen-union/expected.xsd +15 -0
  180. linkml_xsd-0.2.0/tests/compliance/gen-union/invalid/float.xml +2 -0
  181. linkml_xsd-0.2.0/tests/compliance/gen-union/invalid/neither.xml +2 -0
  182. linkml_xsd-0.2.0/tests/compliance/gen-union/schema.yaml +10 -0
  183. linkml_xsd-0.2.0/tests/compliance/gen-union/valid/bool.xml +2 -0
  184. linkml_xsd-0.2.0/tests/compliance/gen-union/valid/int.xml +2 -0
  185. linkml_xsd-0.2.0/tests/compliance/gen-upa/case.yaml +2 -0
  186. linkml_xsd-0.2.0/tests/compliance/gen-upa/expected.xsd +18 -0
  187. linkml_xsd-0.2.0/tests/compliance/gen-upa/invalid/missing-must.xml +2 -0
  188. linkml_xsd-0.2.0/tests/compliance/gen-upa/schema.yaml +16 -0
  189. linkml_xsd-0.2.0/tests/compliance/gen-upa/valid/both.xml +2 -0
  190. linkml_xsd-0.2.0/tests/compliance/gen-upa/valid/must-only.xml +2 -0
  191. linkml_xsd-0.2.0/tests/compliance/mixed-content/case.yaml +3 -0
  192. linkml_xsd-0.2.0/tests/compliance/mixed-content/expected.xsd +40 -0
  193. linkml_xsd-0.2.0/tests/compliance/mixed-content/invalid/unknown-inline.xml +2 -0
  194. linkml_xsd-0.2.0/tests/compliance/mixed-content/schema.yaml +32 -0
  195. linkml_xsd-0.2.0/tests/compliance/mixed-content/valid/nested.xml +2 -0
  196. linkml_xsd-0.2.0/tests/compliance/mixed-content/valid/plain-text.xml +2 -0
  197. linkml_xsd-0.2.0/tests/compliance/namespace/case.yaml +3 -0
  198. linkml_xsd-0.2.0/tests/compliance/namespace/expected.xsd +12 -0
  199. linkml_xsd-0.2.0/tests/compliance/namespace/invalid/no-ns.xml +2 -0
  200. linkml_xsd-0.2.0/tests/compliance/namespace/invalid/wrong-ns.xml +2 -0
  201. linkml_xsd-0.2.0/tests/compliance/namespace/schema.yaml +14 -0
  202. linkml_xsd-0.2.0/tests/compliance/namespace/valid/ok.xml +2 -0
  203. linkml_xsd-0.2.0/tests/compliance/types-builtin/case.yaml +4 -0
  204. linkml_xsd-0.2.0/tests/compliance/types-builtin/expected.xsd +20 -0
  205. linkml_xsd-0.2.0/tests/compliance/types-builtin/invalid/bad-boolean.xml +2 -0
  206. linkml_xsd-0.2.0/tests/compliance/types-builtin/invalid/bad-date.xml +2 -0
  207. linkml_xsd-0.2.0/tests/compliance/types-builtin/invalid/bad-integer.xml +2 -0
  208. linkml_xsd-0.2.0/tests/compliance/types-builtin/schema.yaml +17 -0
  209. linkml_xsd-0.2.0/tests/compliance/types-builtin/valid/all-types.xml +13 -0
  210. linkml_xsd-0.2.0/tests/compliance/types-builtin/valid/empty.xml +2 -0
  211. linkml_xsd-0.2.0/tests/corpora/README.md +27 -0
  212. linkml_xsd-0.2.0/tests/fixtures/nested.yaml +28 -0
  213. linkml_xsd-0.2.0/tests/fixtures/person.yaml +43 -0
  214. linkml_xsd-0.2.0/tests/test_cli.py +137 -0
  215. linkml_xsd-0.2.0/tests/test_compliance.py +109 -0
  216. linkml_xsd-0.2.0/tests/test_config.py +47 -0
  217. linkml_xsd-0.2.0/tests/test_converter.py +74 -0
  218. linkml_xsd-0.2.0/tests/test_corpora.py +52 -0
  219. linkml_xsd-0.2.0/tests/test_coverage.py +35 -0
  220. linkml_xsd-0.2.0/tests/test_expressions.py +186 -0
  221. linkml_xsd-0.2.0/tests/test_frontend.py +99 -0
  222. linkml_xsd-0.2.0/tests/test_property.py +114 -0
  223. linkml_xsd-0.2.0/tests/test_schematron.py +216 -0
  224. linkml_xsd-0.2.0/tests/test_xml_generator.py +55 -0
  225. linkml_xsd-0.2.0/tests/test_xsd11.py +152 -0
  226. linkml_xsd-0.2.0/tests/test_xsts.py +135 -0
  227. linkml_xsd-0.2.0/tools/coverage_matrix.py +40 -0
  228. linkml_xsd-0.2.0/tools/fetch_corpora.py +96 -0
  229. linkml_xsd-0.2.0/tools/gen_golden.py +47 -0
@@ -0,0 +1,33 @@
1
+ # Virtual environments
2
+ .venv/
3
+ venv/
4
+ env/
5
+
6
+ # Python cache
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Tool caches
13
+ .mypy_cache/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+
17
+ # Build artifacts
18
+ build/
19
+ dist/
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+
26
+ # Downloaded test corpora (fetched by tools/fetch_corpora.py), keep the README
27
+ tests/corpora/*
28
+ !tests/corpora/README.md
29
+
30
+ # Coverage artifacts
31
+ .coverage
32
+ coverage.xml
33
+ htmlcov/
@@ -0,0 +1,156 @@
1
+ image: python:3.14
2
+
3
+ stages:
4
+ - test
5
+ - build
6
+ - release
7
+
8
+ variables:
9
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
10
+ # setuptools_scm derives the version from git tags — needs full history.
11
+ GIT_DEPTH: 0
12
+
13
+ cache:
14
+ paths:
15
+ - .cache/pip
16
+
17
+ # Edge-testing: oldest supported (3.10, per requires-python) and newest (3.14).
18
+ test:
19
+ stage: test
20
+ image: $PYTHON_IMAGE
21
+ parallel:
22
+ matrix:
23
+ - PYTHON_IMAGE: ["python:3.10", "python:3.14"]
24
+ before_script:
25
+ - pip install -e ".[dev]"
26
+ script:
27
+ - ruff check src tests
28
+ - ruff format --check src tests
29
+ - mypy src/linkml_to_xsd/
30
+ # Compliance + property tiers run on a plain checkout; corpus tiers skip.
31
+ - pytest -v --cov=linkml_to_xsd --cov-report=term
32
+ # Feeds the GitLab coverage badge (badges/main/coverage.svg).
33
+ coverage: '/TOTAL.*\s+(\d+%)$/'
34
+
35
+ corpus:
36
+ stage: test
37
+ # Exercises the external-corpus smoke tier against LinkML's own schemas.
38
+ # allow_failure: a network hiccup fetching the corpus must not block the pipeline.
39
+ allow_failure: true
40
+ before_script:
41
+ - pip install -e ".[dev]"
42
+ script:
43
+ - python tools/fetch_corpora.py
44
+ - pytest -v tests/test_corpora.py
45
+
46
+ build:
47
+ stage: build
48
+ before_script:
49
+ - pip install build
50
+ script:
51
+ - python -m build
52
+ - ls -l dist/
53
+ artifacts:
54
+ paths:
55
+ - dist/
56
+
57
+ # On a version tag: collect the commit subjects since the previous tag as the
58
+ # release notes (consumed by the `release` job below).
59
+ release-notes:
60
+ stage: build
61
+ rules:
62
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/'
63
+ script:
64
+ - PREV=$(git describe --tags --abbrev=0 "${CI_COMMIT_TAG}^" 2>/dev/null || true)
65
+ - if [ -n "$PREV" ]; then RANGE="${PREV}..${CI_COMMIT_TAG}"; else RANGE="${CI_COMMIT_TAG}"; fi
66
+ - echo "## linkml-to-xsd ${CI_COMMIT_TAG}" > release_notes.md
67
+ - echo "" >> release_notes.md
68
+ - git log --no-merges --pretty='- %s' ${RANGE} >> release_notes.md
69
+ - cat release_notes.md
70
+ artifacts:
71
+ paths:
72
+ - release_notes.md
73
+
74
+ # On a version tag (v1.2.3): publish the wheel/sdist to this project's GitLab
75
+ # package registry. Installable via:
76
+ # pip install linkml-to-xsd \
77
+ # --index-url https://gitlab.com/api/v4/projects/<PROJECT_ID>/packages/pypi/simple
78
+ publish-registry:
79
+ stage: release
80
+ rules:
81
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/'
82
+ needs: [build]
83
+ before_script:
84
+ - pip install twine
85
+ script:
86
+ - >
87
+ TWINE_USERNAME=gitlab-ci-token
88
+ TWINE_PASSWORD=${CI_JOB_TOKEN}
89
+ python -m twine upload
90
+ --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
91
+ dist/*
92
+
93
+ # Create the GitLab release entry for the tag, with the generated changelog.
94
+ release:
95
+ stage: release
96
+ image: registry.gitlab.com/gitlab-org/release-cli:latest
97
+ rules:
98
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/'
99
+ needs: [publish-registry, release-notes]
100
+ script:
101
+ - echo "Releasing $CI_COMMIT_TAG"
102
+ release:
103
+ tag_name: $CI_COMMIT_TAG
104
+ name: "linkml-to-xsd $CI_COMMIT_TAG"
105
+ description: release_notes.md
106
+
107
+ # On a version tag: build the CLI image from the wheel and push it to this
108
+ # project's GitLab container registry (public pulls, no rate-limit drama).
109
+ # docker run --rm registry.gitlab.com/glueswe/public/linkml:v0.1.0 --help
110
+ docker-release:
111
+ stage: release
112
+ image: docker:27
113
+ services:
114
+ - docker:27-dind
115
+ rules:
116
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/'
117
+ needs: [build]
118
+ script:
119
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
120
+ - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG -t $CI_REGISTRY_IMAGE:latest .
121
+ - docker push --all-tags $CI_REGISTRY_IMAGE
122
+
123
+ # On a version tag: publish the wheel/sdist to the public PyPI registry using
124
+ # PyPI trusted publishing (OIDC). Configure the publisher at:
125
+ # https://pypi.org/manage/account/publishing/
126
+ # Project: linkml-xsd, namespace: glueswe, project: linkml, workflow: .gitlab-ci.yml
127
+ publish-pypi:
128
+ stage: release
129
+ rules:
130
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/'
131
+ needs: [build]
132
+ id_tokens:
133
+ PYPI_ID_TOKEN:
134
+ aud: pypi
135
+ before_script:
136
+ - pip install twine
137
+ script:
138
+ - |
139
+ python - <<'PY'
140
+ import json, os, urllib.request
141
+ oidc = os.environ["PYPI_ID_TOKEN"]
142
+ req = urllib.request.Request(
143
+ "https://pypi.org/_/oidc/mint-token",
144
+ data=json.dumps({"token": oidc}).encode(),
145
+ headers={"Content-Type": "application/json"},
146
+ method="POST",
147
+ )
148
+ resp = urllib.request.urlopen(req)
149
+ token = json.loads(resp.read())["token"]
150
+ with open("/tmp/pypi-token", "w") as f:
151
+ f.write(token)
152
+ PY
153
+ - >
154
+ TWINE_USERNAME=__token__
155
+ TWINE_PASSWORD=$(cat /tmp/pypi-token)
156
+ python -m twine upload dist/*
@@ -0,0 +1,21 @@
1
+ # Local hooks running the exact tool versions from `.[dev]` (no rev drift with
2
+ # pyproject pins). Install once with: pre-commit install
3
+ repos:
4
+ - repo: local
5
+ hooks:
6
+ - id: ruff-check
7
+ name: ruff check
8
+ entry: ruff check --fix
9
+ language: system
10
+ types: [python]
11
+ - id: ruff-format
12
+ name: ruff format
13
+ entry: ruff format
14
+ language: system
15
+ types: [python]
16
+ - id: mypy
17
+ name: mypy (src)
18
+ entry: mypy src/linkml_to_xsd/
19
+ language: system
20
+ pass_filenames: false
21
+ files: ^src/
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "linkml-to-xsd"
4
+ abstract: >-
5
+ Generator converting LinkML schemas to XML Schema (XSD 1.0/1.1) and
6
+ Schematron, following the LinkML generator conventions.
7
+ type: software
8
+ authors:
9
+ - family-names: Gutknecht
10
+ given-names: Christian
11
+ affiliation: "Glue Software Engineering AG"
12
+ license: MIT
13
+ repository-code: "https://gitlab.com/glueswe/public/linkml"
14
+ keywords:
15
+ - LinkML
16
+ - XSD
17
+ - XML Schema
18
+ - Schematron
19
+ - schema generation
20
+ - Akoma Ntoso
21
+ - eCH-0296
@@ -0,0 +1,60 @@
1
+ # Contributing
2
+
3
+ Contributions are welcome — the project is built so that most improvements are
4
+ **data, not code**.
5
+
6
+ ## Setup
7
+
8
+ ```bash
9
+ pip install -e ".[dev]"
10
+ pre-commit install # ruff + mypy run on every commit
11
+ ```
12
+
13
+ ## Checks
14
+
15
+ Exactly what CI runs:
16
+
17
+ ```bash
18
+ ruff check src tests && ruff format --check src tests
19
+ mypy src/linkml_to_xsd/
20
+ pytest # 200+ tests, < 10 s
21
+ ```
22
+
23
+ CI tests the oldest (3.10) and newest (3.14) supported Python.
24
+
25
+ ## Adding a feature mapping — one case, one option, one emitter change
26
+
27
+ The house rule for every new LinkML→XSD capability:
28
+
29
+ 1. **Compliance case first** (test-first): `tests/compliance/<feature>/` with
30
+ `schema.yaml`, `valid/*.xml`, `invalid/*.xml` — see
31
+ [`tests/compliance/README.md`](tests/compliance/README.md). Watch it fail.
32
+ 2. If the mapping is a genuine *choice*, add a toggle to
33
+ `config.ConversionOptions` (validated `Literal`, documented default).
34
+ 3. Implement in the emitter (`emit/` for XSD, `schematron/` for rules);
35
+ profile-specific naming goes through `profile/`, never inline.
36
+ 4. `python tools/gen_golden.py <feature>` and **review** the golden XSD —
37
+ it is the contract, not just a snapshot.
38
+ 5. **Tag the case** in `tests/_coverage.py::CASE_FEATURES` —
39
+ `test_coverage.py` fails until you do (this keeps
40
+ [`docs/TEST_COVERAGE.md`](docs/TEST_COVERAGE.md) honest; regenerate it with
41
+ `python tools/coverage_matrix.py`).
42
+ 6. Update the mapping status in
43
+ [`docs/LINKML_XSD_MAPPING.md`](docs/LINKML_XSD_MAPPING.md).
44
+
45
+ A construct you *cannot* express must be reported through
46
+ `diagnostics.report_unsupported` — never dropped silently.
47
+
48
+ ## Merge requests
49
+
50
+ - Branch from `main`; keep MRs focused (one feature/fix).
51
+ - Plain commit messages: what changed and why, imperative subject line.
52
+ - CI must be green; goldens regenerated intentionally (explain drift in the MR).
53
+ - Architectural decisions get an ADR in [`docs/adr/`](docs/adr/).
54
+
55
+ ## Releases
56
+
57
+ Maintainers tag `vX.Y.Z` on `main`; CI builds, publishes to the package
58
+ registry, pushes the Docker image and creates the GitLab release with
59
+ generated notes. Versions come from the tag (`setuptools_scm`) — never edit a
60
+ version string by hand.
@@ -0,0 +1,16 @@
1
+ # linkml-to-xsd CLI image — for XML toolchains without a Python environment.
2
+ #
3
+ # docker run --rm -v $PWD:/work registry.gitlab.com/glueswe/public/linkml:latest \
4
+ # /work/schema.yaml -o /work/schema.xsd
5
+ #
6
+ # CI builds this from the wheel produced by the `build` job (dist/ artifact);
7
+ # for a local build run `python -m build` first.
8
+
9
+ FROM python:3.14-slim
10
+
11
+ COPY dist/*.whl /tmp/dist/
12
+ RUN pip install --no-cache-dir /tmp/dist/*.whl && rm -rf /tmp/dist
13
+
14
+ WORKDIR /work
15
+ ENTRYPOINT ["gen-xsd"]
16
+ CMD ["--help"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Gutknecht, Glue AG
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.
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: linkml-xsd
3
+ Version: 0.2.0
4
+ Summary: Convert LinkML schemas to XSD 1.0/1.1 and Schematron.
5
+ Author-email: Christian Gutknecht <christian.gutknecht@glue.ch>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/glueswe/public/linkml
8
+ Project-URL: Repository, https://gitlab.com/glueswe/public/linkml
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: click>=8.0
23
+ Requires-Dist: lxml>=4.9
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: linkml-runtime>=1.8
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
29
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
30
+ Requires-Dist: xmlschema>=2.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
32
+ Requires-Dist: mypy>=1.0; extra == "dev"
33
+ Requires-Dist: types-pyyaml; extra == "dev"
34
+ Requires-Dist: lxml-stubs; extra == "dev"
35
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # linkml-to-xsd
39
+
40
+ [![pipeline](https://gitlab.com/glueswe/public/linkml/badges/main/pipeline.svg)](https://gitlab.com/glueswe/public/linkml/-/pipelines)
41
+ [![coverage](https://gitlab.com/glueswe/public/linkml/badges/main/coverage.svg)](https://gitlab.com/glueswe/public/linkml/-/pipelines)
42
+ [![release](https://gitlab.com/glueswe/public/linkml/-/badges/release.svg)](https://gitlab.com/glueswe/public/linkml/-/releases)
43
+ [![python](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue)](pyproject.toml)
44
+ [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
45
+
46
+ > **Status 0.x** — the mapping is exercised by 200+ tests and at parity on a
47
+ > real production schema, but the Python API may still change before 1.0.
48
+
49
+ A Python library and CLI that converts [LinkML](https://linkml.io) schemas into
50
+ [XML Schema (XSD)](https://www.w3.org/XML/Schema) — and, for constraints XSD
51
+ cannot express, [Schematron](https://schematron.com). Built on
52
+ [`linkml-runtime`](https://github.com/linkml/linkml-runtime)'s `SchemaView`, so
53
+ `imports`, `mixins`, `slot_usage` and inlining are resolved by the reference
54
+ implementation rather than re-guessed.
55
+
56
+ ## Features
57
+
58
+ - **XSD 1.0** output: classes → `xs:complexType` + global element; `is_a` →
59
+ `xs:extension` (UPA-safe); mixins flattened; enums, user types with facets
60
+ (`pattern`, `minimum_value`, `maximum_value`); cardinality.
61
+ - **References vs. inlining** follow LinkML semantics: a non-inlined class-range
62
+ slot with an identifier becomes a *reference*; `inlined`/`inlined_as_list`
63
+ produce nested elements.
64
+ - **Identity constraints**: `identifier` → `xs:key`, `unique_keys` → `xs:unique`,
65
+ references → `xs:keyref` (real referential integrity).
66
+ - **Value constraints**: `@default` from literal `ifabsent`, `@fixed` from
67
+ `equals_string`/`equals_number`.
68
+ - **Documentation & metadata**: `description` → `xs:documentation`,
69
+ `class_uri`/`slot_uri`/`enum_uri`/`meaning` → `xs:appinfo`.
70
+ - **XSD 1.1** (opt-in): class `rules` also emitted as `xs:assert`.
71
+ - **Schematron**: class `rules` (precondition ⇒ postcondition) and boolean
72
+ combinators (`any_of`/`all_of`/`exactly_one_of`/`none_of`) as XPath assertions.
73
+ - **Profiles**: the eCH-0296 / Akoma Ntoso annotation vocabulary
74
+ (`xml_element`, `xml_attribute`, `xml_namespace`, `xsd_mixed`,
75
+ `xsd_content_model`) via `--profile akn` — proven at parity on the real Fedlex
76
+ SR-101.
77
+ - Example-XML generation and XSD validation helpers.
78
+
79
+ ## Installation
80
+
81
+ From a checkout:
82
+
83
+ ```bash
84
+ pip install -e ".[dev]"
85
+ ```
86
+
87
+ Released versions are published to PyPI and this project's GitLab package
88
+ registry on every `vX.Y.Z` tag:
89
+
90
+ ```bash
91
+ # PyPI (recommended)
92
+ pip install linkml-xsd
93
+
94
+ # GitLab package registry
95
+ pip install linkml-xsd \
96
+ --index-url https://gitlab.com/api/v4/projects/83894258/packages/pypi/simple
97
+ ```
98
+
99
+ Requires Python ≥ 3.10 (CI tests 3.10 and 3.14). Versions are derived
100
+ from git tags (`setuptools_scm`) — no manual version bumps.
101
+
102
+ **No Python at hand** (Java/XSLT toolchains)? Use the Docker image published
103
+ on every release:
104
+
105
+ ```bash
106
+ docker run --rm -v "$PWD":/work \
107
+ registry.gitlab.com/glueswe/public/linkml:latest /work/schema.yaml -o /work/schema.xsd
108
+ ```
109
+
110
+ ## Usage
111
+
112
+ ### CLI
113
+
114
+ Two entry points: **`gen-xsd`** (the [linkml generator
115
+ convention](https://linkml.io/linkml/generators/), like `gen-json-schema`; see
116
+ [`docs/generators/xsd.md`](docs/generators/xsd.md)) and the richer `linkml2xsd`
117
+ group (`convert` / `example` / `validate`).
118
+
119
+ ```bash
120
+ # linkml-style generator CLI
121
+ gen-xsd schema.yaml -o schema.xsd
122
+ gen-xsd schema.yaml --xsd-version 1.1 --schematron schema.sch
123
+
124
+ # Standard LinkML → XSD (targetNamespace derived from the schema id/default_prefix)
125
+ linkml2xsd convert schema.yaml -o schema.xsd
126
+
127
+ # eCH-0296 / Akoma Ntoso profile + a companion Schematron for rules
128
+ linkml2xsd convert schema.yaml -o schema.xsd --profile akn --schematron schema.sch
129
+
130
+ # Example XML instance / validate an instance against an XSD
131
+ linkml2xsd example schema.yaml -o example.xml
132
+ linkml2xsd validate example.xml --xsd schema.xsd
133
+ ```
134
+
135
+ ### Python API
136
+
137
+ ```python
138
+ from linkml_to_xsd import XSDGenerator, SchematronGenerator, ConversionOptions
139
+
140
+ options = ConversionOptions(profile="akn", xsd_version="1.1", emit_appinfo=True)
141
+ xsd = XSDGenerator("schema.yaml", options=options).generate()
142
+
143
+ sch_gen = SchematronGenerator("schema.yaml", options=options)
144
+ sch = sch_gen.generate() if sch_gen.has_rules() else None
145
+ ```
146
+
147
+ `XSDGenerator` accepts a file path, inline YAML, a dict, a `SchemaDefinition` or a
148
+ `SchemaFrontend`.
149
+
150
+ ## Options (`ConversionOptions`)
151
+
152
+ Every genuine mapping choice is a validated field with a default:
153
+
154
+ | Option | Default | Effect |
155
+ |--------|---------|--------|
156
+ | `xsd_version` | `"1.0"` | `"1.1"` also emits `xs:assert` for rules |
157
+ | `emit_schematron` | `False` | (API flag; the CLI uses `--schematron`) |
158
+ | `inlining` | `"linkml"` | or `always_inline` / `always_reference` |
159
+ | `reference_style` | `"identifier"` | or `uri` (`key`/`idref` planned) |
160
+ | `emit_value_constraints` | `True` | facets / `@fixed` |
161
+ | `emit_identity_constraints` | `True` | `xs:key`/`unique`/`keyref` |
162
+ | `emit_documentation` / `emit_appinfo` | `True` / `False` | annotations |
163
+ | `multilingual_documentation` | `False` | split `[en] … [de] …`-tagged descriptions into per-language `xs:documentation` |
164
+ | `target_namespace` | `None` | override the derived namespace |
165
+ | `profile` | `None` | e.g. `"akn"` |
166
+
167
+ ## Architecture
168
+
169
+ ```
170
+ src/linkml_to_xsd/
171
+ config.py # ConversionOptions — one toggle per mapping decision
172
+ frontend/ # SchemaFrontend over linkml-runtime SchemaView
173
+ emit/ # XSDGenerator (+ naming, typemap)
174
+ schematron/ # SchematronGenerator
175
+ rules.py # shared rule → XPath compiler (Schematron + xs:assert)
176
+ profile/ # base (no-op) + akn (eCH-0296 / Akoma Ntoso)
177
+ cli.py xml_generator.py
178
+ ```
179
+
180
+ See [`docs/adr/`](docs/adr/) for architectural decisions (ADR 003 records the
181
+ move to `SchemaView`) and [`docs/LINKML_XSD_MAPPING.md`](docs/LINKML_XSD_MAPPING.md)
182
+ for the full LinkML ↔ XSD element mapping and current status.
183
+
184
+ ## Testing
185
+
186
+ Multi-tier and documented in [`tests/README.md`](tests/README.md);
187
+ [`docs/TEST_COVERAGE.md`](docs/TEST_COVERAGE.md) is the enforced feature × engine
188
+ matrix.
189
+
190
+ | Tier | Checks |
191
+ |------|--------|
192
+ | **Compliance** (`tests/compliance/*`) | one feature per case → XSD compiles, matches its golden, accepts/rejects the right instances |
193
+ | **Coverage** | every case is feature-tagged; baseline features stay covered |
194
+ | **Corpus** | real `kitchen_sink.yaml` + `meta.yaml` convert and compile |
195
+ | **Property** (Hypothesis) | random supported schemas always compile |
196
+ | **XSTS reference** | inventories XSD constructs LinkML can never emit |
197
+
198
+ ```bash
199
+ pytest
200
+ ruff check src tests && ruff format --check src tests
201
+ mypy src/linkml_to_xsd/
202
+
203
+ python tools/fetch_corpora.py [--xsts] # opt-in corpora (git-ignored)
204
+ python tools/gen_golden.py # refresh golden XSDs after a change
205
+ python tools/coverage_matrix.py # regenerate docs/TEST_COVERAGE.md
206
+ ```
207
+
208
+ ## Contributing
209
+
210
+ See [CONTRIBUTING.md](CONTRIBUTING.md) — most improvements are data, not code:
211
+ a new feature mapping is one compliance case + one option + one emitter change.
212
+
213
+ ## License
214
+
215
+ MIT