Skip to content

Commit

Permalink
Fix min occurs for choice content types
Browse files Browse the repository at this point in the history
  • Loading branch information
tefra committed Sep 5, 2022
1 parent 86434c4 commit fb0f3bc
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 28 deletions.
19 changes: 19 additions & 0 deletions tests/codegen/mappers/test_dtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ def test_build_content_type_seq(self):
target = ClassFactory.create()
DtdMapper.build_content(target, content, nillable=True)
self.assertEqual(2, len(target.attrs))
self.assertTrue(target.attrs[0].restrictions.nillable)
self.assertTrue(target.attrs[1].restrictions.nillable)

def test_build_content_type_or(self):
content = DtdContentFactory.create(
Expand All @@ -236,6 +238,20 @@ def test_build_content_type_or(self):
self.assertEqual(2, len(target.attrs))
for attr in target.attrs:
self.assertEqual(str(id(content)), attr.restrictions.choice)
self.assertEqual(0, attr.restrictions.min_occurs)

def test_build_content_type_or_nested(self):
content = DtdContentFactory.create(
type=DtdContentType.OR,
left=DtdContentFactory.create(type=DtdContentType.ELEMENT),
right=DtdContentFactory.create(type=DtdContentType.ELEMENT),
)
target = ClassFactory.create()
DtdMapper.build_content(target, content, min_occurs=1, choice="abc")
self.assertEqual(2, len(target.attrs))
for attr in target.attrs:
self.assertEqual("abc", attr.restrictions.choice)
self.assertEqual(1, attr.restrictions.min_occurs)

def test_build_content_type_pcdata(self):
content = DtdContentFactory.create(
Expand Down Expand Up @@ -285,6 +301,9 @@ def test_build_restrictions(self):
self.assertEqual(sys.maxsize, result.max_occurs)
self.assertTrue(result.nillable)

result = DtdMapper.build_restrictions(DtdContentOccur.PLUS, min_occurs=0)
self.assertEqual(0, result.min_occurs)

def test_build_element(self):
target = ClassFactory.create()
restrictions = Restrictions()
Expand Down
52 changes: 38 additions & 14 deletions tests/codegen/parsers/test_dtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_parse_requires_lxml(self, mock_lxml):

def test_build_element(self):
dtd = self.parse("dtd/complete_example.dtd")
self.assertEqual(6, len(dtd.elements))
self.assertEqual(8, len(dtd.elements))

element = dtd.elements[1]
self.assertEqual(4, len(element.attributes))
Expand Down Expand Up @@ -57,35 +57,59 @@ def test_build_content(self):

actual = asdict(post.content)
expected = {
"name": None,
"occur": DtdContentOccur.ONCE,
"type": DtdContentType.SEQ,
"left": {
"left": None,
"name": "Title",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"right": {
"left": {
"left": None,
"name": "Body",
"name": "Origin",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"name": None,
"occur": DtdContentOccur.ONCE,
"type": DtdContentType.SEQ,
"right": {
"left": None,
"name": "Tags",
"name": "Source",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"type": DtdContentType.OR,
},
"name": None,
"occur": DtdContentOccur.ONCE,
"right": {
"left": {
"left": None,
"name": "Title",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"name": None,
"occur": DtdContentOccur.ONCE,
"right": {
"left": {
"left": None,
"name": "Body",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"name": None,
"occur": DtdContentOccur.ONCE,
"right": {
"left": None,
"name": "Tags",
"occur": DtdContentOccur.ONCE,
"right": None,
"type": DtdContentType.ELEMENT,
},
"type": DtdContentType.SEQ,
},
"type": DtdContentType.SEQ,
},
"type": DtdContentType.SEQ,
}
self.assertEqual(expected, actual)

Expand Down
5 changes: 3 additions & 2 deletions tests/fixtures/dtd/complete_example.dtd
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<!ELEMENT Blog (Post+)>
<!ELEMENT Post (Title,Body,Tags)>
<!ELEMENT Post ((Origin|Source),Title,Body,Tags)>
<!ELEMENT Origin (#PCDATA)>
<!ELEMENT Source (#PCDATA)>
<!ELEMENT Title (#PCDATA)>
<!ELEMENT Body (#PCDATA)>
<!ELEMENT Tags (Tag*)>
<!ELEMENT Tag (#PCDATA)>

<!ATTLIST Post status (draft|published) 'draft'>
<!ATTLIST Post author CDATA #REQUIRED>
<!ATTLIST Post created_at CDATA #REQUIRED>
Expand Down
14 changes: 14 additions & 0 deletions tests/fixtures/dtd/models/complete_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ class Post:
"required": True,
}
)
origin: Optional[str] = field(
default=None,
metadata={
"name": "Origin",
"type": "Element",
}
)
source: Optional[str] = field(
default=None,
metadata={
"name": "Source",
"type": "Element",
}
)
title: Optional[str] = field(
default=None,
metadata={
Expand Down
32 changes: 20 additions & 12 deletions xsdata/codegen/mappers/dtd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
from typing import Optional
Expand Down Expand Up @@ -115,9 +116,11 @@ def build_content(cls, target: Class, content: DtdContent, **kwargs: Any):
restrictions = cls.build_restrictions(content.occur, **kwargs)
cls.build_element(target, content.name, restrictions)
elif content_type == DtdContentType.SEQ:
cls.build_content_tree(target, content)
cls.build_content_tree(target, content, **kwargs)
elif content_type == DtdContentType.OR:
cls.build_content_tree(target, content, choice=str(id(content)))
params = {"min_occurs": 0, "choice": str(id(content))}
params.update(kwargs)
cls.build_content_tree(target, content, **params)

@classmethod
def build_content_tree(cls, target: Class, content: DtdContent, **kwargs: Any):
Expand All @@ -129,21 +132,26 @@ def build_content_tree(cls, target: Class, content: DtdContent, **kwargs: Any):

@classmethod
def build_restrictions(cls, occur: DtdContentOccur, **kwargs: Any) -> Restrictions:
restrictions = Restrictions(**kwargs)
if occur == DtdContentOccur.ONCE:
restrictions.min_occurs = 1
restrictions.max_occurs = 1
min_occurs = 1
max_occurs = 1
elif occur == DtdContentOccur.OPT:
restrictions.min_occurs = 0
restrictions.max_occurs = 1
min_occurs = 0
max_occurs = 1
elif occur == DtdContentOccur.MULT:
restrictions.min_occurs = 0
restrictions.max_occurs = sys.maxsize
min_occurs = 0
max_occurs = sys.maxsize
else: # occur == DtdContentOccur.PLUS:
restrictions.min_occurs = 1
restrictions.max_occurs = sys.maxsize
min_occurs = 1
max_occurs = sys.maxsize

return restrictions
params: Dict[str, Any] = {
"min_occurs": min_occurs,
"max_occurs": max_occurs,
}
params.update(kwargs)

return Restrictions(**params)

@classmethod
def build_element(cls, target: Class, name: str, restrictions: Restrictions):
Expand Down

0 comments on commit fb0f3bc

Please sign in to comment.