Apache POI Как добавить пользовательский формат данных для обработки 13-значных целых чисел в виде строк, а не чисел

Я создаю процессор XLSX, который преобразует XLSX в файл CSV. Поскольку файлы могут стать довольно большими, я использую подход, основанный на событиях, с помощью XSSFSheetXMLHandler.

Это отлично работает, но мои файлы XLSX содержат длинные числа (13 цифр), которые являются уникальными идентификационными номерами, а не реальными числами. При запуске моего кода на машине с Windows он правильно извлекает числа, но при работе на машине с Linux он преобразует их в электронную нотацию.

Например: исходное значение — 7401075293087. В Windows это правильно извлекается в мой CSV, но в Linux значение отображается как 7.40108E+12.

Проблема с XSSFSheetXMLHandler заключается в том, что он читает XLSX скрыто, а затем выдает события, перехваченные SheetContentsHandler, которые вам необходимо реализовать. Один из методов в SheetContentsHandler — это метод ячейки с подписью: ячейка (String cellReference, String formattedValue, комментарий XSSFComment)

Как видите, этот метод уже получил отформатированную ячейку (поэтому в моем случае он получает «7.40108E+12»). Вся остальная логика происходит под одеялом.

Основываясь на моих исследованиях, я считаю, что решение заключается в определении пользовательского DataFormatter, который будет специально обрабатывать 13-значные целые числа как строку, а не форматировать их как E-нотацию.

К сожалению, мой план не сработал должным образом, и я не смог найти помощь в Интернете. Ниже приведен фрагмент моего кода. Я попробовал следующее в методе processSheet:

     Locale locale = new Locale.Builder().setLanguage("en").setRegion("ZA").build(); 
     DataFormatter formatter = new DataFormatter(locale);
     Format format = new MessageFormat("{0,number,full}");
     formatter.addFormat("#############", format);

Вот выдержка из моего кода:

Основная часть кода:

 public void process(String Filename)throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {
     ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);
     XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
     StylesTable styles = xssfReader.getStylesTable();
     XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
     while (iter.hasNext()) {
          InputStream stream = iter.next();
          String sheetName = iter.getSheetName();
          outStream = new FileOutputStream(Filename);
          logger.info(sheetName);
          this.output = new  PrintWriter(Filename);
          processSheet(styles, strings, new SheetToCSV(), stream);
          logger.info("Done with Sheet   :"+sheetName);
          output.flush();
          stream.close();
          outStream.close();
          output.close();
         ++index; 
     }
 } 

 public void processSheet(StylesTable styles,ReadOnlySharedStringsTable strings,SheetContentsHandler sheetHandler, InputStream sheetInputStream)
         throws IOException, ParserConfigurationException, SAXException {

     InputSource sheetSource = new InputSource(sheetInputStream);
     try {
         XMLReader sheetParser = SAXHelper.newXMLReader();
         ContentHandler handler = new XSSFSheetXMLHandler(styles, null, strings, sheetHandler, formatter, false);
         sheetParser.setContentHandler(handler);
         sheetParser.parse(sheetSource);
      } catch(ParserConfigurationException e) {
         throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
      }
 }

И вот пользовательский обработчик:

private class SheetToCSV implements SheetContentsHandler {
         private boolean firstCellOfRow = false;
         private int currentRow = -1;
         private int currentCol = -1;

     private void outputMissingRows(int number) {

         for (int i=0; i<number; i++) {
             for (int j=0; j<minColumns; j++) {
                 output.append(',');
             }
             output.append('\n');
         }
     }

     public void startRow(int rowNum) {
         // If there were gaps, output the missing rows
         outputMissingRows(rowNum-currentRow-1);
         // Prepare for this row
         firstCellOfRow = true;
         currentRow = rowNum;
         currentCol = -1;
     }

     public void endRow(int rowNum) {
         // Ensure the minimum number of columns
         for (int i=currentCol; i<minColumns; i++) {
             output.append(',');
         }
         output.append('\n');
     }

     public void cell(String cellReference, String formattedValue,
             XSSFComment comment) {
         logger.info("CellRef :: Formatted Value   :"+cellReference+" :: "+formattedValue);              
         if (firstCellOfRow) {
             firstCellOfRow = false;
         } else {
             output.append(',');
         }

         // gracefully handle missing CellRef here in a similar way as XSSFCell does
         if(cellReference == null) {
             cellReference = new CellRangeAddress(currentRow, currentCol, currentCol, currentCol).formatAsString();
         }

         // Did we miss any cells?
         int thisCol = (new CellReference(cellReference)).getCol();
         int missedCols = thisCol - currentCol - 1;
         for (int i=0; i<missedCols; i++) {
             output.append(',');
         }
         currentCol = thisCol;

         // Number or string?
         try {
             Double.parseDouble(formattedValue);
             output.append(formattedValue);
         } catch (NumberFormatException e) {
             //formattedValue = formattedValue.replaceAll("\\t", "");
             //formattedValue = formattedValue.replaceAll("\\n", "");
             //formattedValue = formattedValue.trim();
             output.append('"');
             output.append(formattedValue.replace("\"", "\\\"").trim());
             output.append('"');
         }
     }

     public void headerFooter(String text, boolean isHeader, String tagName) {
         // Skip, no headers or footers in CSV
     }

    @Override
    public void ovveriddenFormat(String celRef, int formatIndex,
            String formatedString) {
        // TODO Auto-generated method stub

    }

 }

person Greg Fullard    schedule 19.03.2018    source источник


Ответы (2)


Невозможно воспроизвести, если файл создан с использованием Excel, а ячейки, содержащие 13-значные числа, отформатированы с использованием числового формата 0 или #, не General.

Но что подразумевается под «работой на машине с Linux»? Если я создаю файл *.xlsx, используя Libreoffice Calc, имея ячейки, содержащие 13-значные числа, отформатированные с использованием числового формата General, тогда Calc покажет их как 13-значные числа, а Excel - нет. Для отображения 13-значных чисел в формате Excel ячейки должны быть отформатированы с использованием числового формата 0 или #.

apache poi DataFormatter работает так же, как Excel. И Excel показывает значения из 12 цифр в экспоненциальном представлении при форматировании с использованием General.

Вы можете изменить это поведение, используя:

...
    public void processSheet(
            StylesTable styles,
            ReadOnlySharedStringsTable strings,
            SheetContentsHandler sheetHandler, 
            InputStream sheetInputStream) throws IOException, SAXException {
        DataFormatter formatter = new DataFormatter();
        formatter.addFormat("General", new java.text.DecimalFormat("#.###############"));
...
person Axel Richter    schedule 19.03.2018
comment
Тх Аксель. Ваше предложение меня устроило. Что касается других ваших комментариев: к сожалению, у меня нет контроля над форматированием исходного файла, поэтому я должен обрабатывать то, что у меня есть (и я получаю много разных форматов, поэтому стараюсь быть универсальным). - person Greg Fullard; 03.04.2018
comment
Комментарий W.r.t к машине с Linux: мой основной блок разработки — это машина с Ubuntu 16.04, которая выдала ошибку, как описано. Когда я запустил код на ноутбуке Windows клиента, ошибки не произошло. Точно такой же файл, точно такая же кодовая база. В результате я предположил, что это какая-то проблема, связанная с локалью. - person Greg Fullard; 03.04.2018
comment
Просто для записи я добавил форматирование следующим образом: formatter.addFormat(General, new java.text.DecimalFormat(#.######################## ###)); Это было необходимо, поскольку другие поля в моем XLSX содержали такие значения, как 0,00160455519952056. - person Greg Fullard; 03.04.2018

DZONE написал об этом потрясающую статью: https://dzone.com/articles/simple-string-representation-of-java-decimal-numbe

Другой ответ от StackOverflow:

Row row = sheet.getRow(0);
Object o = getCellValue(row.getCell(0));
System.out.println(new BigDecimal(o.toString()).toPlainString());

REF: Apache POI DataFormatter возвращает экспоненциальное представление

Я не проверял вашу настоящую проблему на Linux-машине ... однако я надеюсь, что это даст некоторые ответы посреди ночи!

person Luigi D'Amico    schedule 19.03.2018