001package org.hl7.fhir.r5.renderers.spreadsheets;
002
003import java.io.IOException;
004import java.io.OutputStream;
005
006import org.hl7.fhir.exceptions.DefinitionException;
007import org.hl7.fhir.r5.model.ElementDefinition;
008import org.hl7.fhir.r5.model.StructureDefinition;
009import org.hl7.fhir.utilities.i18n.I18nConstants;
010
011
012import java.io.ByteArrayOutputStream;
013import java.io.IOException;
014import java.io.OutputStream;
015import java.io.UnsupportedEncodingException;
016import java.util.ArrayList;
017import java.util.Enumeration;
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.poi.ss.usermodel.BorderStyle;
023import org.apache.poi.ss.usermodel.Cell;
024import org.apache.poi.ss.usermodel.CellStyle;
025import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
026import org.apache.poi.ss.usermodel.FillPatternType;
027import org.apache.poi.ss.usermodel.Font;
028import org.apache.poi.ss.usermodel.FontFormatting;
029import org.apache.poi.ss.usermodel.IndexedColors;
030import org.apache.poi.ss.usermodel.PatternFormatting;
031import org.apache.poi.ss.usermodel.Row;
032import org.apache.poi.ss.usermodel.Sheet;
033import org.apache.poi.ss.usermodel.SheetConditionalFormatting;
034import org.apache.poi.ss.usermodel.VerticalAlignment;
035import org.apache.poi.ss.usermodel.Workbook;
036import org.apache.poi.ss.util.CellAddress;
037import org.apache.poi.ss.util.CellRangeAddress;
038import org.apache.poi.xssf.usermodel.XSSFRow;
039import org.apache.poi.xssf.usermodel.XSSFSheet;
040import org.apache.poi.xssf.usermodel.XSSFWorkbook;
041import org.hl7.fhir.r5.formats.IParser.OutputStyle;
042import org.hl7.fhir.r5.context.IWorkerContext;
043import org.hl7.fhir.r5.context.SimpleWorkerContext;
044import org.hl7.fhir.r5.formats.JsonParser;
045import org.hl7.fhir.r5.formats.XmlParser;
046import org.hl7.fhir.r5.model.CanonicalType;
047import org.hl7.fhir.r5.model.Coding;
048import org.hl7.fhir.r5.model.DataType;
049import org.hl7.fhir.r5.model.ElementDefinition;
050import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
051import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
052import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
053import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
054import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
055import org.hl7.fhir.r5.model.IdType;
056import org.hl7.fhir.r5.model.StringType;
057import org.hl7.fhir.r5.model.StructureDefinition;
058import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
059import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
060import org.hl7.fhir.r5.model.UriType;
061import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
062import org.hl7.fhir.utilities.TextStreamWriter;
063import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter;
064import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilter;
065import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCustomFilters;
066import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilterColumn;
067import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFilters;
068import org.openxmlformats.schemas.spreadsheetml.x2006.main.STFilterOperator;
069
070
071public class StructureDefinitionSpreadsheetGenerator extends CanonicalSpreadsheetGenerator {
072  private XmlParser xml = new XmlParser();
073  private JsonParser json = new JsonParser();
074  private boolean asXml;
075  private boolean hideMustSupportFalse;
076  private List<StructureDefinitionMappingComponent> mapKeys = new ArrayList<StructureDefinitionMappingComponent>();
077
078  private static String[] titles = {
079      "Path", "Slice Name", "Alias(s)", "Label", "Min", "Max", "Must Support?", "Is Modifier?", "Is Summary?", "Type(s)", "Short", 
080      "Definition", "Comments", "Requirements", "Default Value", "Meaning When Missing", "Fixed Value", "Pattern", "Example",
081      "Minimum Value", "Maximum Value", "Maximum Length", "Binding Strength", "Binding Description", "Binding Value Set", "Code",
082      "Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max",
083      "Condition(s)", "Constraint(s)"};
084
085  public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean valuesAsXml, boolean hideMustSupportFalse) {
086    super(context);
087    this.asXml = valuesAsXml;
088    this.hideMustSupportFalse = hideMustSupportFalse;
089  }
090
091  public StructureDefinitionSpreadsheetGenerator renderStructureDefinition(StructureDefinition sd) throws Exception {
092    if (sd == null) {
093      System.out.println("no structure!");
094    }
095    if (!sd.hasSnapshot()) {
096      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
097    }
098    addStructureDefinitionMetadata(renderCanonicalResource(sd), sd);
099    Sheet sheet = makeSheet("Elements");
100
101    Row headerRow = sheet.createRow(0);
102    for (int i = 0; i < titles.length; i++) {
103      addCell(headerRow, i, titles[i], styles.get("header"));
104    }
105    int i = titles.length - 1;
106    for (StructureDefinitionMappingComponent map : sd.getMapping()) {
107      i++;
108      addCell(headerRow, i, "Mapping: " + map.getName(), styles.get("header"));
109    }    
110
111    for (ElementDefinition child : sd.getSnapshot().getElement()) {
112      processElement(sheet, sd, child);
113    }
114    configureSheet(sheet, sd);
115    return this;
116  }
117
118
119  private void addStructureDefinitionMetadata(Sheet sheet, StructureDefinition sd) {
120    for (Coding k : sd.getKeyword()) {
121      addMetadataRow(sheet, "Keyword", dr.display(k));
122    }
123    addMetadataRow(sheet, "FHIR Version", sd.getFhirVersionElement().asStringValue());
124    addMetadataRow(sheet, "Kind", sd.getKindElement().asStringValue());
125    addMetadataRow(sheet, "Type", sd.getType());
126    addMetadataRow(sheet, "Base Definition", sd.getBaseDefinition());
127    addMetadataRow(sheet, "Abstract", sd.getAbstractElement().asStringValue());
128    addMetadataRow(sheet, "Derivation", sd.getDerivationElement().asStringValue());
129
130    for (StructureDefinitionContextComponent k : sd.getContext()) {
131      addMetadataRow(sheet, "Context", k.getTypeElement().asStringValue()+":"+k.getExpression());
132    }
133    for (StringType k : sd.getContextInvariant()) {
134      addMetadataRow(sheet, "Context Inv.", k.getValue());
135    }
136    
137  }
138
139  public void processElement(Sheet sheet, StructureDefinition sd, ElementDefinition ed) throws Exception {
140    Row row = sheet.createRow(sheet.getLastRowNum()+1);
141    int i = 0;
142    addCell(row, i++, ed.getPath(), styles.get("body"));
143    addCell(row, i++, ed.getSliceName());
144    addCell(row, i++, itemList(ed.getAlias()));
145    addCell(row, i++, ed.getLabel());
146    addCell(row, i++, ed.getMin());
147    addCell(row, i++, ed.getMax());
148    addCell(row, i++, ed.getMustSupport() ? "Y" : "");
149    addCell(row, i++, ed.getIsModifier() ? "Y" : "");
150    addCell(row, i++, ed.getIsSummary() ? "Y" : "");
151    addCell(row, i++, itemList(ed.getType()));
152    addCell(row, i++, ed.getShort());
153    addCell(row, i++, ed.getDefinition());
154    addCell(row, i++, ed.getComment());
155    addCell(row, i++, ed.getRequirements());
156    addCell(row, i++, ed.getDefaultValue()!=null ? renderType(ed.getDefaultValue()) : "");
157    addCell(row, i++, ed.getMeaningWhenMissing());
158    addCell(row, i++, ed.hasFixed() ? renderType(ed.getFixed()) : "");
159    addCell(row, i++, ed.hasPattern() ? renderType(ed.getPattern()) : "");
160    addCell(row, i++, ed.hasExample() ? renderType(ed.getExample().get(0).getValue()) : ""); // todo...?
161    addCell(row, i++, ed.hasMinValue() ? renderType(ed.getMinValue()) : "");
162    addCell(row, i++, ed.hasMaxValue() ? renderType(ed.getMaxValue()) : "");
163    addCell(row, i++, (ed.hasMaxLength() ? Integer.toString(ed.getMaxLength()) : ""));
164    if (ed.hasBinding()) {
165      addCell(row, i++, ed.getBinding().getStrength()!=null ? ed.getBinding().getStrength().toCode() : "");
166      addCell(row, i++, ed.getBinding().getDescription());
167      if (ed.getBinding().getValueSet()==null)
168        addCell(row, i++, "");
169      else
170        addCell(row, i++, ed.getBinding().getValueSet());
171    } else {
172      addCell(row, i++, "");
173      addCell(row, i++, "");
174      addCell(row, i++, "");
175    }
176    addCell(row, i++, itemList(ed.getCode()));
177    if (ed.hasSlicing()) {
178      addCell(row, i++, itemList(ed.getSlicing().getDiscriminator()));
179      addCell(row, i++, ed.getSlicing().getDescription());
180      addCell(row, i++, ed.getSlicing().getOrdered());
181      addCell(row, i++, ed.getSlicing().getRules()!=null ? ed.getSlicing().getRules().toCode() : "");
182    } else {
183      addCell(row, i++, "");
184      addCell(row, i++, "");
185      addCell(row, i++, "");      
186      addCell(row, i++, "");      
187    }
188    if (ed.getBase()!=null) {
189      addCell(row, i++, ed.getBase().getPath());
190      addCell(row, i++, ed.getBase().getMin());
191      addCell(row, i++, ed.getBase().getMax());
192    } else {
193      addCell(row, i++, "");
194      addCell(row, i++, "");
195      addCell(row, i++, "");      
196    }
197    addCell(row, i++, itemList(ed.getCondition()));
198    addCell(row, i++, itemList(ed.getConstraint()));
199    for (StructureDefinitionMappingComponent mapKey : sd.getMapping()) {
200      String mapString = "";
201      for (ElementDefinitionMappingComponent map : ed.getMapping()) {
202        if (map.getIdentity().equals(mapKey.getIdentity()))
203          mapString = map.getMap();        
204      }
205      addCell(row, i++, mapString);
206    }
207  }
208
209  private String itemList(List l) {
210    StringBuilder s = new StringBuilder();
211    for (int i =0; i< l.size(); i++) {
212      Object o = l.get(i);
213      String val = "";
214      if (o instanceof StringType) {
215        val = ((StringType)o).getValue();
216      } else if (o instanceof UriType) {
217        val = ((UriType)o).getValue();
218      } else if (o instanceof IdType) {
219        val = ((IdType)o).getValue();
220      } else if (o instanceof Enumeration<?>) {
221        val = o.toString();
222      } else if (o instanceof TypeRefComponent) {
223        TypeRefComponent t = (TypeRefComponent)o;
224        val = t.getWorkingCode();
225        if (val == null)
226          val = "";
227        if (val.startsWith("http://hl7.org/fhir/StructureDefinition/"))
228          val = val.substring(40);
229        if (t.hasTargetProfile()) 
230          val = val+ "(" + canonicalList(t.getTargetProfile()) + ")";
231        if (t.hasProfile())
232          val = val + " {" + canonicalList(t.getProfile()) + "}";
233        if (t.hasAggregation()) 
234          val = val + " <<" + aggList(t.getAggregation()) + ">>";
235      } else if (o instanceof Coding) {
236        Coding t = (Coding)o;
237        val = (t.getSystem()==null ? "" : t.getSystem()) + (t.getCode()==null ? "" : "#" + t.getCode()) + (t.getDisplay()==null ? "" : " (" + t.getDisplay() + ")");
238      } else if (o instanceof ElementDefinitionConstraintComponent) {
239        ElementDefinitionConstraintComponent c = (ElementDefinitionConstraintComponent)o;
240        val = c.getKey() + ":" + c.getHuman() + " {" + c.getExpression() + "}";
241      } else if (o instanceof ElementDefinitionSlicingDiscriminatorComponent) {
242        ElementDefinitionSlicingDiscriminatorComponent c = (ElementDefinitionSlicingDiscriminatorComponent)o;
243        val = c.getType().toCode() + ":" + c.getPath() + "}";
244
245      } else {
246        val = o.toString();
247        val = val.substring(val.indexOf("[")+1);
248        val = val.substring(0, val.indexOf("]"));
249      }
250      s = s.append(val);
251      if (i == 0)
252        s.append("\n");
253    }
254    return s.toString();
255  }
256
257  private String aggList(List<org.hl7.fhir.r5.model.Enumeration<AggregationMode>> list) {
258    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
259    for (org.hl7.fhir.r5.model.Enumeration<AggregationMode> c : list)
260      b.append(c.getValue().toCode());
261    return b.toString();
262  }
263
264  private String canonicalList(List<CanonicalType> list) {
265    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|");
266    for (CanonicalType c : list) {
267      String v = c.getValue();
268      if (v.startsWith("http://hl7.org/fhir/StructureDefinition/"))
269        v = v.substring(40);
270      b.append(v);
271    }
272    return b.toString();
273  }
274
275  private String renderType(DataType value) throws Exception {
276    if (value == null)
277      return "";
278    if (value.isPrimitive())
279      return value.primitiveValue();
280
281    String s = null;
282    ByteArrayOutputStream bs = new ByteArrayOutputStream();
283    if (asXml) {
284      xml.setOutputStyle(OutputStyle.PRETTY);
285      xml.compose(bs, "", value);
286      bs.close();
287      s = bs.toString();
288      s = s.substring(s.indexOf("\n")+2);
289    } else {
290      json.setOutputStyle(OutputStyle.PRETTY);
291      json.compose(bs, value, "");
292      bs.close();
293      s = bs.toString();
294    }
295    return s;
296  }
297
298
299  public void configureSheet(Sheet sheet, StructureDefinition sd) throws IOException {
300    for (int i=0; i<34; i++) {
301      sheet.autoSizeColumn(i);
302    }
303    sheet.setColumnHidden(2, true);
304    sheet.setColumnHidden(3, true);
305    sheet.setColumnHidden(30, true);
306    sheet.setColumnHidden(31, true);
307    sheet.setColumnHidden(32, true);
308
309    sheet.setColumnWidth(9, columnPixels(20));
310    sheet.setColumnWidth(11, columnPixels(100));
311    sheet.setColumnWidth(12, columnPixels(100));
312    sheet.setColumnWidth(13, columnPixels(100));
313    sheet.setColumnWidth(15, columnPixels(20));
314    sheet.setColumnWidth(16, columnPixels(20));
315    sheet.setColumnWidth(17, columnPixels(20));
316    sheet.setColumnWidth(18, columnPixels(20));
317    sheet.setColumnWidth(34, columnPixels(100));
318
319    int i = titles.length - 1;
320    for (StructureDefinitionMappingComponent map : sd.getMapping()) {
321      i++;
322      sheet.setColumnWidth(i, columnPixels(50));
323      sheet.autoSizeColumn(i);
324      //  sheet.setColumnHidden(i,  true);
325    }    
326    sheet.createFreezePane(2,1);
327
328    if (hideMustSupportFalse) {
329      SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
330      String address = "A2:AI" + Math.max(Integer.valueOf(sheet.getLastRowNum()), 2);
331      CellRangeAddress[] regions = {
332          CellRangeAddress.valueOf(address)
333      };
334
335      ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("$G2<>\"Y\"");
336      PatternFormatting fill1 = rule1.createPatternFormatting();
337      fill1.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.index);
338      fill1.setFillPattern(PatternFormatting.SOLID_FOREGROUND);
339
340      ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule("$Q2<>\"\"");
341      FontFormatting font = rule2.createFontFormatting();
342      font.setFontColorIndex(IndexedColors.GREY_25_PERCENT.index);
343      font.setFontStyle(true, false);
344
345      sheetCF.addConditionalFormatting(regions, rule1, rule2);
346
347      sheet.setAutoFilter(new CellRangeAddress(0,sheet.getLastRowNum(), 0, titles.length+sd.getMapping().size() - 1));
348
349
350      XSSFSheet xSheet = (XSSFSheet)sheet;
351
352      CTAutoFilter sheetFilter = xSheet.getCTWorksheet().getAutoFilter();
353      CTFilterColumn filterColumn1 = sheetFilter.addNewFilterColumn();
354      filterColumn1.setColId(6);
355      CTCustomFilters filters = filterColumn1.addNewCustomFilters();
356      CTCustomFilter filter1 = filters.addNewCustomFilter();
357      filter1.setOperator(STFilterOperator.NOT_EQUAL);
358      filter1.setVal(" ");
359
360      CTFilterColumn filterColumn2 = sheetFilter.addNewFilterColumn();
361      filterColumn2.setColId(26);
362      CTFilters filters2 = filterColumn2.addNewFilters();
363      filters2.setBlank(true);
364
365      // We have to apply the filter ourselves by hiding the rows: 
366      for (Row row : sheet) {
367        if (row.getRowNum()>0 && (!row.getCell(6).getStringCellValue().equals("Y") || !row.getCell(26).getStringCellValue().isEmpty())) {
368          ((XSSFRow) row).getCTRow().setHidden(true);
369        }
370      }
371    }
372    sheet.setActiveCell(new CellAddress(sheet.getRow(1).getCell(0)));
373
374  }
375
376}