Skip to content

Commit 59a8f87

Browse files
Merge pull request mvantellingen#1125 from da1910/features/strip_whitespace_builtin_types
Implement correct xsd:whitespace behaviour
2 parents 22a79df + 32e9379 commit 59a8f87

File tree

2 files changed

+93
-2
lines changed

2 files changed

+93
-2
lines changed

src/zeep/xsd/types/builtins.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ def _wrapper(self, value):
3434
return _wrapper
3535

3636

37+
def treat_whitespace(behaviour):
38+
def _treat_whitespace(func):
39+
def _wrapper(self, value):
40+
assert behaviour in ["replace", "collapse", "preserve"]
41+
if behaviour == "replace":
42+
return func(self, re.sub(r'[\n\r\t]', ' ', value))
43+
elif behaviour == "collapse":
44+
return func(self, re.sub(r'[\n\r\t ]', ' ', value).strip())
45+
return func(self, value)
46+
return _wrapper
47+
return _treat_whitespace
48+
49+
3750
##
3851
# Primitive types
3952
class String(BuiltinType):
@@ -58,6 +71,7 @@ class Boolean(BuiltinType):
5871
def xmlvalue(self, value):
5972
return "true" if value and value not in ("false", "0") else "false"
6073

74+
@treat_whitespace('collapse')
6175
def pythonvalue(self, value):
6276
"""Return True if the 'true' or '1'. 'false' and '0' are legal false
6377
values, but we consider everything not true as false.
@@ -74,6 +88,7 @@ class Decimal(BuiltinType):
7488
def xmlvalue(self, value):
7589
return str(value)
7690

91+
@treat_whitespace('collapse')
7792
def pythonvalue(self, value):
7893
return _Decimal(value)
7994

@@ -85,6 +100,7 @@ class Float(BuiltinType):
85100
def xmlvalue(self, value):
86101
return str(value).upper()
87102

103+
@treat_whitespace('collapse')
88104
def pythonvalue(self, value):
89105
return float(value)
90106

@@ -97,6 +113,7 @@ class Double(BuiltinType):
97113
def xmlvalue(self, value):
98114
return str(value)
99115

116+
@treat_whitespace('collapse')
100117
def pythonvalue(self, value):
101118
return float(value)
102119

@@ -109,6 +126,7 @@ class Duration(BuiltinType):
109126
def xmlvalue(self, value):
110127
return isodate.duration_isoformat(value)
111128

129+
@treat_whitespace('collapse')
112130
def pythonvalue(self, value):
113131
if value.startswith("PT-"):
114132
value = value.replace("PT-", "PT")
@@ -144,6 +162,7 @@ def xmlvalue(self, value):
144162
return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S.%f%Z")
145163
return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S%Z")
146164

165+
@treat_whitespace('collapse')
147166
def pythonvalue(self, value):
148167

149168
# Determine based on the length of the value if it only contains a date
@@ -166,6 +185,7 @@ def xmlvalue(self, value):
166185
return isodate.isostrf.strftime(value, "%H:%M:%S.%f%Z")
167186
return isodate.isostrf.strftime(value, "%H:%M:%S%Z")
168187

188+
@treat_whitespace('collapse')
169189
def pythonvalue(self, value):
170190
return isodate.parse_time(value)
171191

@@ -180,6 +200,7 @@ def xmlvalue(self, value):
180200
return value
181201
return isodate.isostrf.strftime(value, "%Y-%m-%d")
182202

203+
@treat_whitespace('collapse')
183204
def pythonvalue(self, value):
184205
return isodate.parse_date(value)
185206

@@ -203,6 +224,7 @@ def xmlvalue(self, value):
203224
year, month, tzinfo = value
204225
return "%04d-%02d%s" % (year, month, _unparse_timezone(tzinfo))
205226

227+
@treat_whitespace('collapse')
206228
def pythonvalue(self, value):
207229
match = self._pattern.match(value)
208230
if not match:
@@ -231,6 +253,7 @@ def xmlvalue(self, value):
231253
year, tzinfo = value
232254
return "%04d%s" % (year, _unparse_timezone(tzinfo))
233255

256+
@treat_whitespace('collapse')
234257
def pythonvalue(self, value):
235258
match = self._pattern.match(value)
236259
if not match:
@@ -258,6 +281,7 @@ def xmlvalue(self, value):
258281
month, day, tzinfo = value
259282
return "--%02d-%02d%s" % (month, day, _unparse_timezone(tzinfo))
260283

284+
@treat_whitespace('collapse')
261285
def pythonvalue(self, value):
262286
match = self._pattern.match(value)
263287
if not match:
@@ -288,6 +312,7 @@ def xmlvalue(self, value):
288312
day, tzinfo = value
289313
return "---%02d%s" % (day, _unparse_timezone(tzinfo))
290314

315+
@treat_whitespace('collapse')
291316
def pythonvalue(self, value):
292317
match = self._pattern.match(value)
293318
if not match:
@@ -312,6 +337,7 @@ def xmlvalue(self, value):
312337
month, tzinfo = value
313338
return "--%d%s" % (month, _unparse_timezone(tzinfo))
314339

340+
@treat_whitespace('collapse')
315341
def pythonvalue(self, value):
316342
match = self._pattern.match(value)
317343
if not match:
@@ -352,6 +378,7 @@ class AnyURI(BuiltinType):
352378
def xmlvalue(self, value):
353379
return value
354380

381+
@treat_whitespace('collapse')
355382
def pythonvalue(self, value):
356383
return value
357384

@@ -364,6 +391,7 @@ class QName(BuiltinType):
364391
def xmlvalue(self, value):
365392
return value
366393

394+
@treat_whitespace('collapse')
367395
def pythonvalue(self, value):
368396
return value
369397

@@ -380,10 +408,18 @@ class Notation(BuiltinType):
380408
class NormalizedString(String):
381409
_default_qname = xsd_ns("normalizedString")
382410

411+
@treat_whitespace('replace')
412+
def pythonvalue(self, value):
413+
return value
414+
383415

384416
class Token(NormalizedString):
385417
_default_qname = xsd_ns("token")
386418

419+
@treat_whitespace('collapse')
420+
def pythonvalue(self, value):
421+
return value
422+
387423

388424
class Language(Token):
389425
_default_qname = xsd_ns("language")

tests/test_xsd_builtins.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,42 @@ def test_pythonvalue(self):
1919
result = instance.pythonvalue("foobar")
2020
assert result == "foobar"
2121

22+
result = instance.pythonvalue(" foo\tbar\r\n ")
23+
assert result == " foo\tbar\r\n "
24+
25+
26+
class TestNormalizedString:
27+
def test_xmlvalue(self):
28+
instance = builtins.NormalizedString()
29+
result = instance.xmlvalue("foobar")
30+
assert result == "foobar"
31+
32+
def test_pythonvalue(self):
33+
instance = builtins.NormalizedString()
34+
result = instance.pythonvalue("foobar")
35+
assert result == "foobar"
36+
37+
result = instance.pythonvalue("fo\tob\rar\n")
38+
assert result == "fo ob ar "
39+
40+
41+
class TestToken:
42+
def test_xmlvalue(self):
43+
instance = builtins.Token()
44+
result = instance.xmlvalue("foobar")
45+
assert result == "foobar"
46+
47+
def test_pythonvalue(self):
48+
instance = builtins.Token()
49+
result = instance.pythonvalue("foobar")
50+
assert result == "foobar"
51+
52+
result = instance.pythonvalue("fo\tob\rar")
53+
assert result == "fo ob ar"
54+
55+
result = instance.pythonvalue(" foobar ")
56+
assert result == "foobar"
57+
2258

2359
class TestBoolean:
2460
def test_xmlvalue(self):
@@ -36,6 +72,7 @@ def test_pythonvalue(self):
3672
assert instance.pythonvalue("true") is True
3773
assert instance.pythonvalue("0") is False
3874
assert instance.pythonvalue("false") is False
75+
assert instance.pythonvalue("\t \r\nfalse ") is False
3976

4077

4178
class TestDecimal:
@@ -53,6 +90,7 @@ def test_pythonvalue(self):
5390
assert instance.pythonvalue("10.001") == D("10.001")
5491
assert instance.pythonvalue("+10.001") == D("10.001")
5592
assert instance.pythonvalue("-10.001") == D("-10.001")
93+
assert instance.pythonvalue(" \r\n 10 \t") == D("10")
5694

5795

5896
class TestFloat:
@@ -74,6 +112,7 @@ def test_pythonvalue(self):
74112
assert instance.pythonvalue("-0") == float(0)
75113
assert instance.pythonvalue("0") == float(0)
76114
assert instance.pythonvalue("INF") == float("inf")
115+
assert instance.pythonvalue("\t \r12.78e-2\n ") == float("0.1278")
77116

78117

79118
class TestDouble:
@@ -89,6 +128,7 @@ def test_pythonvalue(self):
89128
assert instance.pythonvalue("12") == float(12)
90129
assert instance.pythonvalue("-0") == float(0)
91130
assert instance.pythonvalue("0") == float(0)
131+
assert instance.pythonvalue(" \r\n0 \t") == float(0)
92132

93133

94134
class TestDuration:
@@ -103,6 +143,10 @@ def test_pythonvalue(self):
103143
value = "P0Y1347M0D"
104144
assert instance.pythonvalue(value) == expected
105145

146+
expected = isodate.parse_duration("P0Y1347M0D")
147+
value = "\r \nP0Y1347M0D\t "
148+
assert instance.pythonvalue(value) == expected
149+
106150

107151
class TestDateTime:
108152
def test_xmlvalue(self):
@@ -137,6 +181,9 @@ def test_pythonvalue(self):
137181
value = datetime.datetime(2016, 3, 4, 0, 0, 0)
138182
assert instance.pythonvalue("2016-03-04") == value
139183

184+
value = datetime.datetime(2016, 3, 4, 0, 0, 0)
185+
assert instance.pythonvalue(" \r\n\t2016-03-04 ") == value
186+
140187
def test_pythonvalue_invalid(self):
141188
instance = builtins.DateTime()
142189
with pytest.raises(ValueError):
@@ -161,6 +208,9 @@ def test_pythonvalue(self):
161208
value = isodate.parse_time("21:14:42.120+0200")
162209
assert instance.pythonvalue("21:14:42.120+0200") == value
163210

211+
value = datetime.time(21, 14, 42)
212+
assert instance.pythonvalue("\t\r\n 21:14:42 ") == value
213+
164214
def test_pythonvalue_invalid(self):
165215
instance = builtins.Time()
166216
with pytest.raises(ValueError):
@@ -181,6 +231,7 @@ def test_pythonvalue(self):
181231
assert instance.pythonvalue("2001-10-26+02:00") == datetime.date(2001, 10, 26)
182232
assert instance.pythonvalue("2001-10-26Z") == datetime.date(2001, 10, 26)
183233
assert instance.pythonvalue("2001-10-26+00:00") == datetime.date(2001, 10, 26)
234+
assert instance.pythonvalue("\r\n\t 2016-03-04 ") == datetime.date(2016, 3, 4)
184235

185236
def test_pythonvalue_invalid(self):
186237
instance = builtins.Date()
@@ -218,8 +269,8 @@ def test_pythonvalue(self):
218269
class TestgYear:
219270
def test_xmlvalue(self):
220271
instance = builtins.gYear()
221-
instance.xmlvalue((2001, None)) == "2001"
222-
instance.xmlvalue((2001, pytz.utc)) == "2001Z"
272+
assert instance.xmlvalue((2001, None)) == "2001"
273+
assert instance.xmlvalue((2001, pytz.utc)) == "2001Z"
223274

224275
def test_pythonvalue(self):
225276
instance = builtins.gYear()
@@ -229,6 +280,7 @@ def test_pythonvalue(self):
229280
assert instance.pythonvalue("2001+00:00") == (2001, pytz.utc)
230281
assert instance.pythonvalue("-2001") == (-2001, None)
231282
assert instance.pythonvalue("-20000") == (-20000, None)
283+
assert instance.pythonvalue(" \t2001+02:00\r\n ") == (2001, pytz.FixedOffset(120))
232284

233285
with pytest.raises(builtins.ParseError):
234286
assert instance.pythonvalue("99")
@@ -247,6 +299,7 @@ def test_pythonvalue(self):
247299
assert instance.pythonvalue("--11-01-04:00") == (11, 1, pytz.FixedOffset(-240))
248300
assert instance.pythonvalue("--11-15") == (11, 15, None)
249301
assert instance.pythonvalue("--02-29") == (2, 29, None)
302+
assert instance.pythonvalue("\t\r\n --05-01 ") == (5, 1, None)
250303

251304
with pytest.raises(builtins.ParseError):
252305
assert instance.pythonvalue("99")
@@ -265,6 +318,7 @@ def test_pythonvalue(self):
265318
assert instance.pythonvalue("--11-04:00") == (11, pytz.FixedOffset(-240))
266319
assert instance.pythonvalue("--11") == (11, None)
267320
assert instance.pythonvalue("--02") == (2, None)
321+
assert instance.pythonvalue("\n\t --11Z \r") == (11, pytz.utc)
268322

269323
with pytest.raises(builtins.ParseError):
270324
assert instance.pythonvalue("99")
@@ -291,6 +345,7 @@ def test_pythonvalue(self):
291345
assert instance.pythonvalue("---01-04:00") == (1, pytz.FixedOffset(-240))
292346
assert instance.pythonvalue("---15") == (15, None)
293347
assert instance.pythonvalue("---31") == (31, None)
348+
assert instance.pythonvalue("\r\n \t---31 ") == (31, None)
294349
with pytest.raises(builtins.ParseError):
295350
assert instance.pythonvalue("99")
296351

0 commit comments

Comments
 (0)