001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.exceptions.FHIRFormatError;
013import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
014import org.hl7.fhir.r5.model.Base;
015import org.hl7.fhir.r5.model.CodeSystem;
016import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
017import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
018import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
019import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
020import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
021import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer;
022import org.hl7.fhir.r5.renderers.CodeSystemRenderer;
023import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
024import org.hl7.fhir.r5.renderers.utils.RenderingContext;
025import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
026import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
027import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
028import org.hl7.fhir.r5.utils.EOperationOutcome;
029import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
030import org.hl7.fhir.utilities.Utilities;
031import org.hl7.fhir.utilities.i18n.RenderingI18nContext;
032import org.hl7.fhir.utilities.validation.ValidationMessage;
033import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
034import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
035import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
036import org.hl7.fhir.utilities.validation.ValidationOptions;
037import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
038import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
039import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
040import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
041import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
042import org.hl7.fhir.utilities.xhtml.XhtmlNode;
043
044@MarkedToMoveToAdjunctPackage
045public class CodeSystemComparer extends CanonicalResourceComparer {
046
047
048  public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
049
050    private StructuralMatch<PropertyComponent> properties = new StructuralMatch<PropertyComponent>(); 
051    private StructuralMatch<CodeSystemFilterComponent> filters = new StructuralMatch<CodeSystemFilterComponent>();                                             
052    private StructuralMatch<ConceptDefinitionComponent> combined;                                             
053    private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
054    public CodeSystemComparison(CodeSystem left, CodeSystem right) {
055      super(left, right);
056      combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
057    }
058
059    public Map<String, String> getPropMap() {
060      return propMap;
061    }
062
063    public StructuralMatch<ConceptDefinitionComponent> getCombined() {
064      return combined;
065    }
066
067    public StructuralMatch<PropertyComponent> getProperties() {
068      return properties;
069    }
070
071    public StructuralMatch<CodeSystemFilterComponent> getFilters() {
072      return filters;
073    }
074
075    @Override
076    protected String abbreviation() {
077      return "cs";
078    }
079
080    @Override
081    protected String summary() {
082      String res = "CodeSystem: "+left.present()+" vs "+right.present();
083      String ch = changeSummary();
084      if (ch != null) {
085        res = res + ". "+ch;
086      }
087      return res;
088    }
089
090
091    @Override
092    protected String fhirType() {
093      return "CodeSystem";
094    }
095
096    @Override
097    protected void countMessages(MessageCounts cnts) {
098      super.countMessages(cnts);
099      combined.countMessages(cnts);
100    }
101
102  }
103
104  private CodeSystem right;
105
106  public CodeSystemComparer(ComparisonSession session) {
107    super(session);
108  }
109
110  public CodeSystemComparison compare(CodeSystem left, CodeSystem right) {    
111    if (left == null)
112      throw new DefinitionException("No CodeSystem provided (left)");
113    if (right == null)
114      throw new DefinitionException("No CodeSystem provided (right)");
115    
116    CodeSystemComparison res = new CodeSystemComparison(left, right);
117    session.identify(res);
118    CodeSystem cs = new CodeSystem();
119    res.setUnion(cs);
120    session.identify(cs);
121    cs.setName("Union"+left.getName()+"And"+right.getName());
122    cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
123    cs.setStatus(left.getStatus());
124    cs.setDate(new Date());
125    for (PropertyComponent pL : left.getProperty()) {
126      cs.addProperty(pL.copy());
127    }
128    for (PropertyComponent pR : left.getProperty()) {
129      PropertyComponent pL = findProperty(left, pR);
130      if (pL == null) {
131        String code = getUniqued(pR.getCode(), cs.getProperty());
132        cs.addProperty(pR.copy().setCode(code));
133      } else {
134        res.getPropMap().put(pR.getCode(), pL.getCode());
135      }
136    }
137
138    CodeSystem cs1 = new CodeSystem();
139    res.setIntersection(cs1);
140    session.identify(cs1);
141    cs1.setName("Intersection"+left.getName()+"And"+right.getName());
142    cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
143    cs1.setStatus(left.getStatus());
144    cs1.setDate(new Date());
145    cs1.getProperty().addAll(cs.getProperty());
146
147
148    List<String> chMetadata = new ArrayList<>();
149    boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
150    if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) {
151      ch = true;
152      chMetadata.add("versionNeeded");
153    }
154    if (comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
155      ch = true;
156      chMetadata.add("compositional");
157    }
158    res.updatedMetadataState(ch, chMetadata);
159    ch = false;
160    ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
161    ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
162    ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right);
163    
164    ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch;
165    ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch;
166    ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch;
167    res.updateDefinitionsState(ch);
168
169    session.annotate(right, res);
170    return res;
171  }
172
173  private String getUniqued(String code, List<PropertyComponent> list) {
174    int i = 0;
175    while (true) {
176      boolean ok = true;
177      String res = code+(i == 0 ? "" : i);
178      for (PropertyComponent t : list) {
179        if (res.equals(t.getCode())) {
180          ok = false;
181        }
182      }
183      if (ok) {
184        return res;
185      }
186    }
187  }
188
189  private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) {
190    for (PropertyComponent t : left.getProperty()) {
191      if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) {
192        return t;
193      } else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) {
194        return t;
195      }
196    }
197    return null;
198  }
199
200  private boolean compareProperties(List<PropertyComponent> left, List<PropertyComponent> right, StructuralMatch<PropertyComponent> combined,
201    List<PropertyComponent> union, List<PropertyComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
202    boolean def = false;
203    List<PropertyComponent> matchR = new ArrayList<>();
204    for (PropertyComponent l : left) {
205      PropertyComponent r = findInList(right, l);
206      if (r == null) {
207        union.add(l);
208        res.updateContentState(true);
209        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
210        session.markDeleted(parent, "concept", l);
211      } else {
212        matchR.add(r);
213        PropertyComponent cdM = merge(l, r, res);
214        PropertyComponent cdI = intersect(l, r, res);
215        union.add(cdM);
216        intersection.add(cdI);
217        StructuralMatch<PropertyComponent> sm = new StructuralMatch<CodeSystem.PropertyComponent>(l, r);
218        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
219          def = true;
220        }
221        combined.getChildren().add(sm);
222      }
223    }
224    for (PropertyComponent r : right) {
225      if (!matchR.contains(r)) {
226        union.add(r);
227        res.updateContentState(true);
228        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
229        session.markAdded(r);     
230      }
231    }
232    return def;
233  }
234
235
236  private boolean compareFilters(List<CodeSystemFilterComponent> left, List<CodeSystemFilterComponent> right, StructuralMatch<CodeSystemFilterComponent> combined,
237    List<CodeSystemFilterComponent> union, List<CodeSystemFilterComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
238    boolean def = false;
239    List<CodeSystemFilterComponent> matchR = new ArrayList<>();
240    for (CodeSystemFilterComponent l : left) {
241      CodeSystemFilterComponent r = findInList(right, l);
242      if (r == null) {
243        union.add(l);
244        res.updateContentState(true);
245        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
246        session.markDeleted(parent, "concept", l);
247      } else {
248        matchR.add(r);
249        CodeSystemFilterComponent cdM = merge(l, r, res);
250        CodeSystemFilterComponent cdI = intersect(l, r, res);
251        union.add(cdM);
252        intersection.add(cdI);
253        StructuralMatch<CodeSystemFilterComponent> sm = new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, r);
254        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
255          def = true;
256        }
257        combined.getChildren().add(sm);
258      }
259    }
260    for (CodeSystemFilterComponent r : right) {
261      if (!matchR.contains(r)) {
262        union.add(r);
263        res.updateContentState(true);
264        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
265        session.markAdded(r);     
266      }
267    }
268    return def;
269  }
270
271  
272  private boolean compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
273    List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
274    boolean def = false;
275    List<ConceptDefinitionComponent> matchR = new ArrayList<>();
276    for (ConceptDefinitionComponent l : left) {
277      ConceptDefinitionComponent r = findInList(right, l);
278      if (r == null) {
279        union.add(l);
280        res.updateContentState(true);
281        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
282        session.markDeleted(parent, "concept", l);
283      } else {
284        matchR.add(r);
285        ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
286        ConceptDefinitionComponent cdI = intersect(l, r, res);
287        union.add(cdM);
288        intersection.add(cdI);
289        StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
290        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
291          def = true;
292        }
293        combined.getChildren().add(sm);
294        if (compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept", r)) {
295          def = true;
296        }
297      }
298    }
299    for (ConceptDefinitionComponent r : right) {
300      if (!matchR.contains(r)) {
301        union.add(r);
302        res.updateContentState(true);
303        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
304        session.markAdded(r);     
305      }
306    }
307    return def;
308  }
309  
310  private CodeSystemFilterComponent findInList(List<CodeSystemFilterComponent> list, CodeSystemFilterComponent item) {
311    for (CodeSystemFilterComponent t : list) {
312      if (t.getCode().equals(item.getCode())) {
313        return t;
314      }
315    }
316    return null;
317  }
318
319
320  private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
321    for (ConceptDefinitionComponent t : list) {
322      if (t.getCode().equals(item.getCode())) {
323        return t;
324      }
325    }
326    return null;
327  }
328
329
330  private PropertyComponent findInList(List<PropertyComponent> list, PropertyComponent item) {
331    for (PropertyComponent t : list) {
332      if (t.getCode().equals(item.getCode())) {
333        return t;
334      }
335    }
336    return null;
337  }
338
339  private boolean compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res, Base parent) {
340    boolean result = false;
341    result = compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res, parent, l.getDisplayElement(), r.getDisplayElement()) || result;
342    result = compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res, parent, l.getDefinitionElement(), r.getDefinitionElement()) || result;
343    // todo: designations, properties
344    return result;
345  }
346
347  private boolean compare(List<ValidationMessage> msgs, PropertyComponent l, PropertyComponent r, String path, CodeSystemComparison res, Base parent) {
348    boolean result = false;
349    result = compareStrings(path, msgs, l.getUri(), r.getUri(), "uri", IssueSeverity.WARNING, res, parent, l.getUriElement(), r.getUriElement()) || result;
350    result = compareStrings(path, msgs, l.hasType() ? l.getType().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
351    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
352    return result;
353  }
354
355  private boolean compare(List<ValidationMessage> msgs, CodeSystemFilterComponent l, CodeSystemFilterComponent r, String path, CodeSystemComparison res, Base parent) {
356    boolean result = false;
357    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
358// todo: repeating
359//    result = compareStrings(path, msgs, l.hasOperator() ? l.getOperator().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
360    result = compareStrings(path, msgs, l.getValue(), r.getValue(), "value", IssueSeverity.WARNING, res, parent, l.getValueElement(), r.getValueElement()) || result;
361    return result;
362  }
363
364  private boolean compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res, Base parent, Base l, Base r) {
365    if (!Utilities.noString(right)) {
366      if (Utilities.noString(left)) {
367        msgs.add(vmI(level, "Value for "+name+" added", path));   
368        session.markAdded(r);     
369        return true;
370      } else if (!left.equals(right)) {
371        if (level != IssueSeverity.NULL) {
372          res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
373        }
374        msgs.add(vmI(level, name+" changed from left to right", path));
375        session.markChanged(r, l);   
376        return true;
377      }
378    } else if (!Utilities.noString(left)) {
379      msgs.add(vmI(level, "Value for "+name+" removed", path));
380      session.markDeleted(parent, "concept", l);
381      return true;
382    }
383    return false;
384  }
385
386  private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
387    ConceptDefinitionComponent cd = l.copy();
388    if (!l.hasDisplay() && r.hasDisplay()) {
389      cd.setDisplay(r.getDisplay());
390    }
391    if (!l.hasDefinition() && r.hasDefinition()) {
392      cd.setDefinition(r.getDefinition());
393    }
394    mergeProps(cd, l, r, destProps, res);
395    mergeDesignations(cd, l, r);
396    return cd;
397  }
398
399  private PropertyComponent merge(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
400    PropertyComponent cd = l.copy();
401    if (!l.hasDescription() && r.hasDescription()) {
402      cd.setDescription(r.getDescription());
403    }
404    return cd;
405  }
406
407
408  private CodeSystemFilterComponent merge(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
409    CodeSystemFilterComponent cd = l.copy();
410    if (!l.hasDescription() && r.hasDescription()) {
411      cd.setDescription(r.getDescription());
412    }
413    return cd;
414  }
415
416  private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
417    ConceptDefinitionComponent cd = l.copy();
418    if (l.hasDisplay() && !r.hasDisplay()) {
419      cd.setDisplay(null);
420    }
421    if (l.hasDefinition() && !r.hasDefinition()) {
422      cd.setDefinition(null);
423    }
424    intersectProps(cd, l, r, res);
425    //    mergeDesignations(cd, l, r);
426    return cd;
427  }
428
429  private PropertyComponent intersect(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
430    PropertyComponent cd = l.copy();
431    if (l.hasDescription() && !r.hasDescription()) {
432      cd.setDescription(null);
433    }
434    return cd;
435  }
436
437  private CodeSystemFilterComponent intersect(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
438    CodeSystemFilterComponent cd = l.copy();
439    if (l.hasDescription() && !r.hasDescription()) {
440      cd.setDescription(null);
441    }
442    return cd;
443  }
444
445  private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) {
446    for (ConceptDefinitionDesignationComponent td : l.getDesignation()) {
447      if (hasDesignation(td, r.getDesignation())) {
448        cd.getDesignation().add(td);
449      }
450    }
451    for (ConceptDefinitionDesignationComponent td : r.getDesignation()) {
452      if (hasDesignation(td, l.getDesignation())) {
453        cd.getDesignation().add(td);
454      }
455    }
456  }
457
458  private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) {
459    for (ConceptDefinitionDesignationComponent t : designation) {
460      if (designationsMatch(td, t)) {
461        return true;
462      }
463    }
464    return false;
465  }
466
467  private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) {
468    if (l.hasUse() != r.hasUse()) {
469      return false;
470    }
471    if (l.hasLanguage() != r.hasLanguage()) {
472      return false;
473    }
474    if (l.hasValue() != r.hasValue()) {
475      return false;
476    }
477    if (l.hasUse()) {
478      if (l.getUse().equalsDeep(r.getUse())) {
479        return false;
480      }
481    }
482    if (l.hasLanguage()) {
483      if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) {
484        return false;
485      }
486    }
487    if (l.hasValue()) {
488      if (l.getValueElement().equalsDeep(r.getValueElement())) {
489        return false;
490      }
491    }
492    return true;
493  }
494
495  private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
496    List<ConceptPropertyComponent> matchR = new ArrayList<>();
497    for (ConceptPropertyComponent lp : l.getProperty()) {
498      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
499      if (rp == null) {
500        cd.getProperty().add(lp);
501      } else {
502        matchR.add(rp);
503        cd.getProperty().add(lp);
504        if (lp.getValue().equalsDeep(rp.getValue())) {
505          cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
506        }
507      }
508    }
509    for (ConceptPropertyComponent rp : r.getProperty()) {
510      if (!matchR.contains(rp)) {
511        cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));        
512      }
513    }
514  }
515
516  private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
517    for (ConceptPropertyComponent lp : l.getProperty()) {
518      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
519      if (rp != null) {
520        cd.getProperty().add(lp);
521      }
522    }
523  }
524
525  private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) {
526    for (ConceptPropertyComponent p : rightProperties) {
527      String rp = res.getPropMap().get(p.getCode());
528      if (rp != null && rp.equals(lp.getCode())) {
529        return p;
530      }
531    }
532    return null;
533  }
534
535  public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
536    // columns: code, display (left|right), properties (left|right)
537    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
538    TableModel model = gen.new TableModel(id, true);
539    model.setAlternating(true);
540    model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));
541    model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
542    for (PropertyComponent p : comparison.getUnion().getProperty()) {
543      model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2));
544    }
545    model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
546    for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) {
547      addRow(gen, model.getRows(), t, comparison);
548    }
549    return gen.generate(model, prefix, 0, null);
550  }
551
552  private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) {
553    Row r = gen.new Row();
554    rows.add(r);
555    r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
556    if (t.hasLeft() && t.hasRight()) {
557      if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
558        if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
559          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
560        } else {
561          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));        
562          r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
563        }
564      } else if (t.getLeft().hasDisplay()) {
565        r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));        
566        r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
567      } else if (t.getRight().hasDisplay()) {        
568        r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
569        r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));        
570      } else {
571        r.getCells().add(missingCell(gen).span(2));
572      }
573      for (PropertyComponent p : comparison.getUnion().getProperty()) {
574        ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison);
575        ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison);
576
577        if (lp != null && rp != null) {
578          if (lp.getValue().equals(rp.getValue())) {
579            r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
580          } else {
581            r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
582            r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
583          }
584        } else if (lp != null) {
585          r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
586          r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
587        } else if (rp != null) {        
588          r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
589          r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));        
590        } else {
591          r.getCells().add(missingCell(gen).span(2));
592        }
593
594      }
595    } else if (t.hasLeft()) {
596      r.setColor(COLOR_NO_ROW_RIGHT);
597      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
598      r.getCells().add(missingCell(gen));
599      for (PropertyComponent p : comparison.getUnion().getProperty()) {
600        r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison));
601        r.getCells().add(missingCell(gen));
602      }
603    } else {
604      r.setColor(COLOR_NO_ROW_LEFT);
605      r.getCells().add(missingCell(gen));
606      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
607      for (PropertyComponent p : comparison.getUnion().getProperty()) {
608        r.getCells().add(missingCell(gen));
609        r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison));
610      }
611    }
612    r.getCells().add(cellForMessages(gen, t.getMessages()));
613  }
614
615  private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
616    ConceptPropertyComponent cp = getProp(cd, p, right, comp);
617    if (cp == null) {
618      return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT);
619    } else {
620      return gen.new Cell(null, null, cp.getValue().toString(), null, null);
621    }
622  }
623
624  public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
625    String c = p.getCode();
626    if (right) {
627      c = comp.getPropMap().get(c);
628    }
629    ConceptPropertyComponent cp = null;
630    if (cd != null) {
631      for (ConceptPropertyComponent t : cd.getProperty()) {
632        if (t.hasCode() && t.getCode().equals(c)) {
633          cp = t;
634        }
635      }
636    }
637    return cp;
638  }
639
640  @Override
641  protected String fhirType() {
642    return "CodeSystem";
643  }
644
645  public XhtmlNode renderUnion(CodeSystemComparison comp, String id, String prefix, String corePath) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
646    CodeSystemRenderer csr = new CodeSystemRenderer(new RenderingContext(session.getContextLeft(), null, new ValidationOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER));
647    return csr.buildNarrative(ResourceWrapper.forResource(csr.getContext(), comp.union));
648  }
649
650  public XhtmlNode renderIntersection(CodeSystemComparison comp, String id, String prefix, String corePath) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
651    CodeSystemRenderer csr = new CodeSystemRenderer(new RenderingContext(session.getContextLeft(), null, new ValidationOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER));
652    return csr.buildNarrative(ResourceWrapper.forResource(csr.getContext(), comp.intersection));
653  }
654
655}