package jnpf.service;
|
|
import jnpf.exception.DataException;
|
import org.docx4j.TraversalUtil;
|
import org.docx4j.XmlUtils;
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
import org.docx4j.wml.ObjectFactory;
|
import org.docx4j.wml.SdtBlock;
|
import org.docx4j.wml.SdtContentBlock;
|
import org.docx4j.wml.SdtElement;
|
import org.docx4j.wml.SdtPr;
|
import org.docx4j.wml.Tag;
|
import org.springframework.http.MediaType;
|
import org.springframework.stereotype.Service;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayOutputStream;
|
import java.io.File;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.nio.file.Files;
|
import java.util.ArrayDeque;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Deque;
|
import java.util.IdentityHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Set;
|
|
/** Embeds and detects the non-printing content-control marker carried by a DMS approval page. */
|
@Service
|
public class DmsApprovalPageTagService {
|
public static final String TAG_VALUE = "DMS_APPROVAL_PAGE";
|
private static final ObjectFactory WML = new ObjectFactory();
|
|
public MultipartFile tagApprovalPage(MultipartFile file) {
|
String name = filename(file);
|
String extension = extension(name);
|
if ("pdf".equals(extension)) {
|
return file;
|
}
|
if (!"docx".equals(extension)) {
|
throw new DataException("审批页仅支持 PDF 或 DOCX 格式");
|
}
|
try {
|
WordprocessingMLPackage word = WordprocessingMLPackage.load(file.getInputStream());
|
if (hasApprovalPage(word)) {
|
return file;
|
}
|
List<Object> original = new ArrayList<>(word.getMainDocumentPart().getContent());
|
word.getMainDocumentPart().getContent().clear();
|
word.getMainDocumentPart().getContent().add(taggedBlock(original));
|
return generated(file, save(word));
|
} catch (DataException exception) {
|
throw exception;
|
} catch (Exception exception) {
|
throw new DataException("写入审批页标签失败:" + message(exception));
|
}
|
}
|
|
public boolean hasApprovalPage(MultipartFile file) {
|
if (file == null || file.isEmpty() || !"docx".equals(extension(file.getOriginalFilename()))) {
|
return false;
|
}
|
try {
|
return hasApprovalPage(WordprocessingMLPackage.load(file.getInputStream()));
|
} catch (Exception exception) {
|
throw new DataException("检查审批页标签失败:" + message(exception));
|
}
|
}
|
|
public boolean hasApprovalPage(WordprocessingMLPackage word) {
|
if (word == null || word.getMainDocumentPart() == null) {
|
return false;
|
}
|
Deque<Object> pending = new ArrayDeque<>();
|
pending.push(word.getMainDocumentPart().getJaxbElement());
|
Set<Object> visited = Collections.newSetFromMap(new IdentityHashMap<>());
|
while (!pending.isEmpty()) {
|
Object value = XmlUtils.unwrap(pending.pop());
|
if (value == null || !visited.add(value)) {
|
continue;
|
}
|
if (value instanceof SdtElement && isApprovalPage((SdtElement) value)) {
|
return true;
|
}
|
List<Object> children = TraversalUtil.getChildrenImpl(value);
|
if (children != null) {
|
for (Object child : children) {
|
if (child != null) pending.push(child);
|
}
|
}
|
}
|
return false;
|
}
|
|
public SdtBlock taggedBlock(List<Object> content) {
|
Tag tag = WML.createTag();
|
tag.setVal(TAG_VALUE);
|
SdtPr properties = WML.createSdtPr();
|
properties.setTag(tag);
|
SdtContentBlock taggedContent = WML.createSdtContentBlock();
|
if (content != null) taggedContent.getContent().addAll(content);
|
SdtBlock block = WML.createSdtBlock();
|
block.setSdtPr(properties);
|
block.setSdtContent(taggedContent);
|
return block;
|
}
|
|
/** Removes approval pages created by this service; they are direct blocks in the main document. */
|
public int removeApprovalPages(WordprocessingMLPackage word) {
|
if (word == null || word.getMainDocumentPart() == null) return 0;
|
List<Object> content = word.getMainDocumentPart().getContent();
|
int before = content.size();
|
content.removeIf(value -> {
|
Object unwrapped = XmlUtils.unwrap(value);
|
return unwrapped instanceof SdtElement && isApprovalPage((SdtElement) unwrapped);
|
});
|
return before - content.size();
|
}
|
|
private boolean isApprovalPage(SdtElement element) {
|
SdtPr properties = element.getSdtPr();
|
Tag tag = properties == null ? null : properties.getTag();
|
return tag != null && TAG_VALUE.equals(tag.getVal());
|
}
|
|
private byte[] save(WordprocessingMLPackage word) throws Exception {
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
word.save(output);
|
return output.toByteArray();
|
}
|
|
private MultipartFile generated(MultipartFile source, byte[] content) {
|
return new GeneratedMultipartFile(source.getName(), source.getOriginalFilename(), content);
|
}
|
|
private String filename(MultipartFile file) {
|
String name = file == null ? null : file.getOriginalFilename();
|
if (file == null || file.isEmpty() || name == null || name.isBlank()) {
|
throw new DataException("审批页为空或缺少文件名");
|
}
|
return name.trim();
|
}
|
|
private String extension(String filename) {
|
if (filename == null) return "";
|
int separator = filename.lastIndexOf('.');
|
return separator < 0 ? "" : filename.substring(separator + 1).toLowerCase(Locale.ROOT);
|
}
|
|
private String message(Exception exception) {
|
String value = exception.getMessage();
|
return value == null || value.isBlank() ? exception.getClass().getSimpleName() : value;
|
}
|
|
private static final class GeneratedMultipartFile implements MultipartFile {
|
private final String name;
|
private final String originalFilename;
|
private final byte[] content;
|
|
private GeneratedMultipartFile(String name, String originalFilename, byte[] content) {
|
this.name = name;
|
this.originalFilename = originalFilename;
|
this.content = content;
|
}
|
|
@Override public String getName() { return name; }
|
@Override public String getOriginalFilename() { return originalFilename; }
|
@Override public String getContentType() { return MediaType.parseMediaType(
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document").toString(); }
|
@Override public boolean isEmpty() { return content.length == 0; }
|
@Override public long getSize() { return content.length; }
|
@Override public byte[] getBytes() { return content.clone(); }
|
@Override public InputStream getInputStream() { return new ByteArrayInputStream(content); }
|
@Override public void transferTo(File destination) throws IOException { Files.write(destination.toPath(), content); }
|
}
|
}
|