目前格式:HTML/XML
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import javax.xml.parsers.*;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
public class XmlTreeParser {
private static final DateTimeFormatter FMT_PRIMARY = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
private static final DateTimeFormatter FMT_FALLBACK = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** 從字串解析,回傳 flat List<TreeNodeBean> */
public static List<TreeNodeBean> parseFromString(String xml) throws Exception {
if (xml == null || xml.isBlank()) return List.of();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 安全性:關閉外部實體與 DTD,避免 XXE(在內網系統很常見)
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);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
doc.getDocumentElement().normalize();
NodeList list = doc.getElementsByTagName("TreeSubdir");
List<TreeNodeBean> result = new ArrayList<>();
for (int i = 0; i < list.getLength(); i++) {
Element e = (Element) list.item(i);
String sftPath = getTagValue(e, "sft_path");
String iftSid = getTagValue(e, "ift_sid");
String sftName = getTagValue(e, "sft_name");
String iKBCount = getTagValue(e, "iKBCount");
String dInit = getTagValue(e, "dinit_time");
String dModi = getTagValue(e, "dmodi_time");
String iftPrev = getTagValue(e, "ift_prev");
TreeNodeBean bean = new TreeNodeBean();
bean.setId(iftSid);
bean.setParentId(iftPrev);
bean.setName(sftName);
// 路徑:去頭尾反斜線,split 之後過濾空白
bean.setPathRaw(sftPath);
bean.setPathParts(splitPathSafe(sftPath));
// KBCount:若無法轉數字就設 null(或改成 0)
try {
bean.setKbCount(iKBCount == null || iKBCount.isBlank() ? null : Integer.parseInt(iKBCount.trim()));
} catch (NumberFormatException ex) {
bean.setKbCount(null);
}
// 時間:允許空值;先用主格式,失敗再 fallback
bean.setInitTime(parseDateTimeSafely(dInit));
bean.setModiTime(parseDateTimeSafely(dModi));
result.add(bean);
}
return result;
}
/** 單一標籤取值(忽略不存在) */
private static String getTagValue(Element e, String tag) {
NodeList nl = e.getElementsByTagName(tag);
if (nl.getLength() > 0 && nl.item(0) != null) {
String s = nl.item(0).getTextContent();
return s == null ? "" : s.trim();
}
return "";
}
/** 解析 \3808\3811\3813\ → ["3808","3811","3813"](容錯) */
private static List<String> splitPathSafe(String raw) {
if (raw == null) return List.of();
String s = raw.trim();
if (s.startsWith("\\")) s = s.substring(1);
if (s.endsWith("\\")) s = s.substring(0, s.length() - 1);
if (s.isEmpty()) return List.of();
String[] parts = s.split("\\\\"); // 兩個反斜線為 regex 裡的 '\'
List<String> out = new ArrayList<>(parts.length);
for (String p : parts) {
if (p != null && !p.isBlank()) out.add(p.trim());
}
return out;
}
/** 安全解析時間:空字串→null;主格式失敗→fallback;都失敗→null */
private static LocalDateTime parseDateTimeSafely(String s) {
if (s == null || s.isBlank()) return null;
String v = s.trim();
try {
return LocalDateTime.parse(v, FMT_PRIMARY);
} catch (DateTimeParseException e) {
try {
return LocalDateTime.parse(v, FMT_FALLBACK);
} catch (DateTimeParseException ignored) {
return null;
}
}
}
// --- Demo ----
public static void main(String[] args) throws Exception {
String xml = """
<root>
<TreeSubdir>
<sft_name>業務書櫃</sft_name>
<ift_sid>3808</ift_sid>
<ift_prev>0</ift_prev>
<sft_path>\\</sft_path>
<iKBCount>1</iKBCount>
<dinit_time>2022/04/28 11:20:59</dinit_time>
<dmodi_time>2024/01/03 17:19:03</dmodi_time>
</TreeSubdir>
<TreeSubdir>
<sft_name>M#</sft_name>
<ift_sid>3809</ift_sid>
<ift_prev>3808</ift_prev>
<sft_path>\\3808\\</sft_path>
<iKBCount>4</iKBCount>
<dinit_time>2022/04/28 11:20:59</dinit_time>
<dmodi_time>2024/01/03 17:19:03</dmodi_time>
</TreeSubdir>
</root>
""";
List<TreeNodeBean> flat = parseFromString(xml);
// 建樹 + 印樹(沿用你前面那組方法)
List<TreeNodeBean> roots = TreeUtils.buildTree(flat);
TreeUtils.printTree(roots, 0);
// 檢核任一節點 path
Map<String, TreeNodeBean> idx = TreeUtils.buildIndex(flat);
for (TreeNodeBean n : flat) {
boolean ok = TreeUtils.validateSftPath(n, idx);
System.out.println(n.getId() + " path check = " + ok + " raw=" + n.getPathRaw() +
" -> " + TreeUtils.getPathIdsFromRoot(n, idx, false));
}
}
}
aW1wb3J0IG9yZy53M2MuZG9tLio7CgppbXBvcnQgb3JnLnhtbC5zYXguSW5wdXRTb3VyY2U7CgppbXBvcnQgamF2YXgueG1sLnBhcnNlcnMuKjsKCmltcG9ydCBqYXZhLmlvLlN0cmluZ1JlYWRlcjsKCmltcG9ydCBqYXZhLnRpbWUuTG9jYWxEYXRlVGltZTsKCmltcG9ydCBqYXZhLnRpbWUuZm9ybWF0LkRhdGVUaW1lRm9ybWF0dGVyOwoKaW1wb3J0IGphdmEudGltZS5mb3JtYXQuRGF0ZVRpbWVQYXJzZUV4Y2VwdGlvbjsKCmltcG9ydCBqYXZhLnV0aWwuKjsKCgoKcHVibGljIGNsYXNzIFhtbFRyZWVQYXJzZXIgewoKCgogICAgcHJpdmF0ZSBzdGF0aWMgZmluYWwgRGF0ZVRpbWVGb3JtYXR0ZXIgRk1UX1BSSU1BUlkgPSBEYXRlVGltZUZvcm1hdHRlci5vZlBhdHRlcm4oInl5eXkvTU0vZGQgSEg6bW06c3MiKTsKCiAgICBwcml2YXRlIHN0YXRpYyBmaW5hbCBEYXRlVGltZUZvcm1hdHRlciBGTVRfRkFMTEJBQ0sgPSBEYXRlVGltZUZvcm1hdHRlci5vZlBhdHRlcm4oInl5eXktTU0tZGQgSEg6bW06c3MiKTsKCgoKICAgIC8qKiDlvp7lrZfkuLLop6PmnpDvvIzlm57lgrMgZmxhdCBMaXN0PFRyZWVOb2RlQmVhbj4gKi8KCiAgICBwdWJsaWMgc3RhdGljIExpc3Q8VHJlZU5vZGVCZWFuPiBwYXJzZUZyb21TdHJpbmcoU3RyaW5nIHhtbCkgdGhyb3dzIEV4Y2VwdGlvbiB7CgogICAgICAgIGlmICh4bWwgPT0gbnVsbCB8fCB4bWwuaXNCbGFuaygpKSByZXR1cm4gTGlzdC5vZigpOwoKCgogICAgICAgIERvY3VtZW50QnVpbGRlckZhY3RvcnkgZmFjdG9yeSA9IERvY3VtZW50QnVpbGRlckZhY3RvcnkubmV3SW5zdGFuY2UoKTsKCgoKICAgICAgICAvLyDlronlhajmgKfvvJrpl5zplonlpJbpg6jlr6bpq5ToiIcgRFRE77yM6YG/5YWNIFhYRe+8iOWcqOWFp+e2suezu+e1seW+iOW4uOimi++8iQoKICAgICAgICBmYWN0b3J5LnNldEZlYXR1cmUoImh0dHA6Ly9hcGFjaGUub3JnL3htbC9mZWF0dXJlcy9kaXNhbGxvdy1kb2N0eXBlLWRlY2wiLCB0cnVlKTsKCiAgICAgICAgZmFjdG9yeS5zZXRGZWF0dXJlKCJodHRwOi8veG1sLm9yZy9zYXgvZmVhdHVyZXMvZXh0ZXJuYWwtZ2VuZXJhbC1lbnRpdGllcyIsIGZhbHNlKTsKCiAgICAgICAgZmFjdG9yeS5zZXRGZWF0dXJlKCJodHRwOi8veG1sLm9yZy9zYXgvZmVhdHVyZXMvZXh0ZXJuYWwtcGFyYW1ldGVyLWVudGl0aWVzIiwgZmFsc2UpOwoKICAgICAgICBmYWN0b3J5LnNldFhJbmNsdWRlQXdhcmUoZmFsc2UpOwoKICAgICAgICBmYWN0b3J5LnNldEV4cGFuZEVudGl0eVJlZmVyZW5jZXMoZmFsc2UpOwoKCgogICAgICAgIERvY3VtZW50QnVpbGRlciBidWlsZGVyID0gZmFjdG9yeS5uZXdEb2N1bWVudEJ1aWxkZXIoKTsKCiAgICAgICAgRG9jdW1lbnQgZG9jID0gYnVpbGRlci5wYXJzZShuZXcgSW5wdXRTb3VyY2UobmV3IFN0cmluZ1JlYWRlcih4bWwpKSk7CgogICAgICAgIGRvYy5nZXREb2N1bWVudEVsZW1lbnQoKS5ub3JtYWxpemUoKTsKCgoKICAgICAgICBOb2RlTGlzdCBsaXN0ID0gZG9jLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJUcmVlU3ViZGlyIik7CgoKCiAgICAgICAgTGlzdDxUcmVlTm9kZUJlYW4+IHJlc3VsdCA9IG5ldyBBcnJheUxpc3Q8PigpOwoKICAgICAgICBmb3IgKGludCBpID0gMDsgaSA8IGxpc3QuZ2V0TGVuZ3RoKCk7IGkrKykgewoKICAgICAgICAgICAgRWxlbWVudCBlID0gKEVsZW1lbnQpIGxpc3QuaXRlbShpKTsKCgoKICAgICAgICAgICAgU3RyaW5nIHNmdFBhdGggID0gZ2V0VGFnVmFsdWUoZSwgInNmdF9wYXRoIik7CgogICAgICAgICAgICBTdHJpbmcgaWZ0U2lkICAgPSBnZXRUYWdWYWx1ZShlLCAiaWZ0X3NpZCIpOwoKICAgICAgICAgICAgU3RyaW5nIHNmdE5hbWUgID0gZ2V0VGFnVmFsdWUoZSwgInNmdF9uYW1lIik7CgogICAgICAgICAgICBTdHJpbmcgaUtCQ291bnQgPSBnZXRUYWdWYWx1ZShlLCAiaUtCQ291bnQiKTsKCiAgICAgICAgICAgIFN0cmluZyBkSW5pdCAgICA9IGdldFRhZ1ZhbHVlKGUsICJkaW5pdF90aW1lIik7CgogICAgICAgICAgICBTdHJpbmcgZE1vZGkgICAgPSBnZXRUYWdWYWx1ZShlLCAiZG1vZGlfdGltZSIpOwoKICAgICAgICAgICAgU3RyaW5nIGlmdFByZXYgID0gZ2V0VGFnVmFsdWUoZSwgImlmdF9wcmV2Iik7CgoKCiAgICAgICAgICAgIFRyZWVOb2RlQmVhbiBiZWFuID0gbmV3IFRyZWVOb2RlQmVhbigpOwoKICAgICAgICAgICAgYmVhbi5zZXRJZChpZnRTaWQpOwoKICAgICAgICAgICAgYmVhbi5zZXRQYXJlbnRJZChpZnRQcmV2KTsKCiAgICAgICAgICAgIGJlYW4uc2V0TmFtZShzZnROYW1lKTsKCgoKICAgICAgICAgICAgLy8g6Lev5b6R77ya5Y676aCt5bC+5Y+N5pac57ea77yMc3BsaXQg5LmL5b6M6YGO5r++56m655m9CgogICAgICAgICAgICBiZWFuLnNldFBhdGhSYXcoc2Z0UGF0aCk7CgogICAgICAgICAgICBiZWFuLnNldFBhdGhQYXJ0cyhzcGxpdFBhdGhTYWZlKHNmdFBhdGgpKTsKCgoKICAgICAgICAgICAgLy8gS0JDb3VudO+8muiLpeeEoeazlei9ieaVuOWtl+WwseiorSBudWxs77yI5oiW5pS55oiQIDDvvIkKCiAgICAgICAgICAgIHRyeSB7CgogICAgICAgICAgICAgICAgYmVhbi5zZXRLYkNvdW50KGlLQkNvdW50ID09IG51bGwgfHwgaUtCQ291bnQuaXNCbGFuaygpID8gbnVsbCA6IEludGVnZXIucGFyc2VJbnQoaUtCQ291bnQudHJpbSgpKSk7CgogICAgICAgICAgICB9IGNhdGNoIChOdW1iZXJGb3JtYXRFeGNlcHRpb24gZXgpIHsKCiAgICAgICAgICAgICAgICBiZWFuLnNldEtiQ291bnQobnVsbCk7CgogICAgICAgICAgICB9CgoKCiAgICAgICAgICAgIC8vIOaZgumWk++8muWFgeioseepuuWAvO+8m+WFiOeUqOS4u+agvOW8j++8jOWkseaVl+WGjSBmYWxsYmFjawoKICAgICAgICAgICAgYmVhbi5zZXRJbml0VGltZShwYXJzZURhdGVUaW1lU2FmZWx5KGRJbml0KSk7CgogICAgICAgICAgICBiZWFuLnNldE1vZGlUaW1lKHBhcnNlRGF0ZVRpbWVTYWZlbHkoZE1vZGkpKTsKCgoKICAgICAgICAgICAgcmVzdWx0LmFkZChiZWFuKTsKCiAgICAgICAgfQoKCgogICAgICAgIHJldHVybiByZXN1bHQ7CgogICAgfQoKCgogICAgLyoqIOWWruS4gOaomeexpOWPluWAvO+8iOW/veeVpeS4jeWtmOWcqO+8iSAqLwoKICAgIHByaXZhdGUgc3RhdGljIFN0cmluZyBnZXRUYWdWYWx1ZShFbGVtZW50IGUsIFN0cmluZyB0YWcpIHsKCiAgICAgICAgTm9kZUxpc3QgbmwgPSBlLmdldEVsZW1lbnRzQnlUYWdOYW1lKHRhZyk7CgogICAgICAgIGlmIChubC5nZXRMZW5ndGgoKSA+IDAgJiYgbmwuaXRlbSgwKSAhPSBudWxsKSB7CgogICAgICAgICAgICBTdHJpbmcgcyA9IG5sLml0ZW0oMCkuZ2V0VGV4dENvbnRlbnQoKTsKCiAgICAgICAgICAgIHJldHVybiBzID09IG51bGwgPyAiIiA6IHMudHJpbSgpOwoKICAgICAgICB9CgogICAgICAgIHJldHVybiAiIjsKCiAgICB9CgoKCiAgICAvKiog6Kej5p6QIFwzODA4XDM4MTFcMzgxM1wg4oaSIFsiMzgwOCIsIjM4MTEiLCIzODEzIl3vvIjlrrnpjK/vvIkgKi8KCiAgICBwcml2YXRlIHN0YXRpYyBMaXN0PFN0cmluZz4gc3BsaXRQYXRoU2FmZShTdHJpbmcgcmF3KSB7CgogICAgICAgIGlmIChyYXcgPT0gbnVsbCkgcmV0dXJuIExpc3Qub2YoKTsKCiAgICAgICAgU3RyaW5nIHMgPSByYXcudHJpbSgpOwoKICAgICAgICBpZiAocy5zdGFydHNXaXRoKCJcXCIpKSBzID0gcy5zdWJzdHJpbmcoMSk7CgogICAgICAgIGlmIChzLmVuZHNXaXRoKCJcXCIpKSBzID0gcy5zdWJzdHJpbmcoMCwgcy5sZW5ndGgoKSAtIDEpOwoKICAgICAgICBpZiAocy5pc0VtcHR5KCkpIHJldHVybiBMaXN0Lm9mKCk7CgoKCiAgICAgICAgU3RyaW5nW10gcGFydHMgPSBzLnNwbGl0KCJcXFxcIik7IC8vIOWFqeWAi+WPjeaWnOe3mueCuiByZWdleCDoo6HnmoQgJ1wnCgogICAgICAgIExpc3Q8U3RyaW5nPiBvdXQgPSBuZXcgQXJyYXlMaXN0PD4ocGFydHMubGVuZ3RoKTsKCiAgICAgICAgZm9yIChTdHJpbmcgcCA6IHBhcnRzKSB7CgogICAgICAgICAgICBpZiAocCAhPSBudWxsICYmICFwLmlzQmxhbmsoKSkgb3V0LmFkZChwLnRyaW0oKSk7CgogICAgICAgIH0KCiAgICAgICAgcmV0dXJuIG91dDsKCiAgICB9CgoKCiAgICAvKiog5a6J5YWo6Kej5p6Q5pmC6ZaT77ya56m65a2X5Liy4oaSbnVsbO+8m+S4u+agvOW8j+WkseaVl+KGkmZhbGxiYWNr77yb6YO95aSx5pWX4oaSbnVsbCAqLwoKICAgIHByaXZhdGUgc3RhdGljIExvY2FsRGF0ZVRpbWUgcGFyc2VEYXRlVGltZVNhZmVseShTdHJpbmcgcykgewoKICAgICAgICBpZiAocyA9PSBudWxsIHx8IHMuaXNCbGFuaygpKSByZXR1cm4gbnVsbDsKCiAgICAgICAgU3RyaW5nIHYgPSBzLnRyaW0oKTsKCiAgICAgICAgdHJ5IHsKCiAgICAgICAgICAgIHJldHVybiBMb2NhbERhdGVUaW1lLnBhcnNlKHYsIEZNVF9QUklNQVJZKTsKCiAgICAgICAgfSBjYXRjaCAoRGF0ZVRpbWVQYXJzZUV4Y2VwdGlvbiBlKSB7CgogICAgICAgICAgICB0cnkgewoKICAgICAgICAgICAgICAgIHJldHVybiBMb2NhbERhdGVUaW1lLnBhcnNlKHYsIEZNVF9GQUxMQkFDSyk7CgogICAgICAgICAgICB9IGNhdGNoIChEYXRlVGltZVBhcnNlRXhjZXB0aW9uIGlnbm9yZWQpIHsKCiAgICAgICAgICAgICAgICByZXR1cm4gbnVsbDsKCiAgICAgICAgICAgIH0KCiAgICAgICAgfQoKICAgIH0KCgoKICAgIC8vIC0tLSBEZW1vIC0tLS0KCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgbWFpbihTdHJpbmdbXSBhcmdzKSB0aHJvd3MgRXhjZXB0aW9uIHsKCiAgICAgICAgU3RyaW5nIHhtbCA9ICIiIgoKICAgICAgICAgICAgPHJvb3Q+CgogICAgICAgICAgICAgIDxUcmVlU3ViZGlyPgoKICAgICAgICAgICAgICAgIDxzZnRfbmFtZT7mpa3li5nmm7jmq4M8L3NmdF9uYW1lPgoKICAgICAgICAgICAgICAgIDxpZnRfc2lkPjM4MDg8L2lmdF9zaWQ+CgogICAgICAgICAgICAgICAgPGlmdF9wcmV2PjA8L2lmdF9wcmV2PgoKICAgICAgICAgICAgICAgIDxzZnRfcGF0aD5cXDwvc2Z0X3BhdGg+CgogICAgICAgICAgICAgICAgPGlLQkNvdW50PjE8L2lLQkNvdW50PgoKICAgICAgICAgICAgICAgIDxkaW5pdF90aW1lPjIwMjIvMDQvMjggMTE6MjA6NTk8L2Rpbml0X3RpbWU+CgogICAgICAgICAgICAgICAgPGRtb2RpX3RpbWU+MjAyNC8wMS8wMyAxNzoxOTowMzwvZG1vZGlfdGltZT4KCiAgICAgICAgICAgICAgPC9UcmVlU3ViZGlyPgoKICAgICAgICAgICAgICA8VHJlZVN1YmRpcj4KCiAgICAgICAgICAgICAgICA8c2Z0X25hbWU+TSM8L3NmdF9uYW1lPgoKICAgICAgICAgICAgICAgIDxpZnRfc2lkPjM4MDk8L2lmdF9zaWQ+CgogICAgICAgICAgICAgICAgPGlmdF9wcmV2PjM4MDg8L2lmdF9wcmV2PgoKICAgICAgICAgICAgICAgIDxzZnRfcGF0aD5cXDM4MDhcXDwvc2Z0X3BhdGg+CgogICAgICAgICAgICAgICAgPGlLQkNvdW50PjQ8L2lLQkNvdW50PgoKICAgICAgICAgICAgICAgIDxkaW5pdF90aW1lPjIwMjIvMDQvMjggMTE6MjA6NTk8L2Rpbml0X3RpbWU+CgogICAgICAgICAgICAgICAgPGRtb2RpX3RpbWU+MjAyNC8wMS8wMyAxNzoxOTowMzwvZG1vZGlfdGltZT4KCiAgICAgICAgICAgICAgPC9UcmVlU3ViZGlyPgoKICAgICAgICAgICAgPC9yb290PgoKICAgICAgICAiIiI7CgoKCiAgICAgICAgTGlzdDxUcmVlTm9kZUJlYW4+IGZsYXQgPSBwYXJzZUZyb21TdHJpbmcoeG1sKTsKCgoKICAgICAgICAvLyDlu7rmqLkgKyDljbDmqLnvvIjmsr/nlKjkvaDliY3pnaLpgqPntYTmlrnms5XvvIkKCiAgICAgICAgTGlzdDxUcmVlTm9kZUJlYW4+IHJvb3RzID0gVHJlZVV0aWxzLmJ1aWxkVHJlZShmbGF0KTsKCiAgICAgICAgVHJlZVV0aWxzLnByaW50VHJlZShyb290cywgMCk7CgoKCiAgICAgICAgLy8g5qqi5qC45Lu75LiA56+A6bueIHBhdGgKCiAgICAgICAgTWFwPFN0cmluZywgVHJlZU5vZGVCZWFuPiBpZHggPSBUcmVlVXRpbHMuYnVpbGRJbmRleChmbGF0KTsKCiAgICAgICAgZm9yIChUcmVlTm9kZUJlYW4gbiA6IGZsYXQpIHsKCiAgICAgICAgICAgIGJvb2xlYW4gb2sgPSBUcmVlVXRpbHMudmFsaWRhdGVTZnRQYXRoKG4sIGlkeCk7CgogICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4obi5nZXRJZCgpICsgIiBwYXRoIGNoZWNrID0gIiArIG9rICsgIiByYXc9IiArIG4uZ2V0UGF0aFJhdygpICsKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIC0+ICIgKyBUcmVlVXRpbHMuZ2V0UGF0aElkc0Zyb21Sb290KG4sIGlkeCwgZmFsc2UpKTsKCiAgICAgICAgfQoKICAgIH0KCn0K