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