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