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