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