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