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