docx4j-xsdata 26.2.1__py3-none-any.whl
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.
- docx4j_xsdata/__init__.py +1 -0
- docx4j_xsdata/__main__.py +16 -0
- docx4j_xsdata/cli.py +169 -0
- docx4j_xsdata/codegen/__init__.py +6 -0
- docx4j_xsdata/codegen/class_names.py +217 -0
- docx4j_xsdata/codegen/container.py +296 -0
- docx4j_xsdata/codegen/exceptions.py +23 -0
- docx4j_xsdata/codegen/handlers/__init__.py +55 -0
- docx4j_xsdata/codegen/handlers/add_attribute_substitutions.py +122 -0
- docx4j_xsdata/codegen/handlers/apply_class_names.py +191 -0
- docx4j_xsdata/codegen/handlers/calculate_attribute_paths.py +87 -0
- docx4j_xsdata/codegen/handlers/create_compound_fields.py +263 -0
- docx4j_xsdata/codegen/handlers/create_wrapper_fields.py +140 -0
- docx4j_xsdata/codegen/handlers/designate_class_packages.py +233 -0
- docx4j_xsdata/codegen/handlers/detect_circular_references.py +93 -0
- docx4j_xsdata/codegen/handlers/disambiguate_choices.py +301 -0
- docx4j_xsdata/codegen/handlers/filter_classes.py +41 -0
- docx4j_xsdata/codegen/handlers/flatten_attribute_groups.py +55 -0
- docx4j_xsdata/codegen/handlers/flatten_class_extensions.py +387 -0
- docx4j_xsdata/codegen/handlers/merge_attributes.py +93 -0
- docx4j_xsdata/codegen/handlers/merge_duplicate_classes.py +73 -0
- docx4j_xsdata/codegen/handlers/process_attributes_types.py +339 -0
- docx4j_xsdata/codegen/handlers/process_mixed_content_class.py +47 -0
- docx4j_xsdata/codegen/handlers/rename_duplicate_attributes.py +17 -0
- docx4j_xsdata/codegen/handlers/rename_duplicate_classes.py +164 -0
- docx4j_xsdata/codegen/handlers/reset_attribute_sequence_numbers.py +51 -0
- docx4j_xsdata/codegen/handlers/reset_attribute_sequences.py +54 -0
- docx4j_xsdata/codegen/handlers/sanitize_attributes_default_value.py +284 -0
- docx4j_xsdata/codegen/handlers/sanitize_enumeration_class.py +63 -0
- docx4j_xsdata/codegen/handlers/unnest_inner_classes.py +107 -0
- docx4j_xsdata/codegen/handlers/update_attributes_effective_choice.py +169 -0
- docx4j_xsdata/codegen/handlers/vacuum_inner_classes.py +97 -0
- docx4j_xsdata/codegen/handlers/validate_attributes_overrides.py +202 -0
- docx4j_xsdata/codegen/handlers/validate_references.py +108 -0
- docx4j_xsdata/codegen/mappers/__init__.py +13 -0
- docx4j_xsdata/codegen/mappers/definitions.py +576 -0
- docx4j_xsdata/codegen/mappers/dict.py +75 -0
- docx4j_xsdata/codegen/mappers/dtd.py +332 -0
- docx4j_xsdata/codegen/mappers/element.py +172 -0
- docx4j_xsdata/codegen/mappers/mixins.py +128 -0
- docx4j_xsdata/codegen/mappers/schema.py +399 -0
- docx4j_xsdata/codegen/mixins.py +193 -0
- docx4j_xsdata/codegen/models.py +771 -0
- docx4j_xsdata/codegen/parsers/__init__.py +9 -0
- docx4j_xsdata/codegen/parsers/definitions.py +53 -0
- docx4j_xsdata/codegen/parsers/dtd.py +134 -0
- docx4j_xsdata/codegen/parsers/schema.py +412 -0
- docx4j_xsdata/codegen/resolver.py +279 -0
- docx4j_xsdata/codegen/stopwatch.py +16 -0
- docx4j_xsdata/codegen/transformer.py +449 -0
- docx4j_xsdata/codegen/utils.py +524 -0
- docx4j_xsdata/codegen/validator.py +201 -0
- docx4j_xsdata/codegen/writer.py +88 -0
- docx4j_xsdata/exceptions.py +30 -0
- docx4j_xsdata/formats/__init__.py +0 -0
- docx4j_xsdata/formats/converter.py +878 -0
- docx4j_xsdata/formats/dataclass/__init__.py +0 -0
- docx4j_xsdata/formats/dataclass/client.py +198 -0
- docx4j_xsdata/formats/dataclass/compat.py +205 -0
- docx4j_xsdata/formats/dataclass/context.py +311 -0
- docx4j_xsdata/formats/dataclass/filters.py +1138 -0
- docx4j_xsdata/formats/dataclass/generator.py +410 -0
- docx4j_xsdata/formats/dataclass/models/__init__.py +0 -0
- docx4j_xsdata/formats/dataclass/models/builders.py +600 -0
- docx4j_xsdata/formats/dataclass/models/elements.py +687 -0
- docx4j_xsdata/formats/dataclass/models/generics.py +46 -0
- docx4j_xsdata/formats/dataclass/parsers/__init__.py +12 -0
- docx4j_xsdata/formats/dataclass/parsers/bases.py +293 -0
- docx4j_xsdata/formats/dataclass/parsers/config.py +75 -0
- docx4j_xsdata/formats/dataclass/parsers/dict.py +421 -0
- docx4j_xsdata/formats/dataclass/parsers/handlers/__init__.py +22 -0
- docx4j_xsdata/formats/dataclass/parsers/handlers/lxml.py +98 -0
- docx4j_xsdata/formats/dataclass/parsers/handlers/native.py +171 -0
- docx4j_xsdata/formats/dataclass/parsers/json.py +98 -0
- docx4j_xsdata/formats/dataclass/parsers/mixins.py +301 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/__init__.py +17 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/element.py +722 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/primitive.py +71 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/skip.py +21 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/standard.py +97 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/union.py +190 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/wildcard.py +117 -0
- docx4j_xsdata/formats/dataclass/parsers/nodes/wrapper.py +55 -0
- docx4j_xsdata/formats/dataclass/parsers/skipped.py +136 -0
- docx4j_xsdata/formats/dataclass/parsers/tree.py +73 -0
- docx4j_xsdata/formats/dataclass/parsers/utils.py +315 -0
- docx4j_xsdata/formats/dataclass/parsers/xml.py +123 -0
- docx4j_xsdata/formats/dataclass/ruff.toml +12 -0
- docx4j_xsdata/formats/dataclass/serializers/__init__.py +21 -0
- docx4j_xsdata/formats/dataclass/serializers/code.py +199 -0
- docx4j_xsdata/formats/dataclass/serializers/config.py +79 -0
- docx4j_xsdata/formats/dataclass/serializers/dict.py +109 -0
- docx4j_xsdata/formats/dataclass/serializers/json.py +43 -0
- docx4j_xsdata/formats/dataclass/serializers/mixins.py +1259 -0
- docx4j_xsdata/formats/dataclass/serializers/tree.py +34 -0
- docx4j_xsdata/formats/dataclass/serializers/writers/__init__.py +18 -0
- docx4j_xsdata/formats/dataclass/serializers/writers/lxml.py +103 -0
- docx4j_xsdata/formats/dataclass/serializers/writers/native.py +100 -0
- docx4j_xsdata/formats/dataclass/serializers/xml.py +53 -0
- docx4j_xsdata/formats/dataclass/templates/class.jinja2 +55 -0
- docx4j_xsdata/formats/dataclass/templates/docstrings.accessible.jinja2 +1 -0
- docx4j_xsdata/formats/dataclass/templates/docstrings.blank.jinja2 +0 -0
- docx4j_xsdata/formats/dataclass/templates/docstrings.google.jinja2 +8 -0
- docx4j_xsdata/formats/dataclass/templates/docstrings.numpy.jinja2 +12 -0
- docx4j_xsdata/formats/dataclass/templates/docstrings.rst.jinja2 +9 -0
- docx4j_xsdata/formats/dataclass/templates/enum.jinja2 +20 -0
- docx4j_xsdata/formats/dataclass/templates/imports.jinja2 +11 -0
- docx4j_xsdata/formats/dataclass/templates/module.jinja2 +17 -0
- docx4j_xsdata/formats/dataclass/templates/package.jinja2 +54 -0
- docx4j_xsdata/formats/dataclass/templates/service.jinja2 +4 -0
- docx4j_xsdata/formats/dataclass/transports.py +92 -0
- docx4j_xsdata/formats/dataclass/typing.py +280 -0
- docx4j_xsdata/formats/mixins.py +102 -0
- docx4j_xsdata/formats/types.py +3 -0
- docx4j_xsdata/logger.py +3 -0
- docx4j_xsdata/models/__init__.py +0 -0
- docx4j_xsdata/models/config.py +660 -0
- docx4j_xsdata/models/datatype.py +712 -0
- docx4j_xsdata/models/dtd.py +143 -0
- docx4j_xsdata/models/enums.py +344 -0
- docx4j_xsdata/models/mixins.py +225 -0
- docx4j_xsdata/models/wsdl.py +319 -0
- docx4j_xsdata/models/xsd.py +1050 -0
- docx4j_xsdata/py.typed +0 -0
- docx4j_xsdata/schemas/mathml3-common.xsd +99 -0
- docx4j_xsdata/schemas/mathml3-content.xsd +684 -0
- docx4j_xsdata/schemas/mathml3-presentation.xsd +2151 -0
- docx4j_xsdata/schemas/mathml3-strict-content.xsd +186 -0
- docx4j_xsdata/schemas/mathml3.xsd +9 -0
- docx4j_xsdata/schemas/soapenc.xsd +536 -0
- docx4j_xsdata/schemas/xlink.xsd +270 -0
- docx4j_xsdata/schemas/xml.xsd +34 -0
- docx4j_xsdata/schemas/xsi.xsd +9 -0
- docx4j_xsdata/utils/__init__.py +0 -0
- docx4j_xsdata/utils/click.py +200 -0
- docx4j_xsdata/utils/collections.py +116 -0
- docx4j_xsdata/utils/constants.py +21 -0
- docx4j_xsdata/utils/dates.py +280 -0
- docx4j_xsdata/utils/debug.py +35 -0
- docx4j_xsdata/utils/downloader.py +113 -0
- docx4j_xsdata/utils/graphs.py +49 -0
- docx4j_xsdata/utils/hooks.py +14 -0
- docx4j_xsdata/utils/namespaces.py +188 -0
- docx4j_xsdata/utils/objects.py +29 -0
- docx4j_xsdata/utils/package.py +30 -0
- docx4j_xsdata/utils/testing.py +628 -0
- docx4j_xsdata/utils/text.py +217 -0
- docx4j_xsdata-26.2.1.dist-info/METADATA +167 -0
- docx4j_xsdata-26.2.1.dist-info/RECORD +153 -0
- docx4j_xsdata-26.2.1.dist-info/WHEEL +5 -0
- docx4j_xsdata-26.2.1.dist-info/entry_points.txt +2 -0
- docx4j_xsdata-26.2.1.dist-info/licenses/LICENSE +21 -0
- docx4j_xsdata-26.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "26.2.1"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main() -> None:
|
|
5
|
+
"""Cli entry point."""
|
|
6
|
+
try:
|
|
7
|
+
from docx4j_xsdata.cli import cli
|
|
8
|
+
|
|
9
|
+
cli()
|
|
10
|
+
except ImportError:
|
|
11
|
+
print('Install cli requirements "pip install docx4j-xsdata[cli]"')
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
docx4j_xsdata/cli.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from docx4j_xsdata import __version__
|
|
12
|
+
from docx4j_xsdata.codegen.transformer import ResourceTransformer
|
|
13
|
+
from docx4j_xsdata.logger import logger
|
|
14
|
+
from docx4j_xsdata.models.config import GeneratorConfig, GeneratorOutput
|
|
15
|
+
from docx4j_xsdata.utils.click import URL, LogFormatter, LogHandler, model_options
|
|
16
|
+
from docx4j_xsdata.utils.downloader import Downloader
|
|
17
|
+
from docx4j_xsdata.utils.hooks import load_entry_points
|
|
18
|
+
|
|
19
|
+
# Load cli plugins
|
|
20
|
+
load_entry_points("docx4j_xsdata.plugins.cli")
|
|
21
|
+
|
|
22
|
+
# Setup xsdata logger to print records to stdout/stderr
|
|
23
|
+
handler = LogHandler()
|
|
24
|
+
handler.formatter = LogFormatter()
|
|
25
|
+
|
|
26
|
+
logger.handlers = [handler]
|
|
27
|
+
logger.propagate = False
|
|
28
|
+
|
|
29
|
+
# Attach the cli handler to the python warnings logger
|
|
30
|
+
py_warnings = logging.getLogger("py.warnings")
|
|
31
|
+
py_warnings.handlers = [handler]
|
|
32
|
+
py_warnings.propagate = False
|
|
33
|
+
|
|
34
|
+
# Log warnings as well
|
|
35
|
+
logging.captureWarnings(True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group()
|
|
39
|
+
@click.pass_context
|
|
40
|
+
@click.version_option(__version__)
|
|
41
|
+
def cli(ctx: click.Context, **kwargs: Any) -> None:
|
|
42
|
+
"""Xsdata command line interface."""
|
|
43
|
+
logger.setLevel(logging.INFO)
|
|
44
|
+
formatwarning_orig = warnings.formatwarning
|
|
45
|
+
|
|
46
|
+
logger.info(
|
|
47
|
+
"========= docx4j-xsdata v%s / Python %s / Platform %s =========\n",
|
|
48
|
+
__version__,
|
|
49
|
+
platform.python_version(),
|
|
50
|
+
sys.platform,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def format_warning(message: Any, category: Any, *args: Any) -> str:
|
|
54
|
+
return (
|
|
55
|
+
f"{category.__name__}: {message}" if category else message
|
|
56
|
+
) # pragma: no cover
|
|
57
|
+
|
|
58
|
+
def format_warning_restore() -> None:
|
|
59
|
+
warnings.formatwarning = formatwarning_orig
|
|
60
|
+
|
|
61
|
+
warnings.formatwarning = format_warning # type: ignore
|
|
62
|
+
|
|
63
|
+
ctx.call_on_close(format_warning_restore)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@cli.command("init-config")
|
|
67
|
+
@click.argument("output", type=click.Path(), default=".xsdata.xml")
|
|
68
|
+
def init_config(**kwargs: Any) -> None:
|
|
69
|
+
"""Create or update a configuration file."""
|
|
70
|
+
file_path = Path(kwargs["output"])
|
|
71
|
+
if file_path.exists():
|
|
72
|
+
config = GeneratorConfig.read(file_path)
|
|
73
|
+
logger.info("Updating configuration file %s", kwargs["output"])
|
|
74
|
+
else:
|
|
75
|
+
logger.info("Initializing configuration file %s", kwargs["output"])
|
|
76
|
+
config = GeneratorConfig.create()
|
|
77
|
+
|
|
78
|
+
with file_path.open("w") as fp:
|
|
79
|
+
config.write(fp, config)
|
|
80
|
+
|
|
81
|
+
handler.emit_warnings()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@cli.command("download")
|
|
85
|
+
@click.argument("source", type=URL(), required=True)
|
|
86
|
+
@click.option(
|
|
87
|
+
"-o",
|
|
88
|
+
"--output",
|
|
89
|
+
type=click.Path(),
|
|
90
|
+
default="./",
|
|
91
|
+
help="Output directory, default cwd",
|
|
92
|
+
)
|
|
93
|
+
def download(source: str, output: str) -> None:
|
|
94
|
+
"""Download a schema or a definition locally with all its dependencies."""
|
|
95
|
+
downloader = Downloader(output=Path(output).resolve())
|
|
96
|
+
downloader.wget(source)
|
|
97
|
+
|
|
98
|
+
handler.emit_warnings()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_SUPPORTED_EXTENSIONS = ("wsdl", "xsd", "dtd", "xml", "json")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@cli.command("generate")
|
|
105
|
+
@click.argument("source", required=True)
|
|
106
|
+
@click.option(
|
|
107
|
+
"-r",
|
|
108
|
+
"--recursive",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
default=False,
|
|
111
|
+
help="Search files recursively in the source directory",
|
|
112
|
+
)
|
|
113
|
+
@click.option("-c", "--config", default=".xsdata.xml", help="Project configuration")
|
|
114
|
+
@click.option("--cache", is_flag=True, default=False, help="Cache sources loading")
|
|
115
|
+
@click.option("--debug", is_flag=True, default=False, help="Show debug messages")
|
|
116
|
+
@click.option(
|
|
117
|
+
"--extensions",
|
|
118
|
+
default=",".join(_SUPPORTED_EXTENSIONS),
|
|
119
|
+
help="Comma-separated list of extensions to filter",
|
|
120
|
+
)
|
|
121
|
+
@model_options(GeneratorOutput)
|
|
122
|
+
def generate(**kwargs: Any) -> None:
|
|
123
|
+
"""Generate code from xsd, dtd, wsdl, xml and json files.
|
|
124
|
+
|
|
125
|
+
The input source can be either a filepath, uri or a directory
|
|
126
|
+
containing xml, json, xsd and wsdl files.
|
|
127
|
+
"""
|
|
128
|
+
debug = kwargs.pop("debug")
|
|
129
|
+
if debug:
|
|
130
|
+
logger.setLevel(logging.DEBUG)
|
|
131
|
+
|
|
132
|
+
source = kwargs.pop("source")
|
|
133
|
+
cache = kwargs.pop("cache")
|
|
134
|
+
recursive = kwargs.pop("recursive")
|
|
135
|
+
config_file = Path(kwargs.pop("config")).resolve()
|
|
136
|
+
|
|
137
|
+
# Parse the comma-separated extensions string into a tuple
|
|
138
|
+
extensions_str = kwargs.pop("extensions")
|
|
139
|
+
extensions = tuple(ext.strip() for ext in extensions_str.split(",") if ext.strip())
|
|
140
|
+
|
|
141
|
+
params = {k.replace("__", "."): v for k, v in kwargs.items() if v is not None}
|
|
142
|
+
config = GeneratorConfig.read(config_file)
|
|
143
|
+
config.output.update(**params)
|
|
144
|
+
|
|
145
|
+
transformer = ResourceTransformer(config=config)
|
|
146
|
+
uris = sorted(resolve_source(source, recursive=recursive, extensions=extensions))
|
|
147
|
+
transformer.process(uris, cache=cache)
|
|
148
|
+
|
|
149
|
+
handler.emit_warnings()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def resolve_source(
|
|
153
|
+
source: str, recursive: bool, extensions: tuple[str, ...] = _SUPPORTED_EXTENSIONS
|
|
154
|
+
) -> Iterator[str]:
|
|
155
|
+
"""Yields all supported resource URIs."""
|
|
156
|
+
if source.find("://") > -1 and not source.startswith("file://"):
|
|
157
|
+
yield source
|
|
158
|
+
else:
|
|
159
|
+
path = Path(source).resolve()
|
|
160
|
+
match = "**/*" if recursive else "*"
|
|
161
|
+
if path.is_dir():
|
|
162
|
+
for ext in extensions:
|
|
163
|
+
yield from (x.as_uri() for x in path.glob(f"{match}.{ext}"))
|
|
164
|
+
else: # is a file
|
|
165
|
+
yield path.as_uri()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__": # pragma: no cover
|
|
169
|
+
cli()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""The docx4j fork's explicit class name table.
|
|
2
|
+
|
|
3
|
+
`Substitutions` are regular expressions, which cannot express a thousand
|
|
4
|
+
entry name table like docx4j's `org.docx4j.wml.ObjectFactory`. This is
|
|
5
|
+
the table: one JSON file per namespace in a directory, named by the
|
|
6
|
+
generator config's `<Output><ClassNames>` element.
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{
|
|
10
|
+
"namespace": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
11
|
+
"java_package": "org.docx4j.wml",
|
|
12
|
+
"types": {"CT_PPr": "PPr", "CT_Settings": "CTSettings", "ST_Jc": "STJc"},
|
|
13
|
+
"elements": {"document": "Document", "ins": "RunIns", "t@CT_R": "RT"}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`types` maps the name of a complexType or simpleType in that namespace,
|
|
18
|
+
`elements` the name of a global or local element, to the name of the
|
|
19
|
+
class the generator emits for it. Names that are not in the table keep
|
|
20
|
+
the naming conventions. A name from the table is emitted verbatim: it is
|
|
21
|
+
applied before the case convention, not through it.
|
|
22
|
+
|
|
23
|
+
An `elements` key may carry a **scope**, `element@EnclosingType`, the
|
|
24
|
+
analogue of JAXB's `@XmlElementDecl(scope=...)`. The enclosing type is
|
|
25
|
+
read in the namespace of the file the entry is written in, so `t@CT_R`
|
|
26
|
+
in the wml file is wml's `CT_R` and not the `CT_R` of another namespace
|
|
27
|
+
that happens to hold a wml element. It addresses the one
|
|
28
|
+
element of the one compound field the generator invented an intermediate
|
|
29
|
+
class for (`DisambiguateChoices`, `w:t` of `CT_R` where `w:t`,
|
|
30
|
+
`w:instrText` and `w:delInstrText` all have the type `CT_Text`). Those
|
|
31
|
+
classes have no xml identity of their own -- the element name lives in the
|
|
32
|
+
`choices` metadata of the field -- and JAXB had no class for them either,
|
|
33
|
+
it had one `JAXBElement` per name over the shared type. An unscoped entry
|
|
34
|
+
therefore never renames them; only a scoped one does.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
from dataclasses import dataclass, field
|
|
39
|
+
from keyword import iskeyword
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from docx4j_xsdata.codegen.exceptions import CodegenError
|
|
43
|
+
from docx4j_xsdata.logger import logger
|
|
44
|
+
from docx4j_xsdata.utils.namespaces import build_qname
|
|
45
|
+
|
|
46
|
+
TYPES = "types"
|
|
47
|
+
ELEMENTS = "elements"
|
|
48
|
+
SCOPE_SEPARATOR = "@"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ClassNames:
|
|
53
|
+
"""An explicit schema name to class name table, by namespace.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
types: The (namespace, type name) to class name map
|
|
57
|
+
elements: The (namespace, element name) to class name map
|
|
58
|
+
scoped: The (namespace, element name, enclosing type qualified
|
|
59
|
+
name) to class name map, from the `element@EnclosingType`
|
|
60
|
+
keys; the enclosing type is qualified with the namespace of
|
|
61
|
+
the file the entry came from
|
|
62
|
+
java_packages: The namespace to java package map, informative
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
types: dict[tuple[str, str], str] = field(default_factory=dict)
|
|
66
|
+
elements: dict[tuple[str, str], str] = field(default_factory=dict)
|
|
67
|
+
scoped: dict[tuple[str, str, str], str] = field(default_factory=dict)
|
|
68
|
+
java_packages: dict[str, str] = field(default_factory=dict)
|
|
69
|
+
|
|
70
|
+
def __len__(self) -> int:
|
|
71
|
+
"""Return the total number of entries."""
|
|
72
|
+
return len(self.types) + len(self.elements) + len(self.scoped)
|
|
73
|
+
|
|
74
|
+
def find(self, namespace: str | None, name: str, is_element: bool) -> str | None:
|
|
75
|
+
"""Return the class name for a schema name, or None.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
namespace: The target namespace of the schema that declares it
|
|
79
|
+
name: The complexType, simpleType or element name
|
|
80
|
+
is_element: Whether the class was generated for an element
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The class name to emit, or None to keep the conventions.
|
|
84
|
+
"""
|
|
85
|
+
table = self.elements if is_element else self.types
|
|
86
|
+
return table.get((namespace or "", name))
|
|
87
|
+
|
|
88
|
+
def find_scoped(self, namespace: str | None, name: str, scope: str) -> str | None:
|
|
89
|
+
"""Return the class name for an element in one enclosing type.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
namespace: The target namespace of the schema that declares it
|
|
93
|
+
name: The element name
|
|
94
|
+
scope: The qualified name of the type whose compound field
|
|
95
|
+
declares it
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The class name to emit, or None to keep the conventions.
|
|
99
|
+
"""
|
|
100
|
+
return self.scoped.get((namespace or "", name, scope))
|
|
101
|
+
|
|
102
|
+
def names(self) -> set[str]:
|
|
103
|
+
"""Return every class name the table can produce."""
|
|
104
|
+
return (
|
|
105
|
+
set(self.types.values())
|
|
106
|
+
| set(self.elements.values())
|
|
107
|
+
| set(self.scoped.values())
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def load(cls, directory: Path) -> "ClassNames":
|
|
112
|
+
"""Load every json file of a directory into one table.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
directory: The directory with one json file per namespace
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The class name table.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
CodegenError: If the directory or one of the files is unusable.
|
|
122
|
+
"""
|
|
123
|
+
if not directory.is_dir():
|
|
124
|
+
raise CodegenError(f"ClassNames directory not found: {directory}")
|
|
125
|
+
|
|
126
|
+
obj = cls()
|
|
127
|
+
for path in sorted(directory.glob("*.json")):
|
|
128
|
+
obj.load_file(path)
|
|
129
|
+
|
|
130
|
+
return obj
|
|
131
|
+
|
|
132
|
+
def load_file(self, path: Path) -> None:
|
|
133
|
+
"""Load one namespace file into the table.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
path: The json file path
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
CodegenError: If the file is not a json object.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
data = json.loads(path.read_text())
|
|
143
|
+
except ValueError as e:
|
|
144
|
+
raise CodegenError(f"ClassNames file is not valid json: {path}") from e
|
|
145
|
+
|
|
146
|
+
if not isinstance(data, dict):
|
|
147
|
+
raise CodegenError(f"ClassNames file is not a json object: {path}")
|
|
148
|
+
|
|
149
|
+
namespace = data.get("namespace") or ""
|
|
150
|
+
java_package = data.get("java_package")
|
|
151
|
+
if java_package:
|
|
152
|
+
self.java_packages[namespace] = java_package
|
|
153
|
+
|
|
154
|
+
self.update(namespace, data.get(TYPES), TYPES, path)
|
|
155
|
+
self.update(namespace, data.get(ELEMENTS), ELEMENTS, path)
|
|
156
|
+
|
|
157
|
+
def update(
|
|
158
|
+
self,
|
|
159
|
+
namespace: str,
|
|
160
|
+
entries: dict | None,
|
|
161
|
+
kind: str,
|
|
162
|
+
path: Path,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Merge one section of one file into the table.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
namespace: The namespace the file is for
|
|
168
|
+
entries: The schema name to class name map, if any
|
|
169
|
+
kind: The section name, for the messages
|
|
170
|
+
path: The json file path, for the messages
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
CodegenError: If the section is not a json object.
|
|
174
|
+
"""
|
|
175
|
+
if entries is None:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if not isinstance(entries, dict):
|
|
179
|
+
raise CodegenError(f"ClassNames {kind} is not a json object: {path}")
|
|
180
|
+
|
|
181
|
+
for name, class_name in entries.items():
|
|
182
|
+
if not isinstance(class_name, str) or not self.is_valid(class_name):
|
|
183
|
+
logger.warning(
|
|
184
|
+
"ClassNames: `%s` is not a valid class name, ignoring %s %s",
|
|
185
|
+
class_name,
|
|
186
|
+
kind,
|
|
187
|
+
name,
|
|
188
|
+
)
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
table: dict[tuple, str]
|
|
192
|
+
key: tuple
|
|
193
|
+
if kind == ELEMENTS and SCOPE_SEPARATOR in name:
|
|
194
|
+
local, _, scope = name.partition(SCOPE_SEPARATOR)
|
|
195
|
+
table = self.scoped
|
|
196
|
+
key = (namespace, local, build_qname(namespace, scope))
|
|
197
|
+
elif kind == ELEMENTS:
|
|
198
|
+
table, key = self.elements, (namespace, name)
|
|
199
|
+
else:
|
|
200
|
+
table, key = self.types, (namespace, name)
|
|
201
|
+
|
|
202
|
+
current = table.get(key)
|
|
203
|
+
if current is not None and current != class_name:
|
|
204
|
+
logger.warning(
|
|
205
|
+
"ClassNames: %s %s is mapped twice, `%s` wins over `%s`",
|
|
206
|
+
kind,
|
|
207
|
+
name,
|
|
208
|
+
class_name,
|
|
209
|
+
current,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
table[key] = class_name
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def is_valid(cls, name: str) -> bool:
|
|
216
|
+
"""Return whether the name can be a python class name."""
|
|
217
|
+
return name.isidentifier() and not iskeyword(name)
|