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