maxml 1.0.2__py3-none-any.whl → 1.0.3__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.
- maxml/__init__.py +2 -0
- maxml/element/__init__.py +65 -5
- maxml/enumerations/__init__.py +25 -0
- maxml/exceptions/__init__.py +2 -0
- maxml/version.txt +1 -1
- {maxml-1.0.2.dist-info → maxml-1.0.3.dist-info}/METADATA +1 -1
- maxml-1.0.3.dist-info/RECORD +12 -0
- maxml-1.0.2.dist-info/RECORD +0 -10
- {maxml-1.0.2.dist-info → maxml-1.0.3.dist-info}/WHEEL +0 -0
- {maxml-1.0.2.dist-info → maxml-1.0.3.dist-info}/top_level.txt +0 -0
- {maxml-1.0.2.dist-info → maxml-1.0.3.dist-info}/zip-safe +0 -0
maxml/__init__.py
CHANGED
maxml/element/__init__.py
CHANGED
|
@@ -2,9 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from maxml.namespace import Namespace
|
|
4
4
|
from maxml.logging import logger
|
|
5
|
+
from maxml.enumerations import (
|
|
6
|
+
Context,
|
|
7
|
+
Escape,
|
|
8
|
+
)
|
|
9
|
+
from maxml.exceptions import MaXMLError
|
|
5
10
|
|
|
6
11
|
from classicist import hybridmethod
|
|
7
12
|
|
|
13
|
+
import re
|
|
8
14
|
|
|
9
15
|
logger = logger.getChild(__name__)
|
|
10
16
|
|
|
@@ -77,9 +83,9 @@ class Element(object):
|
|
|
77
83
|
)
|
|
78
84
|
break
|
|
79
85
|
else:
|
|
80
|
-
raise
|
|
81
|
-
"The '%s' namespace has already been registered with a different URI!"
|
|
82
|
-
% (prefix)
|
|
86
|
+
raise MaXMLError(
|
|
87
|
+
"The '%s' namespace, %s, has already been registered with a different URI: %s!"
|
|
88
|
+
% (prefix, uri, namespace.uri)
|
|
83
89
|
)
|
|
84
90
|
else:
|
|
85
91
|
if namespace := Namespace(prefix=prefix, uri=uri):
|
|
@@ -170,7 +176,7 @@ class Element(object):
|
|
|
170
176
|
|
|
171
177
|
break
|
|
172
178
|
else:
|
|
173
|
-
raise
|
|
179
|
+
raise MaXMLError(
|
|
174
180
|
f"No namespace has been registered for the '{prefix}' prefix associated with the '{name}' element!"
|
|
175
181
|
)
|
|
176
182
|
|
|
@@ -557,6 +563,7 @@ class Element(object):
|
|
|
557
563
|
pretty: bool = False,
|
|
558
564
|
indent: str | int = None,
|
|
559
565
|
encoding: str = None,
|
|
566
|
+
escape: Escape = Escape.All,
|
|
560
567
|
**kwargs,
|
|
561
568
|
) -> str | bytes:
|
|
562
569
|
"""Supports serializing the current Element tree to a string or to a bytes array
|
|
@@ -565,6 +572,11 @@ class Element(object):
|
|
|
565
572
|
if not isinstance(pretty, bool):
|
|
566
573
|
raise TypeError("The 'pretty' argument must have a boolean value!")
|
|
567
574
|
|
|
575
|
+
if not isinstance(escape, Escape):
|
|
576
|
+
raise TypeError(
|
|
577
|
+
"The 'escape' argument must reference an Escape enumeration option!"
|
|
578
|
+
)
|
|
579
|
+
|
|
568
580
|
if indent is None:
|
|
569
581
|
indent = 2
|
|
570
582
|
|
|
@@ -585,10 +597,54 @@ class Element(object):
|
|
|
585
597
|
elif not isinstance(encoding, str):
|
|
586
598
|
raise TypeError("The 'encoding' argument must have a string value!")
|
|
587
599
|
|
|
600
|
+
def escaper(value: str, context: Context, escape: Escape) -> str:
|
|
601
|
+
"""Helper method to escape special characters in XML attributes and text."""
|
|
602
|
+
|
|
603
|
+
if isinstance(value, str):
|
|
604
|
+
pass
|
|
605
|
+
elif hasattr(value, "__str__"):
|
|
606
|
+
value = str(value)
|
|
607
|
+
else:
|
|
608
|
+
raise TypeError(
|
|
609
|
+
"The 'value' argument must have a string value or a value that can be cast to a string!"
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
replacements: dict[str, str] = {
|
|
613
|
+
"&": "&",
|
|
614
|
+
"<": "<",
|
|
615
|
+
">": ">",
|
|
616
|
+
'"': """,
|
|
617
|
+
"'": "'",
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if context is Context.Attribute:
|
|
621
|
+
# Required replacements for element attribute values
|
|
622
|
+
required: list[str] = ["&", "<", '"']
|
|
623
|
+
elif context is Context.Text:
|
|
624
|
+
# Required replacements for element text
|
|
625
|
+
required: list[str] = ["&", "<"]
|
|
626
|
+
else:
|
|
627
|
+
# Require all replacements for other contexts
|
|
628
|
+
required: list[str] = [key for key in replacements]
|
|
629
|
+
|
|
630
|
+
for search, replacement in replacements.items():
|
|
631
|
+
if (escape is Escape.All) or (search in required):
|
|
632
|
+
if search == "&":
|
|
633
|
+
# Ensure only standalone "&" characters are replaced, ignoring
|
|
634
|
+
# any that are part of an XML special character escape sequence
|
|
635
|
+
value = re.sub(
|
|
636
|
+
r"&(?!(([a-z]+|#x?[0-9a-fA-F]+);))", replacement, value
|
|
637
|
+
)
|
|
638
|
+
else:
|
|
639
|
+
value = value.replace(search, replacement)
|
|
640
|
+
|
|
641
|
+
return value
|
|
642
|
+
|
|
588
643
|
def stringify(
|
|
589
644
|
element: Element,
|
|
590
645
|
depth: int,
|
|
591
646
|
pretty: bool = False,
|
|
647
|
+
escape: Escape = Escape.All,
|
|
592
648
|
indent: str = None,
|
|
593
649
|
**kwargs,
|
|
594
650
|
) -> str:
|
|
@@ -624,6 +680,8 @@ class Element(object):
|
|
|
624
680
|
string += f"\n{indent * (depth + 2)}"
|
|
625
681
|
newline = True
|
|
626
682
|
|
|
683
|
+
value = escaper(value, context=Context.Attribute, escape=escape)
|
|
684
|
+
|
|
627
685
|
string += f' {key}="{value}"'
|
|
628
686
|
|
|
629
687
|
# Add any non-promoted namespaces (those which can follow any attributes)
|
|
@@ -656,7 +714,7 @@ class Element(object):
|
|
|
656
714
|
|
|
657
715
|
# Include the element's text content, if any
|
|
658
716
|
if element.text and (element.mixed or not element.children):
|
|
659
|
-
string += element.text
|
|
717
|
+
string += escaper(element.text, context=Context.Text, escape=escape)
|
|
660
718
|
|
|
661
719
|
# Include the element's children, if any
|
|
662
720
|
if element.children and (element.mixed or not element.text):
|
|
@@ -669,6 +727,7 @@ class Element(object):
|
|
|
669
727
|
depth=(depth + 1),
|
|
670
728
|
pretty=pretty,
|
|
671
729
|
indent=indent,
|
|
730
|
+
escape=escape,
|
|
672
731
|
**kwargs,
|
|
673
732
|
)
|
|
674
733
|
|
|
@@ -684,6 +743,7 @@ class Element(object):
|
|
|
684
743
|
depth=0,
|
|
685
744
|
pretty=pretty,
|
|
686
745
|
indent=indent,
|
|
746
|
+
escape=escape,
|
|
687
747
|
**kwargs,
|
|
688
748
|
)
|
|
689
749
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from enumerific import Enumeration, auto
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Context(Enumeration):
|
|
5
|
+
"""List of XML contexts, to denote the XML context currently being processed."""
|
|
6
|
+
|
|
7
|
+
Unknown = auto()
|
|
8
|
+
Root = auto(description="Document root node")
|
|
9
|
+
Prolog = auto(description="Document declaration (prolog)")
|
|
10
|
+
Instruction = auto(description="Document processing instruction")
|
|
11
|
+
DocType = auto(description="Document type")
|
|
12
|
+
Element = auto(description="Element comprising opening/closing or self-closing tag")
|
|
13
|
+
TagOpen = auto(description="Opening tag of an element (maybe self-closing)")
|
|
14
|
+
TagClose = auto(description="Closing tag of an element")
|
|
15
|
+
Attribute = auto(description="Attribute held by an element's tag")
|
|
16
|
+
Text = auto(description="Text held between an element's opening and closing tags")
|
|
17
|
+
Data = auto(description="Data held in a CDATA section")
|
|
18
|
+
Comment = auto(description="Comment held in a comment section")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Escape(Enumeration):
|
|
22
|
+
"""List of XML special character escape modes."""
|
|
23
|
+
|
|
24
|
+
All = auto(description="Escape all XML special characters")
|
|
25
|
+
Required = auto(description="Escpae only required XML special characters")
|
maxml/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.3
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
maxml/__init__.py,sha256=9wLSTIlZTXinpBr9ABp_PUWhfxubtNM89Iac2VCNet8,150
|
|
2
|
+
maxml/version.txt,sha256=INLLCW0atBpBQCRtEvB79rjLdD_UgSK3JTLAPUTFwUo,5
|
|
3
|
+
maxml/element/__init__.py,sha256=39X5ncEAT7kdFa9jz-1MVG02O5HaoxcHuFuNUNYswXY,26198
|
|
4
|
+
maxml/enumerations/__init__.py,sha256=N15XKtEZp2cFwqq_1GXyODqoOE--z9WukZGt_rFur2E,1153
|
|
5
|
+
maxml/exceptions/__init__.py,sha256=qMI3bIXJe6zDXzRnxdW3v6wbxSYfd1oK-9lnxvZzzjQ,38
|
|
6
|
+
maxml/logging/__init__.py,sha256=AMpvZEQH9GIBe8609sMwb2T64rovpkRRCc9rs2kHy8g,52
|
|
7
|
+
maxml/namespace/__init__.py,sha256=iZqHWuGi09KtMTr7wHiyTQLXB2TFljUir8wZGLi2b60,3882
|
|
8
|
+
maxml-1.0.3.dist-info/METADATA,sha256=ekfPSZaa565dou1yy4wwqC4fWu264JxFlwqYra-AXpo,16193
|
|
9
|
+
maxml-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
maxml-1.0.3.dist-info/top_level.txt,sha256=ZFK3SmCc04Dzhl9QkO_hVBAEiHwCNrwn8X_PINWE9C4,6
|
|
11
|
+
maxml-1.0.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
12
|
+
maxml-1.0.3.dist-info/RECORD,,
|
maxml-1.0.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
maxml/__init__.py,sha256=QuUB-oFrPKRZ4XzPuk4Mt8r35EMx652PALyAeWvQXIo,72
|
|
2
|
-
maxml/version.txt,sha256=v-wuNFg62n5q8stzmT-3Wj9xR6bJQ-X_X1xClPxXe5A,5
|
|
3
|
-
maxml/element/__init__.py,sha256=8N6ePtw7nOX6VVQ0eG5mmDJkBXo8S1qwVF3k9X2OmLo,23843
|
|
4
|
-
maxml/logging/__init__.py,sha256=AMpvZEQH9GIBe8609sMwb2T64rovpkRRCc9rs2kHy8g,52
|
|
5
|
-
maxml/namespace/__init__.py,sha256=iZqHWuGi09KtMTr7wHiyTQLXB2TFljUir8wZGLi2b60,3882
|
|
6
|
-
maxml-1.0.2.dist-info/METADATA,sha256=J9F0r0aKB7prWv2tALoHhPRtFYCUXm096UOdoRQW5Fc,16193
|
|
7
|
-
maxml-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
maxml-1.0.2.dist-info/top_level.txt,sha256=ZFK3SmCc04Dzhl9QkO_hVBAEiHwCNrwn8X_PINWE9C4,6
|
|
9
|
-
maxml-1.0.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
10
|
-
maxml-1.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|