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 PROPERTIES = Set.of("rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblPrExChange", "tblGridChange", "trPrChange", "tcPrChange"); private static final Set 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 children(Node node) { List 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; } }