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