View Javadoc
1   package de.aikiit.bilanzanalyser.reader;
2   
3   import de.aikiit.bilanzanalyser.entity.BilanzRow;
4   import lombok.AccessLevel;
5   import lombok.NoArgsConstructor;
6   import lombok.extern.log4j.Log4j2;
7   import org.odftoolkit.odfdom.doc.table.OdfTableRow;
8   
9   import java.math.BigDecimal;
10  import java.text.DecimalFormat;
11  import java.text.DecimalFormatSymbols;
12  import java.text.NumberFormat;
13  import java.time.LocalDate;
14  import java.util.Locale;
15  import java.util.Optional;
16  
17  @NoArgsConstructor(access = AccessLevel.PRIVATE)
18  @Log4j2
19  public final class BilanzRowParser {
20  
21      public static Optional<BilanzRow> fromOdfTableRow(final OdfTableRow row) {
22          try {
23              // in order to handle LibreOffice-specific currency values we need to parse properly and handle mixture of '.' and ',' in numbers.
24              DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.GERMANY);
25              DecimalFormat format = new DecimalFormat();
26              format.setDecimalFormatSymbols(symbols);
27              format.setParseBigDecimal(true);
28  
29              var bilanzRow = BilanzRow.builder() //
30                      // remove trailing spaces and currency symbol
31                      .amount(new BigDecimal(format.parse(cleanUpAmount(row.getCellByIndex(1).getStringValue())).toString())) //
32                      .description(row.getCellByIndex(2).getStringValue()) //
33                      .shop(row.getCellByIndex(3).getStringValue()) //
34                      .payment(row.getCellByIndex(4).getStringValue()) //
35                      .category(row.getCellByIndex(5).getStringValue());
36  
37              // map special values in date column to fallback date in entity
38              var sourceDate = row.getCellByIndex(0).getStringValue().trim();
39              if (!sourceDate.isBlank() && !"?".equals(sourceDate)) {
40                  bilanzRow.date(LocalDate.parse(sourceDate));
41              }
42  
43              return Optional.of(bilanzRow.build());
44          } catch (Exception e) {
45              log.error("Skipping row '{}' due to: {}", debugRowValues(row), e.getMessage());
46              return Optional.empty();
47          }
48      }
49  
50      static String debugRowValues(final OdfTableRow row) {
51          if (row == null) {
52              return "null";
53          }
54          return "[" + row.getCellByIndex(0).getStringValue() + ", " + row.getCellByIndex(1).getStringValue() + ", " + row.getCellByIndex(2).getStringValue() + ", " + row.getCellByIndex(3).getStringValue() + ", " + row.getCellByIndex(4).getStringValue() + ", " + row.getCellByIndex(5).getStringValue() + "]";
55  
56      }
57  
58      /**
59       * Removes currency symbol and trims the given amount value from an ODS file.
60       *
61       * @param amount given amount, e.g. 1,23 €
62       * @return trimmed amount in order to be parseable as a numeric.
63       */
64      static String cleanUpAmount(String amount) {
65          if (amount != null && !amount.isEmpty()) {
66              return amount.replace("€", "").trim();
67          }
68          return amount;
69      }
70  }