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