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