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