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;
|
}
|
}
|