刘光辉
3 小时以前 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
package jnpf.fileStorage.dms;
 
import org.w3c.dom.*;
import java.io.IOException;
import java.util.*;
 
/** Rejects tracked edits in a copied OOXML part before the formal file is stored. */
final class DmsDocxRevisionRejector {
    private DmsDocxRevisionRejector() { }
    private static final Set<String> PROPERTIES = Set.of("rPrChange", "pPrChange", "sectPrChange", "tblPrChange",
            "tblPrExChange", "tblGridChange", "trPrChange", "tcPrChange");
    private static final Set<String> RANGES = Set.of("moveFromRangeStart", "moveFromRangeEnd", "moveToRangeStart", "moveToRangeEnd",
            "customXmlInsRangeStart", "customXmlInsRangeEnd", "customXmlDelRangeStart", "customXmlDelRangeEnd",
            "customXmlMoveFromRangeStart", "customXmlMoveFromRangeEnd", "customXmlMoveToRangeStart", "customXmlMoveToRangeEnd");
 
    static boolean reject(Document document) throws IOException {
        return visit(document.getDocumentElement());
    }
 
    private static boolean word(Node node) {
        return "http://schemas.openxmlformats.org/wordprocessingml/2006/main".equals(node.getNamespaceURI())
                || "http://purl.oclc.org/ooxml/wordprocessingml/main".equals(node.getNamespaceURI());
    }
    private static List<Element> children(Node node) {
        List<Element> out = new ArrayList<>();
        for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling())
            if (child instanceof Element e) out.add(e);
        return out;
    }
    private static Element child(Node node, String name) {
        return children(node).stream().filter(e -> word(e) && name.equals(e.getLocalName())).findFirst().orElse(null);
    }
    private static void remove(Node node) { node.getParentNode().removeChild(node); }
    private static void unwrap(Node node) {
        Node parent = node.getParentNode();
        while (node.hasChildNodes()) parent.insertBefore(node.getFirstChild(), node);
        parent.removeChild(node);
    }
    private static boolean visit(Element element) throws IOException {
        boolean changed = false;
        // Restore the old property snapshot before visiting it; current properties are discarded.
        for (Element change : children(element)) {
            if (word(change) && PROPERTIES.contains(change.getLocalName())) {
                Element previous = child(change, element.getLocalName());
                if (previous == null) throw new IOException("修订缺少原属性快照: " + change.getLocalName());
                Node restored = previous.cloneNode(true);
                element.getParentNode().replaceChild(restored, element);
                visit((Element) restored);
                return true;
            }
        }
        if (word(element)) {
            String name = element.getLocalName();
            if ("tr".equals(name) || "tc".equals(name)) {
                Element props = child(element, "tr".equals(name) ? "trPr" : "tcPr");
                String inserted = "tr".equals(name) ? "ins" : "cellIns";
                if (props != null && child(props, inserted) != null) { remove(element); return true; }
            }
            if ("p".equals(name)) {
                Element props = child(element, "pPr");
                Element runProps = props == null ? null : child(props, "rPr");
                Element insertedMark = runProps == null ? null : child(runProps, "ins");
                Element movedMark = runProps == null ? null : child(runProps, "moveTo");
                if (insertedMark != null || movedMark != null) {
                    Node next = element.getNextSibling();
                    while (next != null && !(next instanceof Element)) next = next.getNextSibling();
                    if (!(next instanceof Element following) || !word(following) || !"p".equals(following.getLocalName()))
                        throw new IOException("无法安全拒绝跨结构的段落标记修订");
                    if (insertedMark != null) remove(insertedMark);
                    if (movedMark != null) remove(movedMark);
                    // Reject the inserted/moved-to paragraph break: concatenate into the following paragraph,
                    // whose paragraph properties describe the retained original paragraph mark.
                    Element followingProps = child(following, "pPr");
                    Node anchor = followingProps == null ? following.getFirstChild() : followingProps.getNextSibling();
                    for (Node n = element.getFirstChild(); n != null;) {
                        Node nextChild = n.getNextSibling();
                        if (n != props) following.insertBefore(n, anchor);
                        n = nextChild;
                    }
                    remove(element);
                    return true;
                }
            }
            if ("cellMerge".equals(name) || "numberingChange".equals(name) || name.startsWith("customXml") && RANGES.contains(name))
                throw new IOException("暂不支持安全拒绝此结构修订: " + name);
            if (RANGES.contains(name)) { remove(element); return true; }
            if ("ins".equals(name) || "moveTo".equals(name) || "cellIns".equals(name)) {
                remove(element); return true;
            }
            if ("del".equals(name) || "moveFrom".equals(name) || "cellDel".equals(name)) {
                if ("trPr".equals(element.getParentNode().getLocalName()) || "rPr".equals(element.getParentNode().getLocalName())
                        || "tcPr".equals(element.getParentNode().getLocalName())) { remove(element); return true; }
                for (Element nested : children(element)) visit(nested);
                unwrap(element); return true;
            }
            if ("delText".equals(name) || "delInstrText".equals(name)) {
                String local = "delText".equals(name) ? "t" : "instrText";
                element.getOwnerDocument().renameNode(element, element.getNamespaceURI(),
                        element.getPrefix() == null ? local : element.getPrefix() + ":" + local);
                changed = true;
            }
        }
        for (Element nested : children(element)) {
            if (nested.getParentNode() == element) changed |= visit(nested);
        }
        return changed;
    }
}