Skip to content

Commit e13d28b

Browse files
authored
Merge pull request #119 from strictdoc-project/stanislaw/develop
Doors: a yet another example from a user: XHTML tags with their own namespaces
2 parents 03fe053 + d8e2474 commit e13d28b

5 files changed

Lines changed: 155 additions & 18 deletions

File tree

reqif/helpers/lxml.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from copy import deepcopy
23
from itertools import chain
34

@@ -54,15 +55,41 @@ def lxml_stringify_namespaced_children(node, namespace_tag=None) -> str:
5455
nskey = namespace_tag
5556

5657
def _lxml_stringify_reqif_ns_node(node):
57-
assert node is not None
5858
output = ""
5959
node_no_ns_tag = etree.QName(node).localname
60+
61+
# There are ReqIF files where the XHTML-based tags can have their own
62+
# namespace that overrides the document-level namespace. Making the best
63+
# effort to detect this case. Example:
64+
# <THE-VALUE>
65+
# <div xmlns="http://www.w3.org/1999/xhtml">
66+
# &quot;New Header&quot; Template reqFile
67+
# </div>
68+
# </THE-VALUE>
69+
# FIXME: Such tricks can introduce further edge cases. Not clear how to
70+
# implement this properly.
71+
node_has_its_own_ns = False
72+
node_ns_match = re.search("{(.+?)}", node.tag)
73+
if node_ns_match:
74+
node_ns = node_ns_match.group(1)
75+
76+
for nskey_, ns_ in node.nsmap.items():
77+
if ns_ == node_ns and nskey_ is None:
78+
node_has_its_own_ns = True
79+
6080
tag = (
61-
f"{nskey}:{node_no_ns_tag}"
62-
if node.tag[0] == "{" or namespace_tag is not None
63-
else node.tag
81+
node_no_ns_tag
82+
if node_has_its_own_ns
83+
else (
84+
f"{nskey}:{node_no_ns_tag}"
85+
if node.tag[0] == "{" or namespace_tag is not None
86+
else node.tag
87+
)
6488
)
6589
output += f"<{tag}"
90+
if node_has_its_own_ns:
91+
output += f' xmlns="{node.nsmap[None]}"'
92+
6693
for attribute, attribute_value in node.attrib.items():
6794
output += f' {attribute}="{lxml_escape_for_html(attribute_value)}"'
6895
# <object> is surprisingly a tag that must have a closing tag even

reqif/models/reqif_reqif_header.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
from typing import Optional
1+
from typing import Optional, Union
22

33
from reqif.helpers.debug import auto_described
44

55

6+
# Sometimes, the ReqIFs have empty tags. Example:
7+
# <REPOSITORY-ID/>. Creating a custom class to catch this case.
8+
class EmptyTag:
9+
pass
10+
11+
612
@auto_described
713
class ReqIFReqIFHeader: # pylint: disable=too-many-instance-attributes
814
def __init__( # pylint: disable=too-many-arguments
915
self,
1016
identifier: Optional[str] = None,
1117
comment: Optional[str] = None,
1218
creation_time: Optional[str] = None,
13-
repository_id: Optional[str] = None,
19+
repository_id: Union[None, str, EmptyTag] = None,
1420
req_if_tool_id: Optional[str] = None,
1521
req_if_version: Optional[str] = None,
1622
source_tool_id: Optional[str] = None,
@@ -19,7 +25,7 @@ def __init__( # pylint: disable=too-many-arguments
1925
self.identifier: Optional[str] = identifier
2026
self.comment: Optional[str] = comment
2127
self.creation_time: Optional[str] = creation_time
22-
self.repository_id: Optional[str] = repository_id
28+
self.repository_id: Union[None, str, EmptyTag] = repository_id
2329
self.req_if_tool_id: Optional[str] = req_if_tool_id
2430
self.req_if_version: Optional[str] = req_if_version
2531
self.source_tool_id: Optional[str] = source_tool_id

reqif/parsers/header_parser.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Optional
1+
from typing import Union
22

33
from reqif.helpers.lxml import lxml_escape_title
4-
from reqif.models.reqif_reqif_header import ReqIFReqIFHeader
4+
from reqif.models.reqif_reqif_header import EmptyTag, ReqIFReqIFHeader
55

66

77
class ReqIFHeaderParser:
@@ -21,7 +21,7 @@ def parse(xml_header) -> ReqIFReqIFHeader:
2121

2222
comment = None
2323
creation_time = None
24-
repository_id: Optional[str] = None
24+
repository_id: Union[None, str, EmptyTag] = None
2525
req_if_tool_id = None
2626
req_if_version = None
2727
source_tool_id = None
@@ -37,7 +37,10 @@ def parse(xml_header) -> ReqIFReqIFHeader:
3737

3838
xml_repository_id = xml_reqif_header.find("REPOSITORY-ID")
3939
if xml_repository_id is not None:
40-
repository_id = xml_repository_id.text
40+
if xml_repository_id.text is not None:
41+
repository_id = xml_repository_id.text
42+
else:
43+
repository_id = EmptyTag()
4144

4245
xml_req_if_tool_id = xml_reqif_header.find("REQ-IF-TOOL-ID")
4346
if xml_req_if_tool_id is not None:
@@ -84,13 +87,16 @@ def unparse(header: ReqIFReqIFHeader) -> str:
8487
f"{header.creation_time}"
8588
"</CREATION-TIME>\n"
8689
)
87-
if header.repository_id:
88-
output += (
89-
" "
90-
"<REPOSITORY-ID>"
91-
f"{header.repository_id}"
92-
"</REPOSITORY-ID>\n"
93-
)
90+
if header.repository_id is not None:
91+
if isinstance(header.repository_id, str):
92+
output += (
93+
" "
94+
"<REPOSITORY-ID>"
95+
f"{header.repository_id}"
96+
"</REPOSITORY-ID>\n"
97+
)
98+
else:
99+
output += " <REPOSITORY-ID/>\n"
94100
if header.req_if_tool_id:
95101
output += (
96102
" "
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd" xmlns:reqif-common="http://www.prostep.org/reqif" xmlns:rm-reqif="http://www.ibm.com/rm/reqif" xmlns:reqif="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rm="http://www.ibm.com/rm" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd reqif.xsd">
3+
<THE-HEADER>
4+
<REQ-IF-HEADER IDENTIFIER="_c9f16ebe-c3b5-4966-a5f8-b1090d3a6c2d">
5+
<CREATION-TIME>2022-01-01T00:01:01.260Z</CREATION-TIME>
6+
<REPOSITORY-ID/>
7+
<REQ-IF-TOOL-ID>IBM</REQ-IF-TOOL-ID>
8+
<REQ-IF-VERSION>1.0</REQ-IF-VERSION>
9+
<SOURCE-TOOL-ID>IBM</SOURCE-TOOL-ID>
10+
<TITLE>IBM Engineering Requirements</TITLE>
11+
</REQ-IF-HEADER>
12+
</THE-HEADER>
13+
<CORE-CONTENT>
14+
<REQ-IF-CONTENT>
15+
<SPEC-OBJECTS>
16+
<SPEC-OBJECT IDENTIFIER="_f05294de-d4cf-455d-9307-c130376dfd44" LAST-CHANGE="2022-01-01T00:01:01.260Z">
17+
<TYPE>
18+
<SPEC-OBJECT-TYPE-REF>_ee420d2b-599c-49ea-a298-6a732ece21eb</SPEC-OBJECT-TYPE-REF>
19+
</TYPE>
20+
<VALUES>
21+
<ATTRIBUTE-VALUE-XHTML>
22+
<DEFINITION>
23+
<ATTRIBUTE-DEFINITION-XHTML-REF>_3ca983d9-5ec5-4752-898f-37ab52beafd0</ATTRIBUTE-DEFINITION-XHTML-REF>
24+
</DEFINITION>
25+
<THE-VALUE>
26+
<div xmlns="http://www.w3.org/1999/xhtml">&lt;FOOOOO&gt; BAR</div>
27+
</THE-VALUE>
28+
</ATTRIBUTE-VALUE-XHTML>
29+
<ATTRIBUTE-VALUE-XHTML>
30+
<DEFINITION>
31+
<ATTRIBUTE-DEFINITION-XHTML-REF>_1f20e97d-8b0f-4f64-a093-deedc195e91a</ATTRIBUTE-DEFINITION-XHTML-REF>
32+
</DEFINITION>
33+
<THE-VALUE>
34+
<div xmlns="http://www.w3.org/1999/xhtml">&quot;New Header&quot; Template reqFile</div>
35+
</THE-VALUE>
36+
</ATTRIBUTE-VALUE-XHTML>
37+
<ATTRIBUTE-VALUE-DATE THE-VALUE="2022-01-01T00:01:01.260Z">
38+
<DEFINITION>
39+
<ATTRIBUTE-DEFINITION-DATE-REF>_dc18f331-d466-4de2-8182-0ef4ced9e174</ATTRIBUTE-DEFINITION-DATE-REF>
40+
</DEFINITION>
41+
</ATTRIBUTE-VALUE-DATE>
42+
<ATTRIBUTE-VALUE-STRING THE-VALUE="XSX_6qwe82_363">
43+
<DEFINITION>
44+
<ATTRIBUTE-DEFINITION-STRING-REF>_d2325dd8-82f3-43d6-b50b-6eb92820fd55</ATTRIBUTE-DEFINITION-STRING-REF>
45+
</DEFINITION>
46+
</ATTRIBUTE-VALUE-STRING>
47+
<ATTRIBUTE-VALUE-BOOLEAN THE-VALUE="false">
48+
<DEFINITION>
49+
<ATTRIBUTE-DEFINITION-BOOLEAN-REF>_d09cedc9-c249-4ff3-affb-e863dcb732b9</ATTRIBUTE-DEFINITION-BOOLEAN-REF>
50+
</DEFINITION>
51+
</ATTRIBUTE-VALUE-BOOLEAN>
52+
<ATTRIBUTE-VALUE-XHTML>
53+
<DEFINITION>
54+
<ATTRIBUTE-DEFINITION-XHTML-REF>_05ee3f1d-6c0c-4e86-8bca-67cc5355db9c</ATTRIBUTE-DEFINITION-XHTML-REF>
55+
</DEFINITION>
56+
<THE-VALUE>
57+
<div xmlns="http://www.w3.org/1999/xhtml"/>
58+
</THE-VALUE>
59+
</ATTRIBUTE-VALUE-XHTML>
60+
<ATTRIBUTE-VALUE-ENUMERATION>
61+
<VALUES>
62+
<ENUM-VALUE-REF>_91651ec0-8832-4553-a201-cad184aa6fdb</ENUM-VALUE-REF>
63+
</VALUES>
64+
<DEFINITION>
65+
<ATTRIBUTE-DEFINITION-ENUMERATION-REF>_03fdb162-e81a-4724-809e-2b660994e18f</ATTRIBUTE-DEFINITION-ENUMERATION-REF>
66+
</DEFINITION>
67+
</ATTRIBUTE-VALUE-ENUMERATION>
68+
<ATTRIBUTE-VALUE-DATE THE-VALUE="2022-01-01T00:01:01.260Z">
69+
<DEFINITION>
70+
<ATTRIBUTE-DEFINITION-DATE-REF>_136696e2-ada5-407a-b65b-e271a96ab983</ATTRIBUTE-DEFINITION-DATE-REF>
71+
</DEFINITION>
72+
</ATTRIBUTE-VALUE-DATE>
73+
<ATTRIBUTE-VALUE-STRING THE-VALUE="0848764">
74+
<DEFINITION>
75+
<ATTRIBUTE-DEFINITION-STRING-REF>_a450fa62-c5ac-43fc-9d08-c90432810d15</ATTRIBUTE-DEFINITION-STRING-REF>
76+
</DEFINITION>
77+
</ATTRIBUTE-VALUE-STRING>
78+
<ATTRIBUTE-VALUE-STRING THE-VALUE="SE6DAW">
79+
<DEFINITION>
80+
<ATTRIBUTE-DEFINITION-STRING-REF>_977a9dd7-8efb-40dd-8456-6e1b548a27c1</ATTRIBUTE-DEFINITION-STRING-REF>
81+
</DEFINITION>
82+
</ATTRIBUTE-VALUE-STRING>
83+
<ATTRIBUTE-VALUE-STRING THE-VALUE="VZ6KYN">
84+
<DEFINITION>
85+
<ATTRIBUTE-DEFINITION-STRING-REF>_318a9d27-fdd6-44ea-8117-5ecc5b6b422a</ATTRIBUTE-DEFINITION-STRING-REF>
86+
</DEFINITION>
87+
</ATTRIBUTE-VALUE-STRING>
88+
</VALUES>
89+
</SPEC-OBJECT>
90+
</SPEC-OBJECTS>
91+
<SPEC-RELATION-GROUPS>
92+
</SPEC-RELATION-GROUPS>
93+
</REQ-IF-CONTENT>
94+
</CORE-CONTENT>
95+
</REQ-IF>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RUN: mkdir -p %S/output
2+
RUN: %reqif passthrough %S/sample.reqif %S/output/sample.reqif
3+
RUN: diff %S/sample.reqif %S/output/sample.reqif

0 commit comments

Comments
 (0)