001package org.hl7.fhir.r4.utils; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.List; 006import java.util.Map; 007 008import org.apache.commons.lang3.NotImplementedException; 009import org.hl7.fhir.r4.conformance.ProfileUtilities; 010import org.hl7.fhir.r4.context.IWorkerContext; 011import org.hl7.fhir.r4.model.Base; 012import org.hl7.fhir.r4.model.BooleanType; 013import org.hl7.fhir.r4.model.CanonicalType; 014import org.hl7.fhir.r4.model.Coding; 015import org.hl7.fhir.r4.model.DateTimeType; 016import org.hl7.fhir.r4.model.DateType; 017import org.hl7.fhir.r4.model.DecimalType; 018import org.hl7.fhir.r4.model.Element; 019import org.hl7.fhir.r4.model.ElementDefinition; 020import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 021import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 022import org.hl7.fhir.r4.model.Enumeration; 023import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 024import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 025import org.hl7.fhir.r4.model.Factory; 026import org.hl7.fhir.r4.model.InstantType; 027import org.hl7.fhir.r4.model.IntegerType; 028import org.hl7.fhir.r4.model.Quantity; 029import org.hl7.fhir.r4.model.Questionnaire; 030import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent; 031import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; 032import org.hl7.fhir.r4.model.QuestionnaireResponse; 033import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent; 034import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; 035import org.hl7.fhir.r4.model.Reference; 036import org.hl7.fhir.r4.model.Resource; 037import org.hl7.fhir.r4.model.StringType; 038import org.hl7.fhir.r4.model.StructureDefinition; 039import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 040import org.hl7.fhir.r4.model.TimeType; 041import org.hl7.fhir.r4.model.Type; 042import org.hl7.fhir.r4.model.UriType; 043import org.hl7.fhir.r4.model.ValueSet; 044import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 045import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 046import org.hl7.fhir.r4.terminologies.ValueSetExpander; 047import org.hl7.fhir.exceptions.DefinitionException; 048import org.hl7.fhir.exceptions.FHIRException; 049import org.hl7.fhir.exceptions.FHIRFormatError; 050import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 051import org.hl7.fhir.utilities.Utilities; 052 053 054 055/* 056 Copyright (c) 2011+, HL7, Inc. 057 All rights reserved. 058 059 Redistribution and use in source and binary forms, with or without modification, 060 are permitted provided that the following conditions are met: 061 062 * Redistributions of source code must retain the above copyright notice, this 063 list of conditions and the following disclaimer. 064 * Redistributions in binary form must reproduce the above copyright notice, 065 this list of conditions and the following disclaimer in the documentation 066 and/or other materials provided with the distribution. 067 * Neither the name of HL7 nor the names of its contributors may be used to 068 endorse or promote products derived from this software without specific 069 prior written permission. 070 071 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 072 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 073 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 074 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 075 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 076 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 077 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 078 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 079 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 080 POSSIBILITY OF SUCH DAMAGE. 081 082 */ 083 084 085/** 086 * This class takes a profile, and builds a questionnaire from it 087 * 088 * If you then convert this questionnaire to a form using the 089 * XMLTools form builder, and then take the QuestionnaireResponse 090 * this creates, you can use QuestionnaireInstanceConvert to 091 * build an instance the conforms to the profile 092 * 093 * FHIR context: 094 * conceptLocator, codeSystems, valueSets, maps, client, profiles 095 * You don"t have to provide any of these, but 096 * the more you provide, the better the conversion will be 097 * 098 * @author Grahame 099 * 100 */ 101public class QuestionnaireBuilder { 102 103 private static final int MaxListboxCodings = 20; 104 private IWorkerContext context; 105 private int lastid = 0; 106 private Resource resource; 107 private StructureDefinition profile; 108 private Questionnaire questionnaire; 109 private QuestionnaireResponse response; 110 private String questionnaireId; 111 private Factory factory = new Factory(); 112 private Map<String, String> vsCache = new HashMap<String, String>(); 113 private ValueSetExpander expander; 114 115 // sometimes, when this is used, the questionnaire is already build and cached, and we are 116 // processing the response. for technical reasons, we still go through the process, but 117 // we don't do the intensive parts of the work (save time) 118 private Questionnaire prebuiltQuestionnaire; 119 120 public QuestionnaireBuilder(IWorkerContext context) { 121 super(); 122 this.context = context; 123 } 124 125 public Resource getReference() { 126 return resource; 127 } 128 129 public void setReference(Resource resource) { 130 this.resource = resource; 131 } 132 133 public StructureDefinition getProfile() { 134 return profile; 135 } 136 137 public void setProfile(StructureDefinition profile) { 138 this.profile = profile; 139 } 140 141 public Questionnaire getQuestionnaire() { 142 return questionnaire; 143 } 144 145 public void setQuestionnaire(Questionnaire questionnaire) { 146 this.questionnaire = questionnaire; 147 } 148 149 public QuestionnaireResponse getResponse() { 150 return response; 151 } 152 153 public void setResponse(QuestionnaireResponse response) { 154 this.response = response; 155 } 156 157 public String getQuestionnaireId() { 158 return questionnaireId; 159 } 160 161 public void setQuestionnaireId(String questionnaireId) { 162 this.questionnaireId = questionnaireId; 163 } 164 165 public Questionnaire getPrebuiltQuestionnaire() { 166 return prebuiltQuestionnaire; 167 } 168 169 public void setPrebuiltQuestionnaire(Questionnaire prebuiltQuestionnaire) { 170 this.prebuiltQuestionnaire = prebuiltQuestionnaire; 171 } 172 173 public ValueSetExpander getExpander() { 174 return expander; 175 } 176 177 public void setExpander(ValueSetExpander expander) { 178 this.expander = expander; 179 } 180 181 public void build() throws FHIRException { 182 if (profile == null) 183 throw new DefinitionException("QuestionnaireBuilder.build: no profile found"); 184 185 if (resource != null) 186 if (!profile.getType().equals(resource.getResourceType().toString())) 187 throw new DefinitionException("Wrong Type"); 188 189 if (prebuiltQuestionnaire != null) 190 questionnaire = prebuiltQuestionnaire; 191 else 192 questionnaire = new Questionnaire(); 193 if (resource != null) 194 response = new QuestionnaireResponse(); 195 processMetadata(); 196 197 198 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 199 List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>(); 200 201 if (resource != null) 202 answerGroups.addAll(response.getItem()); 203 if (prebuiltQuestionnaire != null) { 204 // give it a fake group to build 205 Questionnaire.QuestionnaireItemComponent group = new Questionnaire.QuestionnaireItemComponent(); 206 group.setType(QuestionnaireItemType.GROUP); 207 buildGroup(group, profile, profile.getSnapshot().getElement().get(0), list, answerGroups); 208 } else 209 buildGroup(questionnaire.getItem().get(0), profile, profile.getSnapshot().getElement().get(0), list, answerGroups); 210 // 211 // NarrativeGenerator ngen = new NarrativeGenerator(context); 212 // ngen.generate(result); 213 // 214 // if FResponse <> nil then 215 // FResponse.collapseAllContained; 216 } 217 218 private void processMetadata() { 219 // todo: can we derive a more informative identifier from the questionnaire if we have a profile 220 if (prebuiltQuestionnaire == null) { 221 questionnaire.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(questionnaireId); 222 questionnaire.setVersion(profile.getVersion()); 223 questionnaire.setStatus(profile.getStatus()); 224 questionnaire.setDate(profile.getDate()); 225 questionnaire.setPublisher(profile.getPublisher()); 226 Questionnaire.QuestionnaireItemComponent item = new Questionnaire.QuestionnaireItemComponent(); 227 questionnaire.addItem(item); 228 item.getCode().addAll(profile.getKeyword()); 229 questionnaire.setId(nextId("qs")); 230 } 231 232 if (response != null) { 233 // no identifier - this is transient 234 response.setQuestionnaire("#"+questionnaire.getId()); 235 response.getContained().add(questionnaire); 236 response.setStatus(QuestionnaireResponseStatus.INPROGRESS); 237 QuestionnaireResponse.QuestionnaireResponseItemComponent item = new QuestionnaireResponse.QuestionnaireResponseItemComponent(); 238 response.addItem(item); 239 item.setUserData("object", resource); 240 } 241 242 } 243 244 private String nextId(String prefix) { 245 lastid++; 246 return prefix+Integer.toString(lastid); 247 } 248 249 private void buildGroup(QuestionnaireItemComponent group, StructureDefinition profile, ElementDefinition element, 250 List<ElementDefinition> parents, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 251 group.setLinkId(element.getPath()); // todo: this will be wrong when we start slicing 252 group.setText(element.getShort()); // todo - may need to prepend the name tail... 253 if (element.getComment() != null) { 254 Questionnaire.QuestionnaireItemComponent display = new Questionnaire.QuestionnaireItemComponent(); 255 display.setType(QuestionnaireItemType.DISPLAY); 256 display.setText(element.getComment()); 257 group.addItem(display); 258 } 259 group.setType(QuestionnaireItemType.GROUP); 260 ToolingExtensions.addFlyOver(group, element.getDefinition()); 261 group.setRequired(element.getMin() > 0); 262 if (element.getMin() > 0) 263 ToolingExtensions.addMin(group, element.getMin()); 264 group.setRepeats(!element.getMax().equals("1")); 265 if (!element.getMax().equals("*")) 266 ToolingExtensions.addMax(group, Integer.parseInt(element.getMax())); 267 268 for (org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) { 269 ag.setLinkId(group.getLinkId()); 270 ag.setText(group.getText()); 271 } 272 273 // now, we iterate the children 274 List<ElementDefinition> list = ProfileUtilities.getChildList(profile, element); 275 for (ElementDefinition child : list) { 276 277 if (!isExempt(element, child) && !parents.contains(child)) { 278 List<ElementDefinition> nparents = new ArrayList<ElementDefinition>(); 279 nparents.addAll(parents); 280 nparents.add(child); 281 QuestionnaireItemComponent childGroup = group.addItem(); 282 childGroup.setType(QuestionnaireItemType.GROUP); 283 284 List<QuestionnaireResponse.QuestionnaireResponseItemComponent> nResponse = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>(); 285 processExisting(child.getPath(), answerGroups, nResponse); 286 // if the element has a type, we add a question. else we add a group on the basis that 287 // it will have children of its own 288 if (child.getType().isEmpty() || isAbstractType(child.getType())) 289 buildGroup(childGroup, profile, child, nparents, nResponse); 290 else if (isInlineDataType(child.getType())) 291 buildGroup(childGroup, profile, child, nparents, nResponse); // todo: get the right children for this one... 292 else 293 buildQuestion(childGroup, profile, child, child.getPath(), nResponse, parents); 294 } 295 } 296 } 297 298 private boolean isAbstractType(List<TypeRefComponent> type) { 299 return type.size() == 1 && (type.get(0).getCode().equals("Element") || type.get(0).getCode().equals("BackboneElement")); 300 } 301 302 private boolean isInlineDataType(List<TypeRefComponent> type) { 303 return type.size() == 1 && !Utilities.existsInList(type.get(0).getCode(), "code", "string", "id", "oid", "markdown", "uri", "boolean", "decimal", "dateTime", "date", "instant", "time", "CodeableConcept", "Period", "Ratio", 304 "HumanName", "Address", "ContactPoint", "Identifier", "integer", "positiveInt", "unsignedInt", "Coding", "Quantity", "Count", "Age", "Duration", 305 "Distance", "Money", "Money", "Reference", "Duration", "base64Binary", "Attachment", "Age", "Range", "Timing", "Annotation", "SampledData", "Extension", 306 "SampledData", "Narrative", "Resource", "Meta", "url", "canonical"); 307 } 308 309 private boolean isExempt(ElementDefinition element, ElementDefinition child) { 310 String n = tail(child.getPath()); 311 String t = ""; 312 if (!element.getType().isEmpty()) 313 t = element.getType().get(0).getCode(); 314 315 // we don't generate questions for the base stuff in every element 316 if (t.equals("Resource") && (n.equals("text") || n.equals("language") || n.equals("contained"))) 317 return true; 318 // we don't generate questions for extensions 319 else if (n.equals("extension") || n.equals("modifierExtension")) { 320 if (child.getType().size() > 0 && !child.getType().get(0).hasProfile()) 321 return false; 322 else 323 return true; 324 } else 325 return false; 326 } 327 328 private String tail(String path) { 329 return path.substring(path.lastIndexOf('.')+1); 330 } 331 332 private void processExisting(String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> nResponse) throws FHIRException { 333 // processing existing data 334 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) { 335 List<Base> children = ((Element) ag.getUserData("object")).listChildrenByName(tail(path)); 336 for (Base child : children) { 337 if (child != null) { 338 QuestionnaireResponse.QuestionnaireResponseItemComponent ans = ag.addItem(); 339 ans.setUserData("object", child); 340 nResponse.add(ans); 341 } 342 } 343 } 344 } 345 346 private void buildQuestion(QuestionnaireItemComponent group, StructureDefinition profile, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException { 347 group.setLinkId(path); 348 349 // in this context, we don't have any concepts to mark... 350 group.setText(element.getShort()); // prefix with name? 351 group.setRequired(element.getMin() > 0); 352 if (element.getMin() > 0) 353 ToolingExtensions.addMin(group, element.getMin()); 354 group.setRepeats(!element.getMax().equals('1')); 355 if (!element.getMax().equals("*")) 356 ToolingExtensions.addMax(group, Integer.parseInt(element.getMax())); 357 358 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) { 359 ag.setLinkId(group.getLinkId()); 360 ag.setText(group.getText()); 361 } 362 363 if (!Utilities.noString(element.getComment())) 364 ToolingExtensions.addFlyOver(group, element.getDefinition()+" "+element.getComment()); 365 else 366 ToolingExtensions.addFlyOver(group, element.getDefinition()); 367 368 if (element.getType().size() > 1 || element.getType().get(0).getCode().equals("*")) { 369 List<TypeRefComponent> types = expandTypeList(element.getType()); 370 Questionnaire.QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.CHOICE, element.getPath(), "_type", "type", null, makeTypeList(profile, types, element.getPath())); 371 for (TypeRefComponent t : types) { 372 Questionnaire.QuestionnaireItemComponent sub = q.addItem(); 373 sub.setType(QuestionnaireItemType.GROUP); 374 sub.setLinkId(element.getPath()+"._"+t.getUserData("text")); 375 sub.setText((String) t.getUserData("text")); 376 // always optional, never repeats 377 378 List<QuestionnaireResponse.QuestionnaireResponseItemComponent> selected = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>(); 379 selectTypes(profile, sub, t, answerGroups, selected); 380 processDataType(profile, sub, element, element.getPath()+"._"+t.getUserData("text"), t, selected, parents); 381 } 382 } else 383 // now we have to build the question panel for each different data type 384 processDataType(profile, group, element, element.getPath(), element.getType().get(0), answerGroups, parents); 385 386 } 387 388 private List<TypeRefComponent> expandTypeList(List<TypeRefComponent> types) { 389 List<TypeRefComponent> result = new ArrayList<TypeRefComponent>(); 390 for (TypeRefComponent t : types) { 391 if (t.hasProfile()) 392 result.add(t); 393 else if (t.getCode().equals("*")) { 394 result.add(new TypeRefComponent().setCode("integer")); 395 result.add(new TypeRefComponent().setCode("decimal")); 396 result.add(new TypeRefComponent().setCode("dateTime")); 397 result.add(new TypeRefComponent().setCode("date")); 398 result.add(new TypeRefComponent().setCode("instant")); 399 result.add(new TypeRefComponent().setCode("time")); 400 result.add(new TypeRefComponent().setCode("string")); 401 result.add(new TypeRefComponent().setCode("uri")); 402 result.add(new TypeRefComponent().setCode("boolean")); 403 result.add(new TypeRefComponent().setCode("Coding")); 404 result.add(new TypeRefComponent().setCode("CodeableConcept")); 405 result.add(new TypeRefComponent().setCode("Attachment")); 406 result.add(new TypeRefComponent().setCode("Identifier")); 407 result.add(new TypeRefComponent().setCode("Quantity")); 408 result.add(new TypeRefComponent().setCode("Range")); 409 result.add(new TypeRefComponent().setCode("Period")); 410 result.add(new TypeRefComponent().setCode("Ratio")); 411 result.add(new TypeRefComponent().setCode("HumanName")); 412 result.add(new TypeRefComponent().setCode("Address")); 413 result.add(new TypeRefComponent().setCode("ContactPoint")); 414 result.add(new TypeRefComponent().setCode("Timing")); 415 result.add(new TypeRefComponent().setCode("Reference")); 416 } else 417 result.add(t); 418 } 419 return result; 420 } 421 422 private ValueSet makeTypeList(StructureDefinition profile, List<TypeRefComponent> types, String path) { 423 ValueSet vs = new ValueSet(); 424 vs.setName("Type options for "+path); 425 vs.setDescription(vs.getName()); 426 vs.setStatus(PublicationStatus.ACTIVE); 427 vs.setExpansion(new ValueSetExpansionComponent()); 428 vs.getExpansion().setIdentifier(Factory.createUUID()); 429 vs.getExpansion().setTimestampElement(DateTimeType.now()); 430 for (TypeRefComponent t : types) { 431 if (t.hasTarget()) { 432 for (UriType u : t.getTargetProfile()) { 433 if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 434 ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains(); 435 cc.setCode(u.getValue().substring(40)); 436 cc.setSystem("http://hl7.org/fhir/resource-types"); 437 cc.setDisplay(cc.getCode()); 438 } 439 } 440 } else if (!t.hasProfile()) { 441 ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains(); 442 cc.setCode(t.getCode()); 443 cc.setDisplay(t.getCode()); 444 cc.setSystem("http://hl7.org/fhir/data-types"); 445 } else for (UriType u : t.getProfile()) { 446 ProfileUtilities pu = new ProfileUtilities(context, null, null); 447 StructureDefinition ps = pu.getProfile(profile, u.getValue()); 448 if (ps != null) { 449 ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains(); 450 cc.setCode(u.getValue()); 451 cc.setDisplay(ps.getType()); 452 cc.setSystem("http://hl7.org/fhir/resource-types"); 453 } 454 } 455 } 456 457 return vs; 458 } 459 460 private void selectTypes(StructureDefinition profile, QuestionnaireItemComponent sub, TypeRefComponent t, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> source, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> dest) throws FHIRFormatError { 461 List<QuestionnaireResponse.QuestionnaireResponseItemComponent> temp = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>(); 462 463 for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : source) 464 if (instanceOf(t, (Element) g.getUserData("object"))) 465 temp.add(g); 466 for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : temp) 467 source.remove(g); 468 for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : temp) { 469 // 1st the answer: 470 assert(g.getItem().size() == 0); // it should be empty 471 QuestionnaireResponse.QuestionnaireResponseItemComponent q = g.addItem(); 472 q.setLinkId(g.getLinkId()+"._type"); 473 q.setText("type"); 474 475 QuestionnaireResponseItemAnswerComponent a = q.addAnswer(); 476 if (t.hasTarget()) { 477 for (UriType u : t.getTargetProfile()) { 478 if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 479 Coding cc = new Coding(); 480 a.setValue(cc); 481 cc.setCode(u.getValue().substring(40)); 482 cc.setSystem("http://hl7.org/fhir/resource-types"); 483 } 484 } 485 } else { 486 Coding cc = new Coding(); 487 a.setValue(cc); 488 ProfileUtilities pu = new ProfileUtilities(context, null, null); 489 StructureDefinition ps = null; 490 if (t.hasProfile()) 491 ps = pu.getProfile(profile, t.getProfile().get(0).getValue()); 492 493 if (ps != null) { 494 cc.setCode(t.getProfile().get(0).getValue()); 495 cc.setSystem("http://hl7.org/fhir/resource-types"); 496 } else { 497 cc.setCode(t.getCode()); 498 cc.setSystem("http://hl7.org/fhir/data-types"); 499 } 500 } 501 502 // 1st: create the subgroup 503 QuestionnaireResponse.QuestionnaireResponseItemComponent subg = a.addItem(); 504 dest.add(subg); 505 subg.setLinkId(sub.getLinkId()); 506 subg.setText(sub.getText()); 507 subg.setUserData("object", g.getUserData("object")); 508 } 509 } 510 511 private boolean instanceOf(TypeRefComponent t, Element obj) { 512 if (t.getCode().equals("Reference")) { 513 if (!(obj instanceof Reference)) { 514 return false; 515 } else { 516 String url = ((Reference) obj).getReference(); 517 // there are several problems here around profile matching. This process is degenerative, and there's probably nothing we can do to solve it 518 if (url.startsWith("http:") || url.startsWith("https:")) 519 return true; 520 else if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) 521 return url.startsWith(t.getProfile().get(0).getValue().substring(40)+'/'); 522 else 523 return true; 524 } 525 } else if (t.getCode().equals("Quantity")) { 526 return obj instanceof Quantity; 527 } else 528 throw new NotImplementedException("Not Done Yet"); 529 } 530 531 private QuestionnaireItemComponent addQuestion(QuestionnaireItemComponent group, QuestionnaireItemType af, String path, String id, String name, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 532 return addQuestion(group, af, path, id, name, answerGroups, null); 533 } 534 535 private QuestionnaireItemComponent addQuestion(QuestionnaireItemComponent group, QuestionnaireItemType af, String path, String id, String name, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, ValueSet vs) throws FHIRException { 536 QuestionnaireItemComponent result = group.addItem(); 537 if (vs != null) { 538 if (vs.getExpansion() == null) { 539 result.setOptions(vs.getUrl()); 540 ToolingExtensions.addControl(result, "lookup"); 541 } else { 542 if (Utilities.noString(vs.getId())) { 543 vs.setId(nextId("vs")); 544 questionnaire.getContained().add(vs); 545 vsCache.put(vs.getUrl(), vs.getId()); 546 vs.setText(null); 547 vs.setCompose(null); 548 vs.getContact().clear(); 549 vs.setPublisherElement(null); 550 vs.setCopyrightElement(null); 551 } 552 result.setOptions("#"+vs.getId()); 553 } 554 } 555 556 result.setLinkId(path+'.'+id); 557 result.setText(name); 558 result.setType(af); 559 result.setRequired(false); 560 result.setRepeats(false); 561 if (id.endsWith("/1")) 562 id = id.substring(0, id.length()-2); 563 564 if (answerGroups != null) { 565 566 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) { 567 List<Base> children = new ArrayList<Base>(); 568 569 QuestionnaireResponse.QuestionnaireResponseItemComponent aq = null; 570 Element obj = (Element) ag.getUserData("object"); 571 if (isPrimitive((TypeRefComponent) obj)) 572 children.add(obj); 573 else if (obj instanceof Enumeration) { 574 String value = ((Enumeration) obj).toString(); 575 children.add(new StringType(value)); 576 } else 577 children = obj.listChildrenByName(id); 578 579 for (Base child: children) { 580 if (child != null) { 581 if (aq == null) { 582 aq = ag.addItem(); 583 aq.setLinkId(result.getLinkId()); 584 aq.setText(result.getText()); 585 } 586 aq.addAnswer().setValue(convertType(child, af, vs, result.getLinkId())); 587 } 588 } 589 } 590 } 591 return result; 592 } 593 594 @SuppressWarnings("unchecked") 595 private Type convertType(Base value, QuestionnaireItemType af, ValueSet vs, String path) throws FHIRException { 596 switch (af) { 597 // simple cases 598 case BOOLEAN: if (value instanceof BooleanType) return (Type) value; break; 599 case DECIMAL: if (value instanceof DecimalType) return (Type) value; break; 600 case INTEGER: if (value instanceof IntegerType) return (Type) value; break; 601 case DATE: if (value instanceof DateType) return (Type) value; break; 602 case DATETIME: if (value instanceof DateTimeType) return (Type) value; break; 603 case TIME: if (value instanceof TimeType) return (Type) value; break; 604 case STRING: 605 if (value instanceof StringType) 606 return (Type) value; 607 else if (value instanceof UriType) 608 return new StringType(((UriType) value).asStringValue()); 609 break; 610 case TEXT: if (value instanceof StringType) return (Type) value; break; 611 case QUANTITY: if (value instanceof Quantity) return (Type) value; break; 612 613 // complex cases: 614 // ? QuestionnaireItemTypeAttachment: ...? 615 case CHOICE: 616 case OPENCHOICE : 617 if (value instanceof Coding) 618 return (Type) value; 619 else if (value instanceof Enumeration) { 620 Coding cc = new Coding(); 621 cc.setCode(((Enumeration<Enum<?>>) value).asStringValue()); 622 cc.setSystem(getSystemForCode(vs, cc.getCode(), path)); 623 return cc; 624 } else if (value instanceof StringType) { 625 Coding cc = new Coding(); 626 cc.setCode(((StringType) value).asStringValue()); 627 cc.setSystem(getSystemForCode(vs, cc.getCode(), path)); 628 return cc; 629 } 630 break; 631 632 case REFERENCE: 633 if (value instanceof Reference) 634 return (Type) value; 635 else if (value instanceof StringType) { 636 Reference r = new Reference(); 637 r.setReference(((StringType) value).asStringValue()); 638 } 639 break; 640 default: 641 break; 642 } 643 644 throw new FHIRException("Unable to convert from '"+value.getClass().toString()+"' for Answer Format "+af.toCode()+", path = "+path); 645 } 646 647 private String getSystemForCode(ValueSet vs, String code, String path) throws FHIRException { 648// var 649// i, q : integer; 650// begin 651 String result = null; 652 if (vs == null) { 653 if (prebuiltQuestionnaire == null) 654 throw new FHIRException("Logic error at path = "+path); 655 for (Resource r : prebuiltQuestionnaire.getContained()) { 656 if (r instanceof ValueSet) { 657 vs = (ValueSet) r; 658 if (vs.hasExpansion()) { 659 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 660 if (c.getCode().equals(code)) { 661 if (result == null) 662 result = c.getSystem(); 663 else 664 throw new FHIRException("Multiple matches in "+vs.getUrl()+" for code "+code+" at path = "+path); 665 } 666 } 667 } 668 } 669 } 670 } 671 672 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 673 if (c.getCode().equals(code)) { 674 if (result == null) 675 result = c.getSystem(); 676 else 677 throw new FHIRException("Multiple matches in "+vs.getUrl()+" for code "+code+" at path = "+path); 678 } 679 } 680 if (result != null) 681 return result; 682 throw new FHIRException("Unable to resolve code "+code+" at path = "+path); 683 } 684 685 private boolean isPrimitive(TypeRefComponent t) { 686 String code = t.getCode(); 687 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code); 688 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 689 } 690 691 private void processDataType(StructureDefinition profile, QuestionnaireItemComponent group, ElementDefinition element, String path, TypeRefComponent t, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException { 692 if (t.getCode().equals("code")) 693 addCodeQuestions(group, element, path, answerGroups); 694 else if (Utilities.existsInList(t.getCode(), "string", "id", "oid", "uuid", "markdown")) 695 addStringQuestions(group, element, path, answerGroups); 696 else if (Utilities.existsInList(t.getCode(), "uri", "url", "canonical")) 697 addUriQuestions(group, element, path, answerGroups); 698 else if (t.getCode().equals("boolean")) 699 addBooleanQuestions(group, element, path, answerGroups); 700 else if (t.getCode().equals("decimal")) 701 addDecimalQuestions(group, element, path, answerGroups); 702 else if (t.getCode().equals("dateTime") || t.getCode().equals("date")) 703 addDateTimeQuestions(group, element, path, answerGroups); 704 else if (t.getCode().equals("instant")) 705 addInstantQuestions(group, element, path, answerGroups); 706 else if (t.getCode().equals("time")) 707 addTimeQuestions(group, element, path, answerGroups); 708 else if (t.getCode().equals("CodeableConcept")) 709 addCodeableConceptQuestions(group, element, path, answerGroups); 710 else if (t.getCode().equals("Period")) 711 addPeriodQuestions(group, element, path, answerGroups); 712 else if (t.getCode().equals("Ratio")) 713 addRatioQuestions(group, element, path, answerGroups); 714 else if (t.getCode().equals("HumanName")) 715 addHumanNameQuestions(group, element, path, answerGroups); 716 else if (t.getCode().equals("Address")) 717 addAddressQuestions(group, element, path, answerGroups); 718 else if (t.getCode().equals("ContactPoint")) 719 addContactPointQuestions(group, element, path, answerGroups); 720 else if (t.getCode().equals("Identifier")) 721 addIdentifierQuestions(group, element, path, answerGroups); 722 else if (t.getCode().equals("integer") || t.getCode().equals("positiveInt") || t.getCode().equals("unsignedInt") ) 723 addIntegerQuestions(group, element, path, answerGroups); 724 else if (t.getCode().equals("Coding")) 725 addCodingQuestions(group, element, path, answerGroups); 726 else if (Utilities.existsInList(t.getCode(), "Quantity", "Count", "Age", "Duration", "Distance", "Money")) 727 addQuantityQuestions(group, element, path, answerGroups); 728 else if (t.getCode().equals("Money")) 729 addMoneyQuestions(group, element, path, answerGroups); 730 else if (t.getCode().equals("Reference")) 731 addReferenceQuestions(group, element, path, t.getTargetProfile(), answerGroups); 732 else if (t.getCode().equals("Duration")) 733 addDurationQuestions(group, element, path, answerGroups); 734 else if (t.getCode().equals("base64Binary")) 735 addBinaryQuestions(group, element, path, answerGroups); 736 else if (t.getCode().equals("Attachment")) 737 addAttachmentQuestions(group, element, path, answerGroups); 738 else if (t.getCode().equals("Age")) 739 addAgeQuestions(group, element, path, answerGroups); 740 else if (t.getCode().equals("Range")) 741 addRangeQuestions(group, element, path, answerGroups); 742 else if (t.getCode().equals("Timing")) 743 addTimingQuestions(group, element, path, answerGroups); 744 else if (t.getCode().equals("Annotation")) 745 addAnnotationQuestions(group, element, path, answerGroups); 746 else if (t.getCode().equals("SampledData")) 747 addSampledDataQuestions(group, element, path, answerGroups); 748 else if (t.getCode().equals("Extension")) { 749 if (t.hasProfile()) 750 addExtensionQuestions(profile, group, element, path, t.getProfile().get(0).getValue(), answerGroups, parents); 751 } else if (t.getCode().equals("SampledData")) 752 addSampledDataQuestions(group, element, path, answerGroups); 753 else if (!t.getCode().equals("Narrative") && !t.getCode().equals("Resource") && !t.getCode().equals("Meta") && !t.getCode().equals("Signature")) { 754 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t.getCode()); 755 if (sd == null) 756 throw new NotImplementedException("Unhandled Data Type: "+t.getCode()+" on element "+element.getPath()); 757 buildGroup(group, sd, sd.getSnapshot().getElementFirstRep(), parents, answerGroups); 758 } 759 } 760 761 private void addCodeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 762 ToolingExtensions.addFhirType(group, "code"); 763 ValueSet vs = resolveValueSet(null, element.hasBinding() ? element.getBinding() : null); 764 addQuestion(group, QuestionnaireItemType.CHOICE, path, "value", unCamelCase(tail(element.getPath())), answerGroups, vs); 765 group.setText(null); 766 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 767 ag.setText(null); 768 } 769 770 private String unCamelCase(String s) { 771 StringBuilder result = new StringBuilder(); 772 773 for (int i = 0; i < s.length(); i++) { 774 if (Character.isUpperCase(s.charAt(i))) 775 result.append(' '); 776 result.append(s.charAt(i)); 777 } 778 return result.toString().toLowerCase(); 779 } 780 781 private void addStringQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 782 ToolingExtensions.addFhirType(group, "string"); 783 addQuestion(group, QuestionnaireItemType.STRING, path, "value", group.getText(), answerGroups); 784 group.setText(null); 785 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 786 ag.setText(null); 787 } 788 789 private void addTimeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 790 ToolingExtensions.addFhirType(group, "time"); 791 addQuestion(group, QuestionnaireItemType.TIME, path, "value", group.getText(), answerGroups); 792 group.setText(null); 793 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 794 ag.setText(null); 795 } 796 797 private void addUriQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 798 ToolingExtensions.addFhirType(group, "uri"); 799 addQuestion(group, QuestionnaireItemType.STRING, path, "value", group.getText(), answerGroups); 800 group.setText(null); 801 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 802 ag.setText(null); 803 } 804 805 private void addBooleanQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 806 ToolingExtensions.addFhirType(group, "boolean"); 807 addQuestion(group, QuestionnaireItemType.BOOLEAN, path, "value", group.getText(), answerGroups); 808 group.setText(null); 809 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 810 ag.setText(null); 811 } 812 813 private void addDecimalQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 814 ToolingExtensions.addFhirType(group, "decimal"); 815 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", group.getText(), answerGroups); 816 group.setText(null); 817 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 818 ag.setText(null); 819 } 820 821 private void addIntegerQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 822 ToolingExtensions.addFhirType(group, "integer"); 823 addQuestion(group, QuestionnaireItemType.INTEGER, path, "value", group.getText(), answerGroups); 824 group.setText(null); 825 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 826 ag.setText(null); 827 } 828 829 private void addDateTimeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 830 ToolingExtensions.addFhirType(group, "datetime"); 831 addQuestion(group, QuestionnaireItemType.DATETIME, path, "value", group.getText(), answerGroups); 832 group.setText(null); 833 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 834 ag.setText(null); 835 } 836 837 private void addInstantQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 838 ToolingExtensions.addFhirType(group, "instant"); 839 addQuestion(group, QuestionnaireItemType.DATETIME, path, "value", group.getText(), answerGroups); 840 group.setText(null); 841 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 842 ag.setText(null); 843 } 844 845 private void addBinaryQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) { 846 ToolingExtensions.addFhirType(group, "binary"); 847 // ? Lloyd: how to support binary content 848 } 849 850 // Complex Types --------------------------------------------------------------- 851 852 private QuestionnaireItemType answerTypeForBinding(ElementDefinitionBindingComponent binding) { 853 if (binding == null) 854 return QuestionnaireItemType.OPENCHOICE; 855 else if (binding.getStrength() != BindingStrength.REQUIRED) 856 return QuestionnaireItemType.OPENCHOICE; 857 else 858 return QuestionnaireItemType.CHOICE; 859 } 860 861 private void addCodingQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 862 ToolingExtensions.addFhirType(group, "Coding"); 863 addQuestion(group, answerTypeForBinding(element.hasBinding() ? element.getBinding() : null), path, "value", group.getText(), answerGroups, resolveValueSet(null, element.hasBinding() ? element.getBinding() : null)); 864 group.setText(null); 865 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 866 ag.setText(null); 867 } 868 869 private void addCodeableConceptQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 870 ToolingExtensions.addFhirType(group, "CodeableConcept"); 871 addQuestion(group, answerTypeForBinding(element.hasBinding() ? element.getBinding() : null), path, "coding", "code:", answerGroups, resolveValueSet(null, element.hasBinding() ? element.getBinding() : null)); 872 addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups); 873 } 874 875 private ValueSet makeAnyValueSet() { 876 // TODO Auto-generated method stub 877 return null; 878 } 879 880 private void addPeriodQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 881 ToolingExtensions.addFhirType(group, "Period"); 882 addQuestion(group, QuestionnaireItemType.DATETIME, path, "low", "start:", answerGroups); 883 addQuestion(group, QuestionnaireItemType.DATETIME, path, "end", "end:", answerGroups); 884 } 885 886 private void addRatioQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 887 ToolingExtensions.addFhirType(group, "Ratio"); 888 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "numerator", "numerator:", answerGroups); 889 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "denominator", "denominator:", answerGroups); 890 addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups); 891 } 892 893 private void addHumanNameQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 894 ToolingExtensions.addFhirType(group, "Name"); 895 addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups); 896 addQuestion(group, QuestionnaireItemType.STRING, path, "family", "family:", answerGroups).setRepeats(true); 897 addQuestion(group, QuestionnaireItemType.STRING, path, "given", "given:", answerGroups).setRepeats(true); 898 } 899 900 private void addAddressQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 901 ToolingExtensions.addFhirType(group, "Address"); 902 addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups); 903 addQuestion(group, QuestionnaireItemType.STRING, path, "line", "line:", answerGroups).setRepeats(true); 904 addQuestion(group, QuestionnaireItemType.STRING, path, "city", "city:", answerGroups); 905 addQuestion(group, QuestionnaireItemType.STRING, path, "state", "state:", answerGroups); 906 addQuestion(group, QuestionnaireItemType.STRING, path, "postalCode", "post code:", answerGroups); 907 addQuestion(group, QuestionnaireItemType.STRING, path, "country", "country:", answerGroups); 908 addQuestion(group, QuestionnaireItemType.CHOICE, path, "use", "use:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/address-use")); 909 } 910 911 private void addContactPointQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 912 ToolingExtensions.addFhirType(group, "ContactPoint"); 913 addQuestion(group, QuestionnaireItemType.CHOICE, path, "system", "type:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/contact-point-system")); 914 addQuestion(group, QuestionnaireItemType.STRING, path, "value", "value:", answerGroups); 915 addQuestion(group, QuestionnaireItemType.CHOICE, path, "use", "use:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/contact-point-use")); 916 } 917 918 private void addIdentifierQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 919 ToolingExtensions.addFhirType(group, "Identifier"); 920 addQuestion(group, QuestionnaireItemType.STRING, path, "label", "label:", answerGroups); 921 addQuestion(group, QuestionnaireItemType.STRING, path, "system", "system:", answerGroups); 922 addQuestion(group, QuestionnaireItemType.STRING, path, "value", "value:", answerGroups); 923 } 924 925 private void addSimpleQuantityQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 926 ToolingExtensions.addFhirType(group, "Quantity"); 927 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups); 928 addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups); 929 addQuestion(group, QuestionnaireItemType.STRING, path, "code", "coded units:", answerGroups); 930 addQuestion(group, QuestionnaireItemType.STRING, path, "system", "units system:", answerGroups); 931 } 932 933 private void addQuantityQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 934 ToolingExtensions.addFhirType(group, "Quantity"); 935 addQuestion(group, QuestionnaireItemType.CHOICE, path, "comparator", "comp:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/quantity-comparator")); 936 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups); 937 addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups); 938 addQuestion(group, QuestionnaireItemType.STRING, path, "code", "coded units:", answerGroups); 939 addQuestion(group, QuestionnaireItemType.STRING, path, "system", "units system:", answerGroups); 940 } 941 942 private void addMoneyQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 943 ToolingExtensions.addFhirType(group, "Money"); 944 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups); 945 addQuestion(group, QuestionnaireItemType.STRING, path, "currency", "currency:", answerGroups); 946 } 947 948 private void addAgeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 949 ToolingExtensions.addFhirType(group, "Age"); 950 addQuestion(group, QuestionnaireItemType.CHOICE, path, "comparator", "comp:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/quantity-comparator")); 951 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups); 952 addQuestion(group, QuestionnaireItemType.CHOICE, path, "units", "units:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/duration-units")); 953 } 954 955 private void addDurationQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 956 ToolingExtensions.addFhirType(group, "Duration"); 957 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups); 958 addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups); 959 } 960 961 private void addAttachmentQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) { 962 ToolingExtensions.addFhirType(group, "Attachment"); 963 // raise Exception.Create("addAttachmentQuestions not Done Yet"); 964 } 965 966 private void addRangeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 967 ToolingExtensions.addFhirType(group, "Range"); 968 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "low", "low:", answerGroups); 969 addQuestion(group, QuestionnaireItemType.DECIMAL, path, "high", "high:", answerGroups); 970 addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups); 971 } 972 973 private void addSampledDataQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) { 974 ToolingExtensions.addFhirType(group, "SampledData"); 975 } 976 977 private void addTimingQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 978 ToolingExtensions.addFhirType(group, "Schedule"); 979 addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups); 980 addQuestion(group, QuestionnaireItemType.DATETIME, path, "date", "date:", answerGroups); 981 QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.REFERENCE, path, "author", "author:", answerGroups); 982 ToolingExtensions.addAllowedResource(q, "Patient"); 983 ToolingExtensions.addAllowedResource(q, "Practitioner"); 984 ToolingExtensions.addAllowedResource(q, "RelatedPerson"); 985 } 986 987 private void addAnnotationQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) { 988 ToolingExtensions.addFhirType(group, "Annotation"); 989 } 990 // Special Types --------------------------------------------------------------- 991 992 private void addReferenceQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<CanonicalType> profileURL, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException { 993 // var 994 // rn : String; 995 // i : integer; 996 // q : TFhirQuestionnaireGroupQuestion; 997 ToolingExtensions.addFhirType(group, "Reference"); 998 999 QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.REFERENCE, path, "value", group.getText(), answerGroups); 1000 group.setText(null); 1001 CommaSeparatedStringBuilder rn = new CommaSeparatedStringBuilder(); 1002 for (UriType u : profileURL) 1003 if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) 1004 rn.append(u.getValue().substring(40)); 1005 if (rn.length() == 0) 1006 ToolingExtensions.addReferenceFilter(q, "subject=$subj&patient=$subj&encounter=$encounter"); 1007 else { 1008 ToolingExtensions.addAllowedResource(q, rn.toString()); 1009 ToolingExtensions.addReferenceFilter(q, "subject=$subj&patient=$subj&encounter=$encounter"); 1010 } 1011 for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) 1012 ag.setText(null); 1013 } 1014 1015 1016 private void addExtensionQuestions(StructureDefinition profile, QuestionnaireItemComponent group, ElementDefinition element, String path, String url, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException { 1017 // if this a profiled extension, then we add it 1018 if (!Utilities.noString(url)) { 1019 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 1020 if (ed != null) { 1021 if (answerGroups.size() > 0) 1022 throw new NotImplementedException("Debug this"); 1023 buildQuestion(group, profile, ed.getSnapshot().getElement().get(0), path+".extension["+url+"]", answerGroups, parents); 1024 } 1025 } 1026 } 1027 1028 private ValueSet resolveValueSet(String url) { 1029// if (prebuiltQuestionnaire != null) 1030 return null; // we don't do anything with value sets in this case 1031 1032// if (vsCache.containsKey(url)) 1033// return (ValueSet) questionnaire.getContained(vsCache.get(url)); 1034// else { 1035// ValueSet vs = context.findValueSet(url); 1036// if (vs != null) 1037// return expander.expand(vs, MaxListboxCodings, false); 1038// } 1039// 1040// /* on e: ETooCostly do 1041// begin 1042// result := TFhirValueSet.Create; 1043// try 1044// result.identifierST := ref.referenceST; 1045// result.link; 1046// finally 1047// result.Free; 1048// end; 1049// end; 1050// on e : Exception do 1051// raise; 1052// end;*/ 1053// } 1054 } 1055 1056 private ValueSet resolveValueSet(Object object, ElementDefinitionBindingComponent binding) { 1057 return null; 1058 } 1059 1060}