1
1
package com .fasterxml .jackson .dataformat .xml .deser ;
2
2
3
3
import java .io .IOException ;
4
+
5
+ import javax .xml .XMLConstants ;
4
6
import javax .xml .stream .*;
5
7
6
8
import org .codehaus .stax2 .XMLStreamLocation2 ;
@@ -29,14 +31,19 @@ public class XmlTokenStream
29
31
public final static int XML_ATTRIBUTE_NAME = 3 ;
30
32
public final static int XML_ATTRIBUTE_VALUE = 4 ;
31
33
public final static int XML_TEXT = 5 ;
32
- public final static int XML_END = 6 ;
34
+ public final static int XML_NULL = 6 ; // since 2.10
35
+ public final static int XML_END = 7 ;
33
36
34
37
// // // token replay states
35
38
36
39
private final static int REPLAY_START_DUP = 1 ;
37
40
private final static int REPLAY_END = 2 ;
38
41
private final static int REPLAY_START_DELAYED = 3 ;
39
-
42
+
43
+ // Some helpful XML Constants
44
+
45
+ private final static String XSI_NAMESPACE = XMLConstants .W3C_XML_SCHEMA_INSTANCE_NS_URI ;
46
+
40
47
/*
41
48
/**********************************************************************
42
49
/* Configuration
@@ -64,6 +71,13 @@ public class XmlTokenStream
64
71
65
72
protected int _attributeCount ;
66
73
74
+ /**
75
+ * Marker used to indicate presence of `xsi:nil="true"' in current START_ELEMENT.
76
+ *
77
+ * @since 2.10
78
+ */
79
+ protected boolean _xsiNilFound ;
80
+
67
81
/**
68
82
* If true we have a START_ELEMENT with mixed text
69
83
*
@@ -76,7 +90,7 @@ public class XmlTokenStream
76
90
* to return (as field name and value pair), if any; -1
77
91
* when no attributes to return
78
92
*/
79
- protected int _nextAttributeIndex = 0 ;
93
+ protected int _nextAttributeIndex ;
80
94
81
95
protected String _localName ;
82
96
@@ -124,11 +138,17 @@ public XmlTokenStream(XMLStreamReader xmlReader, Object sourceRef,
124
138
+XMLStreamConstants .START_ELEMENT +"), instead got " +xmlReader .getEventType ());
125
139
}
126
140
_xmlReader = Stax2ReaderAdapter .wrapIfNecessary (xmlReader );
127
- _currentState = XML_START_ELEMENT ;
128
141
_localName = _xmlReader .getLocalName ();
129
142
_namespaceURI = _xmlReader .getNamespaceURI ();
130
- _attributeCount = _xmlReader .getAttributeCount ();
131
143
_formatFeatures = formatFeatures ;
144
+
145
+ _checkXsiAttributes (); // sets _attributeCount, _nextAttributeIndex
146
+
147
+ if (_xsiNilFound ) {
148
+ _currentState = XML_NULL ;
149
+ } else {
150
+ _currentState = XML_START_ELEMENT ;
151
+ }
132
152
}
133
153
134
154
public XMLStreamReader2 getXmlReader () {
@@ -200,10 +220,13 @@ public void skipEndElement() throws IOException, XMLStreamException
200
220
public String getText () { return _textValue ; }
201
221
public String getLocalName () { return _localName ; }
202
222
public String getNamespaceURI () { return _namespaceURI ; }
223
+
224
+ /*// not used as of 2.10
203
225
public boolean hasAttributes() {
204
226
return (_currentState == XML_START_ELEMENT) && (_attributeCount > 0);
205
227
}
206
-
228
+ */
229
+
207
230
public void closeCompletely () throws XMLStreamException {
208
231
_xmlReader .closeCompletely ();
209
232
}
@@ -319,6 +342,13 @@ private final int _next() throws XMLStreamException
319
342
++_nextAttributeIndex ;
320
343
// fall through
321
344
case XML_START_ELEMENT : // attributes to return?
345
+
346
+ // 06-Sep-2019, tatu: `xsi:nil` to induce "real" null value?
347
+ if (_xsiNilFound ) {
348
+ _xsiNilFound = false ;
349
+ return (_currentState = XML_NULL );
350
+ }
351
+
322
352
if (_nextAttributeIndex < _attributeCount ) {
323
353
_localName = _xmlReader .getAttributeLocalName (_nextAttributeIndex );
324
354
_namespaceURI = _xmlReader .getAttributeNamespace (_nextAttributeIndex );
@@ -358,11 +388,23 @@ private final int _next() throws XMLStreamException
358
388
}
359
389
// text followed by END_ELEMENT
360
390
return _handleEndElement ();
391
+ case XML_NULL :
392
+ // at this point we are pointing to START_ELEMENT, need to find
393
+ // matching END_ELEMENT, handle it
394
+ // 06-Sep-2019, tatu: Should handle error cases better but for now this'll do
395
+ switch (_skipUntilTag ()) {
396
+ case XMLStreamConstants .END_ELEMENT :
397
+ return _handleEndElement ();
398
+ case XMLStreamConstants .END_DOCUMENT :
399
+ throw new IllegalStateException ("Unexpected end-of-input after null token" );
400
+ default :
401
+ throw new IllegalStateException ("Unexpected START_ELEMENT after null token" );
402
+ }
403
+
361
404
case XML_END :
362
405
return XML_END ;
363
406
// throw new IllegalStateException("No more XML tokens available (end of input)");
364
407
}
365
-
366
408
// Ok: must be END_ELEMENT; see what tag we get (or end)
367
409
switch (_skipUntilTag ()) {
368
410
case XMLStreamConstants .END_DOCUMENT :
@@ -463,13 +505,22 @@ private final String _getText(XMLStreamReader2 r) throws XMLStreamException
463
505
/* Internal methods, other
464
506
/**********************************************************************
465
507
*/
508
+
509
+ /*
510
+ _xmlReader = Stax2ReaderAdapter.wrapIfNecessary(xmlReader);
511
+ _currentState = XML_START_ELEMENT;
512
+ _localName = _xmlReader.getLocalName();
513
+ _namespaceURI = _xmlReader.getNamespaceURI();
514
+ _attributeCount = _xmlReader.getAttributeCount();
515
+ _formatFeatures = formatFeatures;
516
+ */
466
517
467
518
private final int _initStartElement () throws XMLStreamException
468
519
{
469
520
final String ns = _xmlReader .getNamespaceURI ();
470
521
final String localName = _xmlReader .getLocalName ();
471
- _attributeCount = _xmlReader . getAttributeCount ();
472
- _nextAttributeIndex = 0 ;
522
+
523
+ _checkXsiAttributes () ;
473
524
474
525
/* Support for virtual wrapping: in wrapping, may either
475
526
* create a new wrapper scope (if in sub-tree, or matches
@@ -497,6 +548,30 @@ private final int _initStartElement() throws XMLStreamException
497
548
return (_currentState = XML_START_ELEMENT );
498
549
}
499
550
551
+ /**
552
+ * @since 2.10
553
+ */
554
+ private final void _checkXsiAttributes () {
555
+ int count = _xmlReader .getAttributeCount ();
556
+ _attributeCount = count ;
557
+
558
+ // [dataformat-xml#354]: xsi:nul handling; at first only if first attribute
559
+ if (count >= 1 ) {
560
+ if ("nil" .equals (_xmlReader .getAttributeLocalName (0 ))) {
561
+ if (XSI_NAMESPACE .equals (_xmlReader .getAttributeNamespace (0 ))) {
562
+ // need to skip, regardless of value
563
+ _nextAttributeIndex = 1 ;
564
+ // but only mark as nil marker if enabled
565
+ _xsiNilFound = "true" .equals (_xmlReader .getAttributeValue (0 ));
566
+ return ;
567
+ }
568
+ }
569
+ }
570
+
571
+ _nextAttributeIndex = 0 ;
572
+ _xsiNilFound = false ;
573
+ }
574
+
500
575
/**
501
576
* Method called to handle details of repeating "virtual"
502
577
* start/end elements, needed for handling 'unwrapped' lists.
0 commit comments