Apache POI Cara menambahkan DataFormatter khusus untuk menangani bilangan bulat 13 digit sebagai string, bukan angka

Saya sedang membangun prosesor XLSX yang mengubah XLSX menjadi file CSV. Karena filenya bisa menjadi cukup besar, saya menggunakan pendekatan berbasis peristiwa menggunakan XSSFSheetXMLHandler

Ini berfungsi dengan baik, tetapi file XLSX saya berisi angka panjang (13 digit) yang merupakan nomor identifikasi unik, bukan angka sebenarnya. Saat menjalankan kode saya di mesin Windows, kode tersebut mengekstrak angka dengan benar, tetapi saat dijalankan di mesin Linux, kode tersebut mengubahnya menjadi notasi E.

Misalnya: nilai sumbernya adalah 7401075293087. Di Windows, nilai ini diekstraksi dengan benar ke dalam CSV saya, tetapi di Linux nilainya muncul sebagai 7.40108E+12

Masalah dengan XSSFSheetXMLHandler adalah ia membaca XLSX di balik selimut dan kemudian menampilkan peristiwa yang ditangkap oleh SheetContentsHandler yang perlu Anda terapkan. Salah satu metode di SheetContentsHandler adalah metode sel dengan tanda tangan: sel (String cellReference, String formattedValue, XSSFComment comment)

Seperti yang Anda lihat, metode ini sudah menerima sel yang diformat (jadi dalam kasus saya ia menerima "7.40108E+12"). Logika lainnya terjadi secara tersembunyi.

Berdasarkan penyelidikan saya, saya yakin solusinya terletak pada mendefinisikan DataFormatter khusus yang secara khusus akan memperlakukan bilangan bulat 13 digit sebagai string, alih-alih memformatnya sebagai notasi E.

Sayangnya rencana saya tidak berjalan sesuai harapan dan saya tidak dapat menemukan bantuan online. Di bawah ini adalah ekstrak kode saya. Saya mencoba yang berikut ini dalam metode 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);

Berikut ekstrak kode saya:

Bagian utama kode:

 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());
      }
 }

Dan inilah penangan khusus:

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 sumber


Jawaban (2)


Tidak dapat direproduksi jika file dibuat menggunakan Excel dan sel yang berisi 13 digit angka diformat menggunakan format angka 0 atau #, bukan General.

Tapi apa yang dimaksud dengan "berjalan di mesin Linux"? Jika saya membuat file *.xlsx menggunakan Libreoffice Calc yang selnya berisi 13 digit angka diformat menggunakan format angka General, maka Calc akan menampilkannya sebagai 13 digit angka tetapi Excel tidak. Untuk menampilkan angka 13 digit di Excel sel harus diformat menggunakan format angka 0 atau #.

apache poi DataFormatter dibuat berfungsi seperti yang dilakukan Excel. Dan Excel menampilkan nilai dari 12 digit sebagai notasi ilmiah jika diformat menggunakan General.

Anda dapat mengubah perilaku ini menggunakan:

...
    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
Terima kasih Axel. Saran Anda membantu saya. Mengenai komentar Anda yang lain: Sayangnya saya tidak punya kendali atas pemformatan file sumber, jadi saya harus memproses apa yang saya punya (dan saya mendapatkan banyak format berbeda, jadi cobalah untuk menjadi generik). - person Greg Fullard; 03.04.2018
comment
Komentar W.r.t tentang mesin Linux: Kotak pengembang utama saya adalah mesin Ubuntu 16.04, yang memberikan kesalahan seperti yang dijelaskan. Ketika saya menjalankan kode di laptop Windows pelanggan, kesalahan tidak terjadi. File yang sama persis, basis kode yang sama persis. Akibatnya, saya berasumsi bahwa ini adalah masalah khusus lokal - person Greg Fullard; 03.04.2018
comment
Sekadar catatan, saya menambahkan format sebagai berikut: formatter.addFormat(General, new java.text.DecimalFormat(#.######################### ###)); Ini diperlukan karena bidang lain di XLSX saya berisi nilai seperti 0,00160455519952056 - person Greg Fullard; 03.04.2018

DZONE menulis artikel menarik tentang ini: https://dzone.com/articles/simple-string-representation-of-java-decimal-numbe

Jawaban lain dari StackOverflow adalah:

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

REF: Apache POI DataFormatter Mengembalikan Notasi Ilmiah

Saya tidak menguji masalah Anda yang sebenarnya pada mesin linux.. namun saya harap ini memberikan beberapa jawaban di tengah malam!

person Luigi D'Amico    schedule 19.03.2018