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