001package org.hl7.fhir.r5.elementmodel;
002
003import java.io.ByteArrayInputStream;
004
005/*
006  Copyright (c) 2011+, HL7, Inc.
007  All rights reserved.
008
009  Redistribution and use in source and binary forms, with or without modification, 
010  are permitted provided that the following conditions are met:
011
012 * Redistributions of source code must retain the above copyright notice, this 
013     list of conditions and the following disclaimer.
014 * Redistributions in binary form must reproduce the above copyright notice, 
015     this list of conditions and the following disclaimer in the documentation 
016     and/or other materials provided with the distribution.
017 * Neither the name of HL7 nor the names of its contributors may be used to 
018     endorse or promote products derived from this software without specific 
019     prior written permission.
020
021  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
022  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
023  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
024  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
025  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
026  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
027  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
028  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
029  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
030  POSSIBILITY OF SUCH DAMAGE.
031
032 */
033
034
035
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.OutputStream;
039import java.io.OutputStreamWriter;
040import java.math.BigDecimal;
041import java.nio.charset.StandardCharsets;
042import java.util.ArrayList;
043import java.util.HashMap;
044import java.util.HashSet;
045import java.util.List;
046import java.util.Map;
047import java.util.Map.Entry;
048import java.util.Set;
049
050import org.hl7.fhir.exceptions.FHIRException;
051import org.hl7.fhir.exceptions.FHIRFormatError;
052import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
053import org.hl7.fhir.r5.context.ContextUtilities;
054import org.hl7.fhir.r5.context.IWorkerContext;
055import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
056import org.hl7.fhir.r5.elementmodel.JsonParser.ILogicalModelResolver;
057import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
058import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
059import org.hl7.fhir.r5.extensions.ExtensionUtilities;
060import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
061import org.hl7.fhir.r5.formats.IParser.OutputStyle;
062import org.hl7.fhir.r5.formats.JsonCreator;
063import org.hl7.fhir.r5.formats.JsonCreatorCanonical;
064import org.hl7.fhir.r5.formats.JsonCreatorDirect;
065import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
066import org.hl7.fhir.r5.model.ElementDefinition;
067import org.hl7.fhir.r5.model.StructureDefinition;
068
069import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
070import org.hl7.fhir.utilities.StringPair;
071import org.hl7.fhir.utilities.FileUtilities;
072import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
073import org.hl7.fhir.utilities.Utilities;
074import org.hl7.fhir.utilities.VersionUtilities;
075import org.hl7.fhir.utilities.i18n.I18nConstants;
076import org.hl7.fhir.utilities.json.model.JsonArray;
077import org.hl7.fhir.utilities.json.model.JsonComment;
078import org.hl7.fhir.utilities.json.model.JsonElement;
079import org.hl7.fhir.utilities.json.model.JsonNull;
080import org.hl7.fhir.utilities.json.model.JsonObject;
081import org.hl7.fhir.utilities.json.model.JsonPrimitive;
082import org.hl7.fhir.utilities.json.model.JsonProperty;
083import org.hl7.fhir.utilities.validation.ValidationMessage;
084import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
085import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
086import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
087import org.hl7.fhir.utilities.xhtml.XhtmlParser;
088
089
090@MarkedToMoveToAdjunctPackage
091public class JsonParser extends ParserBase {
092
093  public interface ILogicalModelResolver {
094
095    StructureDefinition resolve(JsonObject object);
096
097  }
098
099  private JsonCreator json;
100  private boolean allowComments;
101  private boolean elideElements;
102//  private boolean suppressResourceType;
103
104  private Element baseElement;
105  private boolean markedXhtml;
106  private ILogicalModelResolver logicalModelResolver;
107
108  public JsonParser(IWorkerContext context, ProfileUtilities utilities) {
109    super(context, utilities);
110
111  }
112
113  public JsonParser(IWorkerContext context) {
114    super(context);
115  }
116
117  public Element parse(String source, String type) throws Exception {
118    return parse(source, type, false);
119  }
120  
121  public Element parse(String source, String type, boolean inner) throws Exception {
122    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", source.getBytes(StandardCharsets.UTF_8), false);
123    JsonObject obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 
124    String path = "/"+type;
125    StructureDefinition sd = getDefinition(focusFragment.getErrors(), -1, -1, type);
126    if (sd == null) {
127      return null;
128    }
129
130    if (inner) {
131      // we have an anonymous wrapper that has an arbitrarily named property with the specified type. We're going to invent a snapshot for that 
132      sd = new StructureDefinition();
133      sd.setType("Wrapper");
134      ElementDefinition bEd = sd.getSnapshot().addElement();
135      ElementDefinition nEd = sd.getSnapshot().addElement();
136      bEd.setPath("Wrapper");
137      nEd.setPath("Wrapper."+obj.getProperties().get(0).getName());
138      nEd.addType().setCode(type);
139      nEd.setMax(obj.getProperties().get(0).getValue().isJsonArray() ? "*" : "1"); 
140    }
141    Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON);
142    result.setPath(type);
143    checkObject(focusFragment.getErrors(), obj, result, path);
144    result.setType(type);
145    parseChildren(focusFragment.getErrors(), path, obj, result, true, null);
146    result.numberChildren();
147    return result;
148  }
149
150
151  @Override
152  public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException {
153    return parse(inStream, 0);
154  }
155  
156  public List<ValidatedFragment> parse(InputStream inStream, int line) throws IOException, FHIRException {
157//    long start = System.currentTimeMillis();
158    byte[] content = FileUtilities.streamToBytes(inStream);
159    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", content, false);
160    
161    ByteArrayInputStream stream = new ByteArrayInputStream(content);
162    
163    // if we're parsing at this point, then we're going to use the custom parser
164    String source = FileUtilities.streamToString(stream);
165    JsonObject obj = null;
166    
167    if (policy == ValidationPolicy.EVERYTHING) {
168      try {
169        obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 
170      } catch (Exception e) {
171        logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1, null, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
172      }
173    } else {
174      obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 
175    }
176    
177    if (obj != null) {
178      focusFragment.setElement(parse(focusFragment.getErrors(), obj));
179    }
180    List<ValidatedFragment> res = new ArrayList<>();
181    res.add(focusFragment);
182
183    return res;
184  }
185
186  public Element parse(List<ValidationMessage> errors, JsonObject object) throws FHIRException {
187    return parse(errors, object, null);
188  }
189  
190  private StructureDefinition resolveLogical(JsonObject object) {
191    StructureDefinition sd = getLogical();
192    if (sd != null) {
193      return sd;
194    } else if (logicalModelResolver != null) {
195      return logicalModelResolver.resolve(object);
196    } else {
197      return null;
198    }
199  }
200  
201  public Element parse(List<ValidationMessage> errors, JsonObject object, String statedPath) throws FHIRException {
202    StructureDefinition sd = resolveLogical(object);
203    String name;
204    String path;      
205    if (sd == null) {
206      JsonElement rt = object.get("resourceType");
207      if (rt == null) {
208        logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
209        return null;
210      } else if (!rt.isJsonString()) {
211        logError(errors, "2022-11-26", line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
212        return null;
213      } else {
214        name = rt.asString();
215
216        sd = getDefinition(errors, line(object), col(object), name);
217        if (sd == null) {
218         return null;
219        }
220      }
221      path = statedPath == null ? name : statedPath;
222    } else {
223      name = sd.getType();
224      path = statedPath == null ? sd.getTypeTail() : statedPath;
225    }
226    baseElement = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON);
227    checkObject(errors, object, baseElement, path);
228    baseElement.markLocation(line(object), col(object));
229    baseElement.setType(name);
230    baseElement.setPath(statedPath == null ? baseElement.fhirTypeRoot() : statedPath);
231    parseChildren(errors, path, object, baseElement, true, null);
232    baseElement.numberChildren();
233    return baseElement;
234  }
235
236  private void checkObject(List<ValidationMessage> errors, JsonObject object, Element b, String path) {
237    b.setNativeObject(object);
238    checkComments(errors, object, b, path);
239    if (policy == ValidationPolicy.EVERYTHING) {
240      if (object.getProperties().size() == 0) {
241        logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
242      }
243    }    
244  }
245
246  private void checkComments(List<ValidationMessage> errors, JsonElement element, Element b, String path) throws FHIRFormatError {
247    if (element != null && element.hasComments()) {
248      if (allowComments) {
249        for (JsonComment c : element.getComments()) {
250          b.getComments().add(c.getContent());
251        }
252      } else {
253        for (JsonComment c : element.getComments()) {
254          logError(errors, "2022-11-26", c.getStart().getLine(), c.getStart().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMENTS_NOT_ALLOWED), IssueSeverity.ERROR);
255        }        
256      }
257    }
258  }
259
260  private List<Property> parseChildren(List<ValidationMessage> errors, String path, JsonObject object, Element element, boolean hasResourceType, List<Property> properties) throws FHIRException {
261    if (properties == null) {
262      // save time refetching these if we're in a loop
263      properties = element.getProperty().getChildProperties(element.getName(), null);
264    }
265    processChildren(errors, path, object);
266
267    // first pass: process the properties
268    for (Property property : properties) {
269      parseChildItem(errors, path, object.getProperties(), element, property);
270    }
271
272    // second pass: check for things not processed (including duplicates)
273    checkNotProcessed(errors, path, element, hasResourceType, object.getProperties());
274    
275    
276    if (object.isExtraComma()) {
277      logError(errors, "2022-11-26", object.getEnd().getLine(), object.getEnd().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
278    }
279    return properties;
280  }
281
282  private void checkNotProcessed(List<ValidationMessage> errors, String path, Element element, boolean hasResourceType, List<JsonProperty> children) {
283    if (policy != ValidationPolicy.NONE) {
284      for (JsonProperty e : children) {
285        if (hasResourceType && "resourceType".equals(e.getName())) {
286          // nothing
287        } else if (e.getTag() == 0) {
288          StructureDefinition sd = element.getProperty().isLogical() ? getContextUtilities().fetchByJsonName(e.getName()) : null;
289          if (sd != null) {
290            Property property = new Property(context, sd.getSnapshot().getElementFirstRep(), sd, element.getProperty().getUtils(), element.getProperty().getContextUtils());
291            parseChildItem(errors, path, children, element, property);
292          } else if ("fhir_comments".equals(e.getName()) && (VersionUtilities.isR2BVer(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion()))) {
293            if (!e.getValue().isJsonArray()) {
294              logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, e.getValue().type().toName()), IssueSeverity.ERROR);
295            } else {
296              for (JsonElement c : e.getValue().asJsonArray()) {
297                if (!c.isJsonString()) {
298                  logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, c.type().toName()), IssueSeverity.ERROR);
299                } else {
300                  element.getComments().add(c.asString());
301                }
302              }
303            }
304          } else {
305            JsonProperty p = getFoundJsonPropertyByName(e.getName(), children);
306            if (p != null) {
307              logError(errors, "2022-11-26", line(e.getValue()), col(e.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, e.getName()), IssueSeverity.ERROR);            
308            } else {
309              logError(errors, ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getName()), IssueSeverity.ERROR);
310            }
311          }
312        }
313      }
314    }    
315  }
316
317  private JsonProperty getFoundJsonPropertyByName(String name, List<JsonProperty> children) {
318    int hash = name.hashCode();
319    for (JsonProperty p : children) {
320      if (p.getTag() == 1 && hash == p.getNameHash()) {
321        return p;
322      }
323    }
324    return null;
325  }
326
327  private JsonProperty getJsonPropertyByName(String name, List<JsonProperty> children) {
328    int hash = name.hashCode();
329    for (JsonProperty p : children) {
330      if (p.getTag() == 0 && hash == p.getNameHash()) {
331        return p;
332      }
333    }
334    return null;
335  }
336  
337  private JsonProperty getJsonPropertyByBaseName(String name, List<JsonProperty> children) {
338    for (JsonProperty p : children) {
339      if (p.getTag() == 0 && p.getName().startsWith(name)) {
340        return p;
341      }
342    }
343    return null;
344  }
345  
346  private void processChildren(List<ValidationMessage> errors, String path, JsonObject object) {
347    for (JsonProperty p : object.getProperties()) {
348      if (p.isUnquotedName()) {
349        logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_NO_QUOTES, p.getName()), IssueSeverity.ERROR);
350      }
351      if (p.isNoComma()) {
352        logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_MISSING), IssueSeverity.ERROR);        
353      }
354    }
355  }
356
357  public void parseChildItem(List<ValidationMessage> errors, String path, List<JsonProperty> children, Element context, Property property) {
358    JsonProperty jp = getJsonPropertyByName(property.getJsonName(), children);
359    if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) {
360      if (property.isJsonPrimitiveChoice()) {
361        if (jp != null) {
362          jp.setTag(1);
363          JsonElement je = jp.getValue();
364          String type = getTypeFromJsonType(je);
365          if (type == null) {
366            logError(errors, ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(je), property.getName(), property.typeSummary()), IssueSeverity.ERROR);
367          } else if (property.hasType(type)) {
368            Property np = new Property(property.getContext(), property.getDefinition(), property.getStructure(), property.getUtils(), property.getContextUtils(), type);
369            parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, np, path, property.getName(), false);
370          } else {
371            logError(errors, ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(je), property.getName(), type, property.typeSummary()), IssueSeverity.ERROR);
372          }
373        }
374      } else { 
375        String baseName = property.getJsonName().substring(0, property.getName().length()-3);
376        jp = getJsonPropertyByBaseName(baseName, children);
377        JsonProperty jp1 = getJsonPropertyByBaseName("_"+baseName, children);
378        if (jp != null || jp1 != null) {
379          for (TypeRefComponent type : property.getDefinition().getType()) {
380            String eName = baseName + Utilities.capitalize(type.getWorkingCode());
381            if ((jp != null && jp.getName().equals(eName) || (jp1 != null && jp1.getName().equals("_"+eName)))) {
382              if (!isPrimitive(type.getWorkingCode()) && jp != null) {
383                parseChildComplex(errors, path, jp, context, property, eName, false);
384                break;
385              } else if (isPrimitive(type.getWorkingCode()) && (jp != null || jp1 != null)) {
386                parseChildPrimitive(errors, jp, jp1, context, property, path, eName, false);
387                break;
388              }
389            }
390          }
391        }
392      }
393    } else if (property.isPrimitive(property.getType(null))) {
394      parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, property, path, property.getJsonName(), property.hasJsonName());
395    } else if (jp != null) {
396      parseChildComplex(errors, path, jp, context, property, property.getJsonName(), property.hasJsonName());
397    }
398  }
399
400
401  private String getTypeFromJsonType(JsonElement je) {
402    if (je.isJsonPrimitive()) {
403      JsonPrimitive p = je.asJsonPrimitive();
404      if (p.isJsonString()) {
405        return "string";
406      } else if (p.isJsonBoolean()) {
407        return "boolean";
408      } else {
409        String s = p.asString();
410        if (Utilities.isInteger(s)) {
411          return "integer";
412        } else {
413          return "decimal";
414        }
415      }
416    } else {
417      return null;
418    }
419  }
420
421  private void parseChildComplex(List<ValidationMessage> errors, String path, JsonProperty p, Element element, Property property, String name, boolean isJsonName) throws FHIRException {
422    String npath = path+"."+property.getName();
423    String fpath = element.getPath()+"."+property.getName();
424    if (p != null) { p.setTag(1); }
425    JsonElement e = p == null ? null : p.getValue();
426    if (property.isList() && !property.isJsonKeyArray() && (e instanceof JsonArray)) {
427      JsonArray arr = (JsonArray) e;
428      if (arr.isExtraComma()) {
429        logError(errors, "2022-11-26", arr.getEnd().getLine(), arr.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
430      }
431      if (arr.size() == 0) {
432        if (property.canBeEmpty()) {
433          // nothing
434        } else {
435          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR);
436        }
437      }
438      int c = 0;
439      List<Property> properties = null;
440      for (JsonElement am : arr) {
441        properties = parseChildComplexInstance(errors, npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am, c == 0 ? arr : null, path, properties);
442        c++;
443      }
444    } else if (property.isJsonKeyArray()) {
445      String code = property.getJsonKeyProperty();
446      List<Property> properties = property.getChildProperties(element.getName(), null);
447      if (properties.size() != 2) {
448        logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_CHILD_COUNT, propNames(properties)), IssueSeverity.ERROR);               
449      } else {
450        Property propK = properties.get(0);
451        Property propV = properties.get(1);
452        if (!propK.getName().equals(code)) {
453          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_NAME, propNames(properties)), IssueSeverity.ERROR);                       
454        } else if (!propK.isPrimitive())  {
455          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_TYPE, propNames(properties), propK.typeSummary()), IssueSeverity.ERROR);                       
456        } else if (propV.isList())  {
457          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_LIST, propV.getName()), IssueSeverity.ERROR);                       
458        } else if (propV.isChoice() && propV.getName().endsWith("[x]"))  {
459          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_CHOICE, propV.getName()), IssueSeverity.ERROR);                       
460        } else if (!(e instanceof JsonObject)) {
461          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(e)), IssueSeverity.ERROR);                       
462        } else {
463          JsonObject o = (JsonObject) e;
464          if (o.isExtraComma()) {
465            logError(errors, "2022-11-26", o.getEnd().getLine(), o.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
466          }
467
468          int i = 0;
469          Set<String> names = new HashSet<>();
470          for (JsonProperty pv : o.getProperties()) {
471            if (names.contains(pv.getName())) {
472              logError(errors, "2022-11-26", line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, pv.getName()), IssueSeverity.ERROR);                                     
473            } else {
474              names.add(pv.getName());
475              pv.setTag(1);
476            }
477            // create an array entry
478            String npathArr = path+"."+property.getName()+"["+i+"]";
479            String fpathArr = element.getPath()+"."+property.getName()+"["+i+"]";
480            
481            Element n = new Element(name, property).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON);
482            n.setPath(fpath);
483            element.getChildren().add(n);
484            // handle the key
485            String fpathKey = fpathArr+"."+propK.getName();
486            Element nKey = new Element(code, propK).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON);
487            checkComments(errors, pv.getValue(), n, fpathArr);
488            nKey.setPath(fpathKey);
489            n.getChildren().add(nKey);
490            nKey.setValue(pv.getName());
491            
492            boolean ok = true;
493            Property pvl = propV;
494            if (propV.isJsonPrimitiveChoice()) {
495              ok = false;
496              String type = getTypeFromJsonType(pv.getValue());
497              if (type == null) {
498                logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(pv.getValue()), propV.getName(), propV.typeSummary()), IssueSeverity.ERROR);
499              } else if (propV.hasType(type)) {
500                pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), type);
501                ok = true;
502              } else if (propV.getDefinition().getType().size() == 1 && propV.typeIsConsistent(type)) {
503                pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), propV.getType());
504                ok = true;
505              } else {
506                logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(pv.getValue()), propV.getName(), type, propV.typeSummary()), IssueSeverity.ERROR);
507              }
508            }
509            if (ok) {
510              // handle the value
511              String npathV = npathArr+"."+pvl.getName();
512              String fpathV = fpathArr+"."+pvl.getName();
513              if (propV.isPrimitive(pvl.getType(null))) {
514                parseChildPrimitiveInstance(errors, n, pvl, pvl.getName(), false, npathV, fpathV, pv.getValue(), null);
515              } else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) {
516                parseChildComplexInstance(errors, npathV, fpathV, n, pvl, pvl.getName(), pv.getValue(), null, null, null);
517              } else {
518                logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(pv.getValue())), IssueSeverity.ERROR);                       
519              }
520            }
521            i++;
522          }
523        }
524      }
525    } else {
526      if (property.isJsonList()) {
527        logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(e), name, path), IssueSeverity.ERROR);
528      }
529      parseChildComplexInstance(errors, npath, fpath, element, property, name, e, null, null, null);
530    }
531  }
532
533  private Object propNames(List<Property> properties) {
534    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
535    for (Property p: properties) {
536      b.append(p.getName());
537    }
538    return b.toString();
539  }
540
541  private List<Property> parseChildComplexInstance(List<ValidationMessage> errors, String npath, String fpath, Element element, Property property, String name, JsonElement e, JsonElement commentContext, String commentPath, List<Property> properties) throws FHIRException {
542    if (property.hasTypeSpecifier()) {
543      FHIRPathEngine fpe = new FHIRPathEngine(context);
544      String type = null;
545      String cond = null;
546      for (StringPair sp : property.getTypeSpecifiers()) {
547        if (fpe.evaluateToBoolean(null, baseElement, baseElement, element, fpe.parse(sp.getName()))) {
548          type = sp.getValue();
549          cond = sp.getName();
550          break;
551        }
552      }
553      if (type != null) {
554        StructureDefinition sd = context.fetchResource(StructureDefinition.class, type);
555        if (sd == null) {
556          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ILLEGAL_TYPE, type, cond), IssueSeverity.ERROR);
557        } else {
558          if (sd.getAbstract()) {
559            logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ABSTRACT_TYPE, type, cond), IssueSeverity.ERROR);
560          }
561          property = property.cloneToType(sd);
562        }
563      } else {
564        StructureDefinition sd = context.fetchTypeDefinition(property.getType());
565        if (sd == null) {
566          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ILLEGAL_TYPE, property.getType()), IssueSeverity.ERROR);
567        } else if (sd.getAbstract()) {
568          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ABSTRACT_TYPE, property.getType()), IssueSeverity.ERROR);
569        }        
570      }
571    }
572    if (e instanceof JsonObject) {
573      JsonObject child = (JsonObject) e;
574      Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON);
575      n.setPath(fpath);
576      checkComments(errors, commentContext, n, commentPath);        
577      checkObject(errors, child, n, npath);
578      element.getChildren().add(n);
579      if (property.isResource()) {
580        parseResource(errors, npath, child, n, property);
581      } else {
582        return parseChildren(errors, npath, child, n, false, properties);
583      }
584    } else if (property.isNullable() && e instanceof JsonNull) {
585      // we create an element marked as a null element so we know something was present
586      JsonNull child = (JsonNull) e;
587      Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON);
588      checkComments(errors, commentContext, n, commentPath);        
589      checkComments(errors, child, n, fpath);
590      n.setPath(fpath);
591      element.getChildren().add(n);
592      n.setNull(true);
593      // nothing to do, it's ok, but we treat it like it doesn't exist
594    } else {
595      String msg = context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath);
596      logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, msg, IssueSeverity.ERROR);
597    }
598    return null;
599  }
600
601  private String describe(JsonElement e) {
602    if (e instanceof JsonArray) {
603      return "an Array";
604    }
605    if (e instanceof JsonObject) {
606      return "an Object";
607    }
608    if (e instanceof JsonNull) {
609      return "a Null";
610    }
611    if (e instanceof JsonPrimitive) {
612      return "a Primitive property";
613    }
614    return null;
615  }
616
617  private String describeType(JsonElement e) {
618    return e.type().toName();
619  }
620
621//  JsonProperty main = children.containsKey(name) ? children.get(name) : null;
622//  JsonProperty fork = children.containsKey("_"+name) ? children.get("_"+name) : null;
623  private void parseChildPrimitive(List<ValidationMessage> errors, JsonProperty main, JsonProperty fork, Element element, Property property, String path, String name, boolean isJsonName) throws FHIRException {
624    String npath = path+"."+property.getName();
625    String fpath = element.getPath()+"."+property.getName();
626    if (main != null) { main.setTag(1); }
627    if (fork != null) { fork.setTag(1); }
628    
629    if (main != null && main.getValue().isJsonString() && main.isUnquotedValue()) {
630      logError(errors, "2022-11-26", line(main.getValue()), col(main.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, main.getName(), main.getValue().asString()), IssueSeverity.ERROR);
631    }
632    if (main != null || fork != null) {
633      if (property.isJsonList()) {
634        boolean ok = true;
635        if (!(main == null || main.getValue() instanceof JsonArray)) {
636          logError(errors, ValidationMessage.NO_RULE_DATE, line(main.getValue()), col(main.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR);
637          ok = false;
638        }
639        if (!(fork == null || fork.getValue() instanceof JsonArray)) {
640          logError(errors, ValidationMessage.NO_RULE_DATE, line(fork.getValue()), col(fork.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR);
641          ok = false;
642        }
643        if (ok) {
644          JsonArray arr1 = (JsonArray) (main == null ? null : main.getValue());
645          JsonArray arr2 = (JsonArray) (fork == null ? null : fork.getValue());
646          if (arr1 != null && arr1.isExtraComma()) {
647            logError(errors, "2022-11-26", arr1.getEnd().getLine(), arr1.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
648          }
649          if (arr2 != null && arr2.isExtraComma()) {
650            logError(errors, "2022-11-26", arr2.getEnd().getLine(), arr2.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
651          }
652
653          for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) {
654            JsonElement m = arrI(arr1, i);
655            JsonElement f = arrI(arr2, i);
656            if (m != null && m.isJsonString() && arr1.isUnquoted(i)) {
657              logError(errors, "2022-11-26", line(m), col(m), path+"."+name+"["+i+"]", IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, "item", m.asString()), IssueSeverity.ERROR);
658            }
659            parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, m, f);
660          }
661        }
662      } else {
663        parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, main == null ? null : main.getValue(), fork == null ? null : fork.getValue());
664      }
665    }
666  }
667
668  private JsonElement arrI(JsonArray arr, int i) {
669    return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i);
670  }
671
672  private int arrC(JsonArray arr) {
673    return arr == null ? 0 : arr.size();
674  }
675
676  private void parseChildPrimitiveInstance(List<ValidationMessage> errors, Element element, Property property, String name, boolean isJsonName, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException {
677    if (main != null && !(main.isJsonBoolean() || main.isJsonNumber() || main.isJsonString())) {
678      logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(
679          I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR);
680    } else if (fork != null && !(fork instanceof JsonObject)) {
681      logError(errors, ValidationMessage.NO_RULE_DATE, line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR);
682    } else {
683      Element n = new Element(isJsonName ? property.getName() : name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork)).setFormat(FhirFormat.JSON);
684      if (main != null) {
685        checkComments(errors, main, n, npath);
686      }
687      if (fork != null) {
688        checkComments(errors, fork, n, npath);
689      }
690      n.setPath(fpath);
691      element.getChildren().add(n);
692      if (main != null) {
693        JsonPrimitive p = (JsonPrimitive) main;
694        n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.asString() : p.asString());
695        if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
696          try {
697            XhtmlParser xhtml = new XhtmlParser();
698            n.setXhtml(xhtml.setXmlMode(true).parse(n.getValue(), null).getDocumentElement());
699            if (policy == ValidationPolicy.EVERYTHING) {
700              for (StringPair s : xhtml.getValidationIssues()) {
701                logError(errors, "2022-11-17", line(main), col(main), npath, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR);                
702              }
703            }
704          } catch (Exception e) {
705            logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR);
706          }
707        }
708        if (policy == ValidationPolicy.EVERYTHING) {
709          // now we cross-check the primitive format against the stated type
710          if (Utilities.existsInList(n.getType(), "boolean")) {
711            if (!p.isJsonBoolean()) {
712              logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR);
713            }
714          } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) {
715            if (!p.isJsonNumber())
716              logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR);
717          } else if (!p.isJsonString()) {
718            logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR);
719          }
720        }
721      }
722      if (fork != null) {
723        JsonObject child = (JsonObject) fork;
724        checkObject(errors, child, n, npath);
725        parseChildren(errors, npath, child, n, false, null);
726      }
727    }
728  }
729
730
731  private void parseResource(List<ValidationMessage> errors, String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException {
732    JsonElement rt = res.get("resourceType");
733    if (rt == null) {
734      logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
735    } else if (!rt.isJsonString()) {
736      logError(errors, "2022-11-26", line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
737    } else {
738      String name = rt.asString();
739      StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null));
740      if (sd == null) {
741        logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);                            
742      } else {
743        parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities()), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
744        parent.setType(name);
745        parseChildren(errors, npath, res, parent, true, null);
746      }
747    }
748    if (res.isExtraComma()) {
749      logError(errors, "2022-11-26", res.getEnd().getLine(), res.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
750    }
751
752  }
753
754  private int line(JsonElement e) {
755    return e.getStart().getLine();
756  }
757
758  private int col(JsonElement e) {
759    return e.getEnd().getCol();
760  }
761
762
763  protected void prop(String name, String value, String link) throws IOException {
764    json.link(link);
765    if (name != null)
766      json.name(name);
767    json.value(value);
768  }
769
770  protected void open(String name, String link) throws IOException {
771    json.link(link);
772    if (name != null)
773      json.name(name);
774    json.beginObject();
775  }
776
777  protected void close() throws IOException {
778    json.endObject();
779  }
780
781  protected void openArray(String name, String link) throws IOException {
782    json.link(link);
783    if (name != null)
784      json.name(name);
785    json.beginArray();
786  }
787
788  protected void closeArray() throws IOException {
789    json.endArray();
790  }
791
792
793  @Override
794  public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException {
795    if (e.getPath() == null) {
796      e.populatePaths(null);
797    }
798
799    markedXhtml = false;
800    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
801    if (style == OutputStyle.CANONICAL) {
802      json = new JsonCreatorCanonical(osw);
803    } else if (style == OutputStyle.PRETTY) {
804      json = new JsonCreatorDirect(osw, true, allowComments);
805    } else {
806      json = new JsonCreatorDirect(osw, false, allowComments);
807    }
808    checkComposeComments(e);
809    json.beginObject();
810    if (!isSuppressResourceType(e.getProperty())) {
811      prop("resourceType", e.getType(), null);
812    }
813    Set<String> done = new HashSet<String>();
814    for (Element child : e.getChildren()) {
815      compose(e.getName(), e, done, child);
816    }
817    json.endObject();
818    json.finish();
819    osw.flush();
820  }
821
822  private boolean isSuppressResourceType(Property property) {
823    StructureDefinition sd = property.getStructure();
824    if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_SUPPRESS_RESOURCE_TYPE)) {
825      return ExtensionUtilities.readBoolExtension(sd, ExtensionDefinitions.EXT_SUPPRESS_RESOURCE_TYPE);
826    } else {
827      return false;
828    }
829  }
830
831  private void checkComposeComments(Element e) {
832    for (String s : e.getComments()) {
833      json.comment(s);
834    }
835  }
836
837  public void compose(Element e, JsonCreator json) throws Exception {
838    if (e.getPath() == null) {
839      e.populatePaths(null);
840    }
841    
842    this.json = json;
843    checkComposeComments(e);
844    json.beginObject();
845
846    if (!isSuppressResourceType(e.getProperty())) {
847      prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty()));
848    }
849    Set<String> done = new HashSet<String>();
850    for (Element child : e.getChildren()) {
851      compose(e.getName(), e, done, child);
852    }
853    json.endObject();
854    json.finish();
855  }
856
857  private void compose(String path, Element e, Set<String> done, Element child) throws IOException {
858    if (canonicalFilter.contains(child.getPath())) {
859      return;
860    }
861    checkComposeComments(child);
862    if (wantCompose(path, child)) {
863      boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList();
864      if (!isList) {// for specials, ignore the cardinality of the stated type
865        if (child.isElided() && isElideElements() && json.canElide())
866          json.elide();
867        else
868          compose(child.getName(), path, child);
869      } else if (!done.contains(child.getName())) {
870        done.add(child.getName());
871        List<Element> list = e.getChildrenByName(child.getName());
872        boolean skipList = false;
873        if (json.canElide() && isElideElements()) {
874          boolean foundNonElide = false;
875          for (Element listElement: list) {
876            if (!listElement.isElided()) {
877              foundNonElide = true;
878              break;
879            }
880          }
881          if (!foundNonElide) {
882            json.elide();
883            skipList = true;
884          }
885        }
886        if (!skipList) {
887          if (child.getProperty().getDefinition().hasExtension(ExtensionDefinitions.EXT_JSON_PROP_KEY))
888            composeKeyList(path, list);
889          else 
890            composeList(list.get(0).getName(), path, list);
891        }
892      }
893    }
894  }
895
896  
897  private void composeKeyList(String path, List<Element> list) throws IOException {
898    String keyName = list.get(0).getProperty().getDefinition().getExtensionString(ExtensionDefinitions.EXT_JSON_PROP_KEY);
899    json.name(list.get(0).getName());
900    json.beginObject();
901    for (Element e: list) {
902      Element key = null;
903      Element value = null;
904      for (Element child: e.getChildren()) {
905        if (child.getName().equals(keyName))
906          key = child;
907        else
908          value = child;
909      }
910      if (value.isPrimitive())
911        primitiveValue(key.getValue(), value);
912      else {
913        json.name(key.getValue());
914        checkComposeComments(e);
915        json.beginObject();
916        Set<String> done = new HashSet<String>();
917        for (Element child : value.getChildren()) {
918          compose(value.getName(), value, done, child);
919        }
920        json.endObject();
921        compose(value.getName(), path + "." + key.getValue(), value);
922      }
923    }
924    json.endObject();
925  }
926
927  private void composeList(String name, String path, List<Element> list) throws IOException {
928    // there will be at least one element
929    boolean complex = true;
930    if (list.get(0).isPrimitive()) {
931      boolean prim = false;
932      complex = false;
933      for (Element item : list) {
934        if (item.hasValue())
935          prim = true;
936        if (item.hasChildren())
937          complex = true;
938      }
939      if (prim) {
940        openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
941        for (Element item : list) {
942          if (item.isElided() && json.canElide())
943            json.elide();
944          else if (item.hasValue()) {
945            if (linkResolver != null && item.getProperty().isReference()) {
946              String ref = linkResolver.resolveReference(getReferenceForElement(item));
947              if (ref != null) {
948                json.externalLink(ref);
949              }
950            }
951            primitiveValue(null, item);
952          } else
953            json.nullValue();
954        }
955        closeArray();
956      }
957      name = "_"+name;
958    }
959    if (complex) {
960      openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
961      for (Element item : list) {
962        if (item.isElided() && json.canElide())
963          json.elide();
964        else if (item.hasChildren()) {
965          open(null,null);
966          if (item.getProperty().isResource() && !isSuppressResourceType(item.getProperty())) {
967            prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType()));
968          }
969          if (linkResolver != null && item.getProperty().isReference()) {
970            String ref = linkResolver.resolveReference(getReferenceForElement(item));
971            if (ref != null) {
972              json.externalLink(ref);
973            }
974          }
975          Set<String> done = new HashSet<String>();
976          for (Element child : item.getChildren()) {
977            compose(path+"."+name+"[]", item, done, child);
978          }
979          close();
980        } else {
981          json.nullValue();
982        }
983      }
984      closeArray();
985    }
986  }
987
988  private void primitiveValue(String name, Element item) throws IOException {
989    if (name != null) {
990      if (linkResolver != null)
991        json.link(linkResolver.resolveProperty(item.getProperty()));
992      json.name(name);
993    }
994    String type = item.getType();
995    if (item.hasXhtml()) {
996      json.value(new XhtmlComposer(XhtmlComposer.XML, false).setCanonical(json.isCanonical()).compose(item.getXhtml()));
997    } else if (Utilities.existsInList(type, "boolean")) {
998      json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false));
999    } else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) {
1000      json.value(new Integer(item.getValue()));
1001    } else if (Utilities.existsInList(type, "decimal")) {
1002      try {
1003        json.value(new BigDecimal(item.getValue()));
1004      } catch (Exception e) {
1005        throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue()));
1006      }
1007    } else {
1008      json.value(item.getValue());
1009    }
1010  }
1011
1012  private void compose(String name, String path, Element element) throws IOException {
1013    if (element.isPrimitive() || isPrimitive(element.getType())) {
1014      if (element.hasXhtml() || element.hasValue()) {
1015        primitiveValue(name, element);
1016      }
1017      name = "_"+name;
1018      if (!markedXhtml && element.getType().equals("xhtml"))
1019        json.anchor("end-xhtml");
1020        markedXhtml = true;
1021    }
1022    if (element.hasChildren()) {
1023      open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
1024      if (element.getProperty().isResource() && !isSuppressResourceType(element.getProperty())) {
1025        prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType()));
1026      }
1027      if (linkResolver != null && element.getProperty().isReference()) {
1028        String ref = linkResolver.resolveReference(getReferenceForElement(element));
1029        if (ref != null) {
1030          json.externalLink(ref);
1031        }
1032      }
1033
1034      if ("named-elements".equals(element.getProperty().getDefinition().getExtensionString(ExtensionDefinitions.EXT_EXTENSION_STYLE_NEW, ExtensionDefinitions.EXT_EXTENSION_STYLE_DEPRECATED))) {
1035        composeNamedChildren(path + "." + element.getJsonName(), element);        
1036      } else {
1037        Set<String> done = new HashSet<String>();
1038        for (Element child : element.getChildren()) {
1039          compose(path + "." + element.getJsonName(), element, done, child);
1040        }
1041      }
1042      close();
1043    }
1044  }
1045
1046  private void composeNamedChildren(String path, Element element) throws IOException {
1047    Map<String, StructureDefinition> names = new HashMap<>();
1048    for (Element child : element.getChildren()) {
1049      String name = child.getJsonName();
1050      StructureDefinition sd = child.getProperty().getStructure();
1051      if (!names.containsKey(name)) {
1052        names.put(name, sd);        
1053      } else if (names.get(name) != sd) {
1054        throw new FHIRException("Error: conflicting definitions for "+name+" at "+path+": "+sd.getVersionedUrl()+" / "+names.get(name).getVersionedUrl());
1055      }
1056    }
1057    for (String name : Utilities.sorted(names.keySet())) {
1058      StructureDefinition sd = names.get(name);
1059      ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
1060      boolean list = !"1".equals(ed.getMax());
1061      List<Element> children = new ArrayList<>();
1062      for (Element child : element.getChildren()) {
1063        if (name.equals(child.getJsonName())) {
1064          children.add(child);
1065        }
1066      }
1067      if (list) {        
1068        composeList(name, path+"."+name, children);
1069      } else if (children.size() > 1) {
1070        throw new FHIRException("Error: definitions for "+name+" ("+sd.getVersionedUrl()+") says it cannot repeat, but "+children.size()+" items found");        
1071      } else {
1072        compose(name, path+"."+name, children.get(0));
1073      }
1074    }
1075  }
1076
1077  public boolean isAllowComments() {
1078    return allowComments;
1079  }
1080
1081  public JsonParser setAllowComments(boolean allowComments) {
1082    this.allowComments = allowComments;
1083    return this;
1084  }
1085
1086  public boolean isElideElements() {
1087    return elideElements;
1088  }
1089
1090  public JsonParser setElideElements(boolean elideElements) {
1091    this.elideElements = elideElements;
1092    return this;
1093  }
1094/*
1095  public boolean isSuppressResourceType() {
1096    return suppressResourceType;
1097  }
1098
1099  public JsonParser setSuppressResourceType(boolean suppressResourceType) {
1100    this.suppressResourceType = suppressResourceType;
1101    return this;
1102  }
1103*/
1104
1105  public ILogicalModelResolver getLogicalModelResolver() {
1106    return logicalModelResolver;
1107  }
1108
1109  public JsonParser setLogicalModelResolver(ILogicalModelResolver logicalModelResolver) {
1110    this.logicalModelResolver = logicalModelResolver;
1111    return this;
1112  }
1113
1114}