APACHE POI EXCEL XmlException: является недопустимым символом XML, есть ли способ предварительной обработки файла Excel? - PullRequest
0 голосов
/ 18 ноября 2018

Я использую Java и Apache POI для чтения файлов .xlsx. (60k + строки), но получаю ошибку.

Я использую последнюю версию плагина maven из poi и xmlbeans.

Согласно связанным вопросам, которые я нашел в StackOverflow, последние poi должны успешно обрабатывать файлы с помощью специального символа.

Я могу заменить специальный символ в программе самостоятельно, если это xml-файл. Но это файл Excel.

Сложность в том, что я не знаю, как использовать poi, чтобы успешно прочитать файл "excel".

Или есть способ обработать файл?

Я использую openjdk, версия: "1.8.0_171-1-redhat".

сообщение об ошибке, подобное этому

Caused by: java.io.IOException: unable to parse shared strings table
    at org.apache.poi.xssf.model.SharedStringsTable.readFrom(SharedStringsTable.java:134)
    at org.apache.poi.xssf.model.SharedStringsTable.<init>(SharedStringsTable.java:111)
    ... 11 more
Caused by: org.apache.xmlbeans.XmlException: error: Character reference "&#55357" is an invalid XML character.
    at org.apache.xmlbeans.impl.store.Locale$SaxLoader.load(Locale.java:3440)
    at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1272)
    at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1259)
    at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.parse(SchemaTypeLoaderBase.java:345)
    at org.openxmlformats.schemas.spreadsheetml.x2006.main.SstDocument$Factory.parse(Unknown Source)
    at org.apache.poi.xssf.model.SharedStringsTable.readFrom(SharedStringsTable.java:123)

код

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import org.apache.commons.codec.binary.Base64;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class test2 {

  public static void main(String[] args) throws Exception {
    File file = new File("D:\\Users\\3389\\Desktop\\Review\\drive-download-20181112T012605Z-001\\ticket.xlsx");
    Workbook workbook = null;
    XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); //error occured
    workbook = new SXSSFWorkbook(xssfWorkbook);
    Sheet sheet = xssfWorkbook.getSheetAt(0);  
    System.out.println("the first row:"+sheet.getFirstRowNum());
  }
}

pom.xml

        <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>4.0.0</version>
        </dependency>
        <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml</artifactId>
          <version>4.0.0</version>
        </dependency> 

UTF16SurrogatePairs в shareString.xml (несколько примеров)

&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;
&#55357;&#56397;
&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;&#55357;&#56397;
etc....

1 Ответ

0 голосов
/ 19 ноября 2018

Поскольку заголовок вашего вопроса содержит вопрос «Есть ли способ предварительной обработки файла Excel?», Я постараюсь ответить на этот вопрос:

Подразумевается: /xl/sharedStrings.xml в файле *.xlsx содержит ссылки на числовые символы XML UTF-16-суррогатная пара, такие как &#55357;&#56833; = ?. Это нормально для HTML. Но это не разрешено в Office Open XML, потому что там всегда используется кодировка UTF-8, и оба суррогатных символа в этом XML недопустимы.

Таким образом, если /xl/sharedStrings.xml в файле *.xlsx содержит ссылки на цифровые символы XML UTF-16-суррогатная пара, тогда файл поврежден и его не следует использовать в любом случае. Проблема должна быть решена от тех, кто создал этот файл *.xlsx.

Но если, тем не менее, необходимо восстановить этот файл, то это можно сделать только на строковом уровне. Синтаксический анализ XML невозможен из-за ссылок на числовые символы XML UTF-16-суррогатная пара. Затем необходимо получить /xl/sharedStrings.xml из файла *.xlsx. Затем получите строковое содержимое этого /xl/sharedStrings.xml файла. Затем замените каждую найденную ссылку на числовой символ XML в UTF-16-суррогатной паре заменой на Юникод.

Мой код показывает, как это сделать, используя java.util.regex.Matcher. Он ищет объекты, соответствующие шаблону &#(\\d{5});&#(\\d{5});. Если найдено, то получает суррогатную пару H igh и L ow как целые числа. Затем он проверяет, действительно ли это суррогатные пары (H должно быть между 0xD800 и 0xDBFF, а L должно быть между 0xDC00 и 0xDFFF). Если это так, он рассчитывает N как N = (H - 0xD800) * 0x400 + (L - 0xDC00) + 0x10000. Затем он заменяет ссылку на числовой символ XML в UTF-16-суррогатной паре на ссылку на числовой символ Unicode. После этого все это заменяет оставшиеся отдельные части пар дополнений пустой строкой. Поэтому они будут удалены, поскольку отдельные части пар добавок не допускаются.

import java.io.*;

import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import java.util.regex.Pattern; 
import java.util.regex.Matcher;

public class XSSFWrongXMLinSharedStrings {

 static String replaceUTF16SurrogatePairs(String string) {
  Pattern pattern = Pattern.compile("&#(\\d{5});&#(\\d{5});");
  Matcher matcher = pattern.matcher(string);
  while (matcher.find()) {
   String found = matcher.group();
   int h = Integer.valueOf(matcher.group(1));
   int l = Integer.valueOf(matcher.group(2));
   if (0xD800 <= h && h < 0xDC00 && 0xDC00 <= l && l < 0xDFFF) {
    int n = (h - 0xD800) * 0x400 + (l - 0xDC00) + 0x10000;
System.out.print(found + " will be replaced with ");
System.out.println("&#" + n + ";");
    string = string.replace(found, "&#" + n + ";");
   }
  }
  pattern = Pattern.compile("&#(\\d{5});");
  matcher = pattern.matcher(string);
  while (matcher.find()) {
   String found = matcher.group();
   int n = Integer.valueOf(matcher.group(1));
   if (0xD800 <= n && n < 0xDFFF) {
System.out.println(found + " is single part of supplement pair. It will be removed.");
    string = string.replace(found, "");
   }
  }  
  return string;
 }

 public static void main(String[] args) throws Exception {
  File file = new File("ticket.xlsx");

  //Repairing the /xl/sharedStrings.xml on string level. Parsing XML is not possible because of the UTF-16-surrogate-pair XML numeric character references.
  OPCPackage opcPackage = OPCPackage.open(file);
  PackagePart packagePart = opcPackage.getPartsByName(Pattern.compile("/xl/sharedStrings.xml")).get(0);
  ByteArrayOutputStream sharedStringsBytes = new ByteArrayOutputStream();
  byte[] buffer = new byte[1024];
  int length;
  InputStream inputStream = packagePart.getInputStream();
  while ((length = inputStream.read(buffer)) != -1) {
   sharedStringsBytes.write(buffer, 0, length);
  }
  inputStream.close();
  String sharedStrings = sharedStringsBytes.toString("UTF-8");

  //Replace UTF-16-surrogate-pair XML numeric character reference with it's unicode replacement:
  //sharedStrings = sharedStrings.replace("&#55357;&#56833;", "&#x1F601;");
  //ToDo: Create method for replacing all possible UTF-16-surrogate-pair XML numeric character references with their unicode replacements.
  sharedStrings = replaceUTF16SurrogatePairs(sharedStrings);

  OutputStream outputStream = packagePart.getOutputStream();
  outputStream.write(sharedStrings.getBytes("UTF-8"));
  outputStream.flush();
  outputStream.close();
  opcPackage.close();
  //Now the /xl/sharedStrings.xml in the file does not contain UTF-16-surrogate-pair XML numeric character references any more.

  Workbook workbook = new XSSFWorkbook(file); 
  Sheet sheet = workbook.getSheetAt(0);  
  System.out.println("Success.");
 }
}
...