刘光辉
8 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package jnpf.bizcommon.onlyoffice.service.impl;
 
import jnpf.exception.DataException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
 
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
 
/** Canonicalizes qianpiye flow-version properties after DocumentServer saves a DOCX. */
@Component
public class DmsQianpiyeDocxPropertiesWriter {
    static final String CUSTOM_PROPERTIES_PART = "docProps/custom.xml";
    static final String NEW_FLOW_VERSION_PROPERTY = "DMS_NEW_FLOW_VERSION_ID";
    static final String EDIT_FLOW_VERSION_PROPERTY = "DMS_EDIT_FLOW_VERSION_ID";
    private static final String CUSTOM_PROPERTIES_NAMESPACE =
            "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
    private static final String DOC_PROPERTIES_TYPES_NAMESPACE =
            "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
    private static final String FORMAT_ID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
 
    /** Keeps non-qianpiye and legacy documents intact; duplicate staged values use the last one. */
    public byte[] upsertStagedFlowVersions(byte[] source) {
        if (source == null || source.length == 0) return source;
        try {
            Map<String, byte[]> parts = readParts(source);
            byte[] custom = parts.get(CUSTOM_PROPERTIES_PART);
            if (custom == null) return source;
            Map<String, String> versions = readFlowVersions(custom);
            if (!StringUtils.hasText(versions.get(NEW_FLOW_VERSION_PROPERTY))
                    || !StringUtils.hasText(versions.get(EDIT_FLOW_VERSION_PROPERTY))) return source;
            parts.put(CUSTOM_PROPERTIES_PART, writeFlowVersions(custom, versions));
            return writeParts(parts);
        } catch (DataException e) {
            throw e;
        } catch (Exception e) {
            throw new DataException("写入签批页流程版本属性失败: " + e.getMessage());
        }
    }
 
    private Map<String, byte[]> readParts(byte[] source) throws Exception {
        Map<String, byte[]> result = new LinkedHashMap<>();
        try (ZipInputStream input = new ZipInputStream(new ByteArrayInputStream(source))) {
            ZipEntry entry;
            while ((entry = input.getNextEntry()) != null) {
                if (entry.isDirectory()) continue;
                ByteArrayOutputStream output = new ByteArrayOutputStream();
                byte[] buffer = new byte[8192];
                for (int read; (read = input.read(buffer)) >= 0; ) output.write(buffer, 0, read);
                result.put(entry.getName(), output.toByteArray());
            }
        }
        return result;
    }
 
    private byte[] writeParts(Map<String, byte[]> parts) throws Exception {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try (ZipOutputStream zip = new ZipOutputStream(output)) {
            for (Map.Entry<String, byte[]> part : parts.entrySet()) {
                zip.putNextEntry(new ZipEntry(part.getKey()));
                zip.write(part.getValue());
                zip.closeEntry();
            }
        }
        return output.toByteArray();
    }
 
    private Map<String, String> readFlowVersions(byte[] source) throws Exception {
        org.w3c.dom.Document document = parse(source);
        NodeList properties = document.getElementsByTagNameNS(CUSTOM_PROPERTIES_NAMESPACE, "property");
        Map<String, String> result = new LinkedHashMap<>();
        for (int index = 0; index < properties.getLength(); index++) {
            Element property = (Element) properties.item(index);
            String name = property.getAttribute("name");
            if (NEW_FLOW_VERSION_PROPERTY.equals(name) || EDIT_FLOW_VERSION_PROPERTY.equals(name)) {
                result.put(name, property.getTextContent().trim());
            }
        }
        return result;
    }
 
    private byte[] writeFlowVersions(byte[] source, Map<String, String> versions) throws Exception {
        org.w3c.dom.Document document = parse(source);
        Element root = document.getDocumentElement();
        NodeList properties = document.getElementsByTagNameNS(CUSTOM_PROPERTIES_NAMESPACE, "property");
        int maxPid = 1;
        for (int index = properties.getLength() - 1; index >= 0; index--) {
            Element property = (Element) properties.item(index);
            try { maxPid = Math.max(maxPid, Integer.parseInt(property.getAttribute("pid"))); }
            catch (NumberFormatException ignored) { }
            String name = property.getAttribute("name");
            if (NEW_FLOW_VERSION_PROPERTY.equals(name) || EDIT_FLOW_VERSION_PROPERTY.equals(name)) root.removeChild(property);
        }
        appendProperty(document, root, maxPid + 1, NEW_FLOW_VERSION_PROPERTY, versions.get(NEW_FLOW_VERSION_PROPERTY));
        appendProperty(document, root, maxPid + 2, EDIT_FLOW_VERSION_PROPERTY, versions.get(EDIT_FLOW_VERSION_PROPERTY));
        TransformerFactory factory = TransformerFactory.newInstance();
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        try { factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); } catch (IllegalArgumentException ignored) { }
        try { factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); } catch (IllegalArgumentException ignored) { }
        Transformer transformer = factory.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        ByteArrayOutputStream output = new ByteArrayOutputStream(source.length);
        transformer.transform(new DOMSource(document), new StreamResult(output));
        return output.toByteArray();
    }
 
    private void appendProperty(org.w3c.dom.Document document, Element root, int pid, String name, String value) {
        Element property = document.createElementNS(CUSTOM_PROPERTIES_NAMESPACE, "property");
        property.setAttribute("fmtid", FORMAT_ID);
        property.setAttribute("pid", String.valueOf(pid));
        property.setAttribute("name", name);
        Element text = document.createElementNS(DOC_PROPERTIES_TYPES_NAMESPACE, "vt:lpwstr");
        text.setTextContent(value);
        property.appendChild(text);
        root.appendChild(property);
    }
 
    private org.w3c.dom.Document parse(byte[] source) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        return factory.newDocumentBuilder().parse(new ByteArrayInputStream(source));
    }
}