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