comparison Implab/Xml/JsonXmlReader.cs @ 227:8d5de4eb9c2c v2

Reimplemented JsonXmlReader, added support for null values: JSON null values are mapped to empty nodes with 'xsi:nil' attribute set to 'true'
author cin
date Sat, 09 Sep 2017 03:53:13 +0300
parents
children 6fa235c5a760
comparison
equal deleted inserted replaced
226:9428ea36838e 227:8d5de4eb9c2c
1 using Implab.Formats.JSON;
2 using System;
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.Linq;
6 using System.Text;
7 using System.Threading.Tasks;
8 using System.Xml;
9
10 namespace Implab.Xml {
11 public class JsonXmlReader : XmlReader {
12 struct JsonContext {
13 public string localName;
14 public bool skip;
15 }
16
17 JSONParser m_parser;
18 JsonXmlReaderOptions m_options;
19 JsonXmlReaderPosition m_position = JsonXmlReaderPosition.Initial;
20 XmlNameTable m_nameTable;
21
22 readonly string m_jsonRootName;
23 readonly string m_jsonNamespace;
24 readonly string m_jsonPrefix;
25 readonly bool m_jsonFlattenArrays;
26 readonly string m_jsonArrayItemName;
27
28 string m_jsonLocalName;
29 string m_jsonValueName;
30 bool m_jsonSkip; // indicates wheather to generate closing tag for objects or arrays
31
32 readonly Stack<JsonContext> m_jsonNameStack = new Stack<JsonContext>();
33
34 XmlQualifiedName m_elementQName;
35 string m_elementPrefix;
36 int m_elementDepth;
37 bool m_elementIsEmpty;
38
39 XmlQualifiedName m_qName;
40 string m_prefix;
41 int m_xmlDepth;
42
43 XmlSimpleAttribute[] m_attributes;
44 object m_value;
45 bool m_isEmpty;
46
47 XmlNodeType m_nodeType = XmlNodeType.None;
48
49 bool m_isAttribute; // indicates that we are reading attribute nodes
50 int m_currentAttribute;
51 bool m_currentAttributeRead;
52
53
54 XmlNameContext m_context;
55 int m_nextPrefix = 1;
56
57 readonly string m_xmlnsPrefix;
58 readonly string m_xmlnsNamespace;
59 readonly string m_xsiPrefix;
60 readonly string m_xsiNamespace;
61
62
63 public JsonXmlReader(JSONParser parser, JsonXmlReaderOptions options) {
64 Safe.ArgumentNotNull(parser, nameof(parser));
65 m_parser = parser;
66
67 m_options = options ?? new JsonXmlReaderOptions();
68
69 m_jsonFlattenArrays = m_options.FlattenArrays;
70 m_nameTable = m_options.NameTable ?? new NameTable();
71
72 m_jsonRootName = m_nameTable.Add(string.IsNullOrEmpty(m_options.RootName) ? "data" : m_options.RootName);
73 m_jsonArrayItemName = m_nameTable.Add(string.IsNullOrEmpty(m_options.ArrayItemName) ? "item" : m_options.ArrayItemName);
74 m_jsonNamespace = m_nameTable.Add(m_options.NamespaceUri ?? string.Empty);
75 m_jsonPrefix = m_nameTable.Add(m_options.NodesPrefix ?? string.Empty);
76 m_xmlnsPrefix = m_nameTable.Add(XmlNameContext.XmlnsPrefix);
77 m_xmlnsNamespace = m_nameTable.Add(XmlNameContext.XmlnsNamespace);
78 m_xsiPrefix = m_nameTable.Add(XmlNameContext.XsiPrefix);
79 m_xsiNamespace = m_nameTable.Add(XmlNameContext.XsiNamespace);
80
81 // TODO validate m_jsonRootName, m_jsonArrayItemName
82
83 m_context = new XmlNameContext(null);
84 }
85
86 public override int AttributeCount {
87 get {
88 return m_attributes == null ? 0 : m_attributes.Length;
89 }
90 }
91
92 public override string BaseURI {
93 get {
94 return string.Empty;
95 }
96 }
97
98 public override int Depth {
99 get {
100 return m_xmlDepth;
101 }
102 }
103
104 public override bool EOF {
105 get {
106 return m_position == JsonXmlReaderPosition.Eof;
107 }
108 }
109
110 public override bool IsEmptyElement {
111 get { return m_isEmpty; }
112 }
113
114
115 public override string LocalName {
116 get {
117 return m_qName.Name;
118 }
119 }
120
121 public override string NamespaceURI {
122 get {
123 return m_qName.Namespace;
124 }
125 }
126
127 public override XmlNameTable NameTable {
128 get {
129 return m_nameTable;
130 }
131 }
132
133 public override XmlNodeType NodeType {
134 get {
135 return m_nodeType;
136 }
137 }
138
139 public override string Prefix {
140 get {
141 return m_prefix;
142 }
143 }
144
145 public override ReadState ReadState {
146 get {
147 switch (m_position) {
148 case JsonXmlReaderPosition.Initial:
149 return ReadState.Initial;
150 case JsonXmlReaderPosition.Eof:
151 return ReadState.EndOfFile;
152 case JsonXmlReaderPosition.Closed:
153 return ReadState.Closed;
154 case JsonXmlReaderPosition.Error:
155 return ReadState.Error;
156 default:
157 return ReadState.Interactive;
158 };
159 }
160 }
161
162 public override string Value {
163 get {
164 return ConvertValueToString(m_value);
165 }
166 }
167
168 static string ConvertValueToString(object value) {
169 if (value == null)
170 return string.Empty;
171
172 switch (Convert.GetTypeCode(value)) {
173 case TypeCode.Double:
174 return ((double)value).ToString(CultureInfo.InvariantCulture);
175 case TypeCode.String:
176 return (string)value;
177 case TypeCode.Boolean:
178 return (bool)value ? "true" : "false";
179 default:
180 return value.ToString();
181 }
182 }
183
184 public override string GetAttribute(int i) {
185 Safe.ArgumentInRange(i, 0, AttributeCount - 1, nameof(i));
186 return ConvertValueToString(m_attributes[i].Value);
187 }
188
189 public override string GetAttribute(string name) {
190 if (m_attributes == null)
191 return null;
192 var qName = m_context.Resolve(name);
193 var attr = Array.Find(m_attributes, x => x.QName == qName);
194 var value = ConvertValueToString(attr?.Value);
195 return value == string.Empty ? null : value;
196 }
197
198 public override string GetAttribute(string name, string namespaceURI) {
199 if (m_attributes == null)
200 return null;
201 var qName = new XmlQualifiedName(name, namespaceURI);
202 var attr = Array.Find(m_attributes, x => x.QName == qName);
203 var value = ConvertValueToString(attr?.Value);
204 return value == string.Empty ? null : value;
205 }
206
207 public override string LookupNamespace(string prefix) {
208 return m_context.ResolvePrefix(prefix);
209 }
210
211 public override bool MoveToAttribute(string name) {
212 if (m_attributes == null || m_attributes.Length == 0)
213 return false;
214
215 var qName = m_context.Resolve(name);
216 var index = Array.FindIndex(m_attributes, x => x.QName == qName);
217 if (index >= 0) {
218 MoveToAttributeImpl(index);
219 return true;
220 }
221 return false;
222 }
223
224 public override bool MoveToAttribute(string name, string ns) {
225 if (m_attributes == null || m_attributes.Length == 0)
226 return false;
227
228 var qName = m_context.Resolve(name);
229 var index = Array.FindIndex(m_attributes, x => x.QName == qName);
230 if (index >= 0) {
231 MoveToAttributeImpl(index);
232 return true;
233 }
234 return false;
235 }
236
237 void MoveToAttributeImpl(int i) {
238 if (!m_isAttribute) {
239 m_elementQName = m_qName;
240 m_elementDepth = m_xmlDepth;
241 m_elementPrefix = m_prefix;
242 m_elementIsEmpty = m_isEmpty;
243 m_isAttribute = true;
244 }
245
246 var attr = m_attributes[i];
247
248
249 m_currentAttribute = i;
250 m_currentAttributeRead = false;
251 m_nodeType = XmlNodeType.Attribute;
252
253 m_xmlDepth = m_elementDepth + 1;
254 m_qName = attr.QName;
255 m_value = attr.Value;
256 m_prefix = attr.Prefix;
257 }
258
259 public override bool MoveToElement() {
260 if (m_isAttribute) {
261 m_value = null;
262 m_nodeType = XmlNodeType.Element;
263 m_xmlDepth = m_elementDepth;
264 m_prefix = m_elementPrefix;
265 m_qName = m_elementQName;
266 m_isEmpty = m_elementIsEmpty;
267 m_isAttribute = false;
268 return true;
269 }
270 return false;
271 }
272
273 public override bool MoveToFirstAttribute() {
274 if (m_attributes != null && m_attributes.Length > 0) {
275 MoveToAttributeImpl(0);
276 return true;
277 }
278 return false;
279 }
280
281 public override bool MoveToNextAttribute() {
282 if (m_isAttribute) {
283 var next = m_currentAttribute + 1;
284 if (next < AttributeCount) {
285 MoveToAttributeImpl(next);
286 return true;
287 }
288 return false;
289 } else {
290 return MoveToFirstAttribute();
291 }
292
293 }
294
295 public override bool ReadAttributeValue() {
296 if (!m_isAttribute || m_currentAttributeRead)
297 return false;
298
299 ValueNode(m_attributes[m_currentAttribute].Value);
300 m_currentAttributeRead = true;
301 return true;
302 }
303
304 public override void ResolveEntity() {
305 /* do nothing */
306 }
307
308 /// <summary>
309 /// Determines do we need to increase depth after the current node
310 /// </summary>
311 /// <returns></returns>
312 public bool IsSibling() {
313 switch (m_nodeType) {
314 case XmlNodeType.None: // start document
315 case XmlNodeType.Attribute: // after attribute only it's content can be iterated with ReadAttributeValue method
316 return false;
317 case XmlNodeType.Element:
318 // if the elemnt is empty the next element will be it's sibling
319 return m_isEmpty;
320
321 case XmlNodeType.Document:
322 case XmlNodeType.DocumentFragment:
323 case XmlNodeType.Entity:
324 case XmlNodeType.Text:
325 case XmlNodeType.CDATA:
326 case XmlNodeType.EntityReference:
327 case XmlNodeType.ProcessingInstruction:
328 case XmlNodeType.Comment:
329 case XmlNodeType.DocumentType:
330 case XmlNodeType.Notation:
331 case XmlNodeType.Whitespace:
332 case XmlNodeType.SignificantWhitespace:
333 case XmlNodeType.EndElement:
334 case XmlNodeType.EndEntity:
335 case XmlNodeType.XmlDeclaration:
336 default:
337 return true;
338 }
339 }
340
341 void ValueNode(object value) {
342 if (!IsSibling()) // the node is nested
343 m_xmlDepth++;
344
345 m_qName = XmlQualifiedName.Empty;
346 m_nodeType = XmlNodeType.Text;
347 m_prefix = string.Empty;
348 m_value = value;
349 m_isEmpty = false;
350 m_attributes = null;
351 }
352
353 void ElementNode(string name, string ns, XmlSimpleAttribute[] attrs, bool empty) {
354 if (!IsSibling()) // the node is nested
355 m_xmlDepth++;
356
357 m_context = new XmlNameContext(m_context);
358 List<XmlSimpleAttribute> definedAttrs = null;
359
360 // define new namespaces
361 if (attrs != null) {
362 foreach (var attr in attrs) {
363 if (attr.QName.Name == "xmlns") {
364 m_context.DefinePrefix(ConvertValueToString(attr.Value), string.Empty);
365 } else if (attr.Prefix == m_xmlnsPrefix) {
366 m_context.DefinePrefix(ConvertValueToString(attr.Value), attr.QName.Name);
367 } else {
368 string attrPrefix;
369 if (string.IsNullOrEmpty(attr.QName.Namespace))
370 continue;
371
372 // auto-define prefixes
373 if (!m_context.LookupNamespacePrefix(attr.QName.Namespace, out attrPrefix) || string.IsNullOrEmpty(attrPrefix)) {
374 // new namespace prefix added
375 attrPrefix = m_context.CreateNamespacePrefix(attr.QName.Namespace);
376 attr.Prefix = attrPrefix;
377
378 if (definedAttrs == null)
379 definedAttrs = new List<XmlSimpleAttribute>();
380
381 definedAttrs.Add(new XmlSimpleAttribute(attrPrefix, m_xmlnsNamespace, m_xmlnsPrefix, attr.QName.Namespace));
382 }
383 }
384 }
385 }
386
387 string p;
388 // auto-define prefixes
389 if (!m_context.LookupNamespacePrefix(ns, out p)) {
390 p = m_context.CreateNamespacePrefix(ns);
391 if (definedAttrs == null)
392 definedAttrs = new List<XmlSimpleAttribute>();
393
394 definedAttrs.Add(new XmlSimpleAttribute(p, m_xmlnsNamespace, m_xmlnsPrefix, ns));
395 }
396
397 if (definedAttrs != null) {
398 if (attrs != null)
399 definedAttrs.AddRange(attrs);
400 attrs = definedAttrs.ToArray();
401 }
402
403 m_nodeType = XmlNodeType.Element;
404 m_qName = new XmlQualifiedName(name, ns);
405 m_prefix = p;
406 m_value = null;
407 m_isEmpty = empty;
408 m_attributes = attrs;
409 }
410
411 void EndElementNode(string name, string ns) {
412 if (IsSibling()) // closing the element which has children
413 m_xmlDepth--;
414
415 string p;
416 if (!m_context.LookupNamespacePrefix(ns, out p))
417 throw new Exception($"Failed to lookup namespace '{ns}'");
418
419 m_context = m_context.ParentContext;
420 m_nodeType = XmlNodeType.EndElement;
421 m_prefix = p;
422 m_qName = new XmlQualifiedName(name, ns);
423 m_value = null;
424 m_attributes = null;
425 m_isEmpty = false;
426 }
427
428 void XmlDeclaration() {
429 if (!IsSibling()) // the node is nested
430 m_xmlDepth++;
431 m_nodeType = XmlNodeType.XmlDeclaration;
432 m_qName = new XmlQualifiedName("xml");
433 m_value = "version='1.0'";
434 m_prefix = string.Empty;
435 m_attributes = null;
436 m_isEmpty = false;
437 }
438
439 public override bool Read() {
440 try {
441 string elementName;
442 XmlSimpleAttribute[] elementAttrs = null;
443 MoveToElement();
444
445 switch (m_position) {
446 case JsonXmlReaderPosition.Initial:
447 m_jsonLocalName = m_jsonRootName;
448 m_jsonSkip = false;
449 XmlDeclaration();
450 m_position = JsonXmlReaderPosition.Declaration;
451 return true;
452 case JsonXmlReaderPosition.Declaration:
453 elementAttrs = new[] {
454 new XmlSimpleAttribute(m_xsiPrefix, m_xmlnsNamespace, m_xmlnsPrefix, m_xsiNamespace),
455 string.IsNullOrEmpty(m_jsonPrefix) ?
456 new XmlSimpleAttribute(m_xmlnsPrefix, string.Empty, string.Empty, m_jsonNamespace) :
457 new XmlSimpleAttribute(m_jsonPrefix, m_xmlnsNamespace, m_xmlnsPrefix, m_jsonNamespace)
458 };
459 break;
460 case JsonXmlReaderPosition.ValueElement:
461 if (!m_isEmpty) {
462 ValueNode(m_parser.ElementValue);
463 m_position = JsonXmlReaderPosition.ValueContent;
464 return true;
465 } else {
466 m_position = JsonXmlReaderPosition.ValueEndElement;
467 break;
468 }
469 case JsonXmlReaderPosition.ValueContent:
470 EndElementNode(m_jsonValueName, m_jsonNamespace);
471 m_position = JsonXmlReaderPosition.ValueEndElement;
472 return true;
473 case JsonXmlReaderPosition.Eof:
474 case JsonXmlReaderPosition.Closed:
475 case JsonXmlReaderPosition.Error:
476 return false;
477 }
478
479 while (m_parser.Read()) {
480 var jsonName = m_nameTable.Add(m_parser.ElementName);
481
482 switch (m_parser.ElementType) {
483 case JSONElementType.BeginObject:
484 if (!EnterJsonObject(jsonName, out elementName))
485 continue;
486
487 m_position = JsonXmlReaderPosition.BeginObject;
488 ElementNode(elementName, m_jsonNamespace, elementAttrs, false);
489 break;
490 case JSONElementType.EndObject:
491 if (!LeaveJsonScope(out elementName))
492 continue;
493
494 m_position = JsonXmlReaderPosition.EndObject;
495 EndElementNode(elementName, m_jsonNamespace);
496 break;
497 case JSONElementType.BeginArray:
498 if (!EnterJsonArray(jsonName, out elementName))
499 continue;
500
501 m_position = JsonXmlReaderPosition.BeginArray;
502 ElementNode(elementName, m_jsonNamespace, elementAttrs, false);
503 break;
504 case JSONElementType.EndArray:
505 if (!LeaveJsonScope(out elementName))
506 continue;
507
508 m_position = JsonXmlReaderPosition.EndArray;
509 EndElementNode(elementName, m_jsonNamespace);
510 break;
511 case JSONElementType.Value:
512 if (!VisitJsonValue(jsonName, out m_jsonValueName))
513 continue;
514
515 m_position = JsonXmlReaderPosition.ValueElement;
516 if (m_parser.ElementValue == null)
517 // generate empty element with xsi:nil="true" attribute
518 ElementNode(
519 m_jsonValueName,
520 m_jsonNamespace,
521 new[] {
522 new XmlSimpleAttribute("nil", m_xsiNamespace, m_xsiPrefix, true)
523 },
524 true
525 );
526 else
527 ElementNode(m_jsonValueName, m_jsonNamespace, elementAttrs, m_parser.ElementValue as string == string.Empty);
528 break;
529 default:
530 throw new Exception($"Unexpected JSON element {m_parser.ElementType}: {m_parser.ElementName}");
531 }
532 return true;
533 }
534
535 m_position = JsonXmlReaderPosition.Eof;
536 return false;
537 } catch {
538 m_position = JsonXmlReaderPosition.Error;
539 throw;
540 }
541 }
542
543 void SaveJsonName() {
544 m_jsonNameStack.Push(new JsonContext {
545 skip = m_jsonSkip,
546 localName = m_jsonLocalName
547 });
548
549 }
550
551 bool EnterJsonObject(string name, out string elementName) {
552 SaveJsonName();
553 m_jsonSkip = false;
554
555 if (string.IsNullOrEmpty(name)) {
556 if (m_jsonNameStack.Count != 1 && !m_jsonFlattenArrays)
557 m_jsonLocalName = m_jsonArrayItemName;
558 } else {
559 m_jsonLocalName = name;
560 }
561
562 elementName = m_jsonLocalName;
563 return true;
564 }
565
566 /// <summary>
567 /// Called when JSON parser visits BeginArray ('[') element.
568 /// </summary>
569 /// <param name="name">Optional property name if the array is the member of an object</param>
570 /// <returns>true if element should be emited, false otherwise</returns>
571 bool EnterJsonArray(string name, out string elementName) {
572 SaveJsonName();
573
574 if (string.IsNullOrEmpty(name)) {
575 // m_jsonNameStack.Count == 1 means the root node
576 if (m_jsonNameStack.Count != 1 && !m_jsonFlattenArrays)
577 m_jsonLocalName = m_jsonArrayItemName;
578
579 m_jsonSkip = false; // we should not flatten arrays inside arrays or in the document root
580 } else {
581 m_jsonLocalName = name;
582 m_jsonSkip = m_jsonFlattenArrays;
583 }
584 elementName = m_jsonLocalName;
585
586 return !m_jsonSkip;
587 }
588
589 bool VisitJsonValue(string name, out string elementName) {
590 if (string.IsNullOrEmpty(name)) {
591 // m_jsonNameStack.Count == 0 means that JSON document consists from simple value
592 elementName = (m_jsonNameStack.Count == 0 || m_jsonFlattenArrays) ? m_jsonLocalName : m_jsonArrayItemName;
593 } else {
594 elementName = name;
595 }
596 return true;
597 }
598
599 bool LeaveJsonScope(out string elementName) {
600 elementName = m_jsonLocalName;
601 var skip = m_jsonSkip;
602
603 var prev = m_jsonNameStack.Pop();
604 m_jsonLocalName = prev.localName;
605 m_jsonSkip = prev.skip;
606
607 return !skip;
608 }
609
610 public override string ToString() {
611 switch (NodeType) {
612 case XmlNodeType.Element:
613 return $"<{Name} {string.Join(" ", (m_attributes ?? new XmlSimpleAttribute[0]).Select(x => $"{x.Prefix}{(string.IsNullOrEmpty(x.Prefix) ? "" : ":")}{x.QName.Name}='{ConvertValueToString(x.Value)}'"))} {(IsEmptyElement ? "/" : "")}>";
614 case XmlNodeType.Attribute:
615 return $"@{Name}";
616 case XmlNodeType.Text:
617 return $"{Value}";
618 case XmlNodeType.CDATA:
619 return $"<![CDATA[{Value}]]>";
620 case XmlNodeType.EntityReference:
621 return $"&{Name};";
622 case XmlNodeType.EndElement:
623 return $"</{Name}>";
624 default:
625 return $".{NodeType} {Name} {Value}";
626 }
627 }
628 }
629 }