Я учусь использовать Apache Poi для получения полей из документа Word. Мой демонстрационный код выглядит следующим образом, большинство из них сгенерировано ChatGPT, я вношу небольшую модификацию, но он НЕ работает; снимок для демо-слова также выглядит следующим образом
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import java.io.FileInputStream;
import java.util.List;
public class FindSimpleFieldInDocx {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("/Users/pengbo/word/template1.docx");
XWPFDocument document = new XWPFDocument(fis);
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph para : paragraphs) {
System.out.println(para.getText());
XmlCursor cursor = para.getCTP().newCursor();
cursor.selectPath("./*");
while (cursor.toNextSelection()) {
XmlCursor.TokenType tokenType = cursor.currentTokenType();
if (tokenType.equals(XmlCursor.TokenType.START)) {
if (CTSimpleField.type.getName().getLocalPart().equals(cursor.getName().getLocalPart())) {
//cannot reach here........
CTSimpleField simpleField = (CTSimpleField) cursor.getObject();
String fieldCode = simpleField.getInstr();
System.out.println("find out CTSimpleField: " + fieldCode);
}
}
}
cursor.dispose();
}
document.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
снимок template1.docxфайл документа template1.docx
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.2</version>
</dependency>
<!-- Apache POI OOXML -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
<!-- Apache POI OOXML Schemas -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version> <!-- 或使用与你的POI版本匹配的最新版本 -->
</dependency>
<!-- XMLBeans -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.17.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
кто-нибудь может предоставить мне демонстрационный код, как получить поле офисного слова через текстовый документ? спасибо.
Код, созданный ChatGPT, не работает. Почему я не удивлен?
прикрепленный файл; Хотя он создан ChatGPT, но я дважды проверил, не обнаружил никаких проблем; в любом случае, можете ли вы привести пример того, как получить поле из документа Word.
poi-ooxml-schemas 4.1.2
и poi-ooxml 5.2.2
не сочетаются друг с другом. Apache POI 5 требует poi-ooxml-lite
вместо poi-ooxml-schemas
. Таким образом, poi-ooxml 5.2.2
и poi-ooxml-lite 5.2.2
подходят друг другу.
Сравнивать имена элементов XML с именами типов схемы XmlObject
— не лучший вариант. Для простого поля имя локальной части элемента XML — fldSimple
, а имя типа схемы — CT_SimpleField
. Это не соответствует.
Если вы действительно хотите найти CTSimpleField
, вы ищете объекты, которые являются экземплярами CTSimpleField
: cursor.getObject() instanceof CTSimpleField
:
...
if (tokenType.equals(XmlCursor.TokenType.START)) {
if (cursor.getObject() instanceof CTSimpleField) {
//current object is instance of CTSimpleField
CTSimpleField simpleField = (CTSimpleField) cursor.getObject();
String fieldCode = simpleField.getInstr();
System.out.println("find out CTSimpleField: " + fieldCode);
}
}
...
Это должно найти поля, хранящиеся как простое поле.
Но текущий Microsoft Word не хранит все поля как простые поля. В этом можно убедиться, разархивировав *.docx
ZIP-архив и заглянув в /word/document.xml
. Для меня XML для поля даты выглядит, например, так:
<w:p >
<w:r>
<w:fldChar w:fldCharType = "begin"/>
</w:r>
<w:r>
<w:instrText xml:space = "preserve"> DATE \* MERGEFORMAT </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType = "separate"/>
</w:r>
<w:r>
<w:t>05.06.2024</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType = "end"/>
</w:r>
</w:p>
Чтобы получить это с помощью Apache POI, необходим другой подход.
В следующем полном примере показано, как получить поля CTSimpleField
и CTFldChar
.
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.SimpleValue;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType;
import java.io.FileInputStream;
public class FindFieldsInDocx {
static void findAndPrintCTSimpleFields(XWPFParagraph paragraph) {
XmlCursor cursor = paragraph.getCTP().newCursor();
String declareNameSpaces = "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' ";
cursor.selectPath(declareNameSpaces + ".//w:fldSimple");
while (cursor.toNextSelection()) {
if (cursor.getObject() instanceof CTSimpleField) {
//current object is instance of CTSimpleField
CTSimpleField simpleField = (CTSimpleField) cursor.getObject();
System.out.println(simpleField.getInstr() + " | " + ((SimpleValue)simpleField).getStringValue());
}
}
}
static void findAndPrintCTFldCharFields(XWPFParagraph paragraph) {
StringBuilder runFieldContent = new StringBuilder();
boolean fldCharFound = false;
for (XWPFRun run : paragraph.getRuns()) {
if (containsFldChar(run, STFldCharType.BEGIN)) {
fldCharFound = true;
}
if (fldCharFound) {
runFieldContent.append(((SimpleValue)run.getCTR()).getStringValue());
runFieldContent.append(" | ");
}
if (fldCharFound && containsFldChar(run, STFldCharType.END)) {
fldCharFound = false;
runFieldContent.append(((SimpleValue)run.getCTR()).getStringValue());
System.out.println(runFieldContent);
runFieldContent = new StringBuilder();
}
}
}
static boolean containsFldChar(XWPFRun run, STFldCharType.Enum type) {
XmlCursor cursor = run.getCTR().newCursor();
String declareNameSpaces = "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' ";
cursor.selectPath(declareNameSpaces + ".//w:fldChar");
while (cursor.toNextSelection()) {
if (cursor.getObject() instanceof CTFldChar) {
CTFldChar fldChar = (CTFldChar) cursor.getObject();
if (fldChar.getFldCharType() == type) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
try ( FileInputStream fis = new FileInputStream("./template1.docx");
XWPFDocument document = new XWPFDocument(fis);) {
for (XWPFParagraph paragraph : document.getParagraphs()) {
findAndPrintCTSimpleFields(paragraph);
findAndPrintCTFldCharFields(paragraph);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Я изменил свой код в соответствии с вашим предложением, но, похоже, НЕ работает, он ВСЕ ЕЩЕ не может определить NUMPAGES * MERGEFORMAT. код ключа как if (cursor.getObject() instanceof CTSimpleField) { //current object is instance of CTSimpleField CTSimpleField simpleField = (CTSimpleField) cursor.getObject(); String fieldCode = simpleField.getInstr(); }
my word файл как файл @Axel Richter
Ваш код работает, спасибо.
template1.docx Не размещайте ссылку на изображение документа. Киньте ссылку на сам документ