系列第二篇,使用輕量級 JavaScript 樹組件 dTree 將 OPML 訂閱數據渲染為可交互的分組樹,奠定左側導航基礎。
用戶請求 → RssTeamServlet (Controller)
→ ReadXML (Service) 解析 OPML → List (Model)
→ left.jsp (View) 渲染 dTree 腳本
支援兩種 Foxmail 導出 OPML 格式差異:
<body><outline .../> 直接包含訂閱<body><outline title="分組"><outline .../></outline> 巢狀結構解析策略:判斷 outline 屬性數量 > 2 為葉子訂閱節點,否則為分組節點,遞歸處理。
public class RssTeamServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String realPath = getServletContext().getRealPath("/");
ReadXML reader = new ReadXML();
reader.ReadRssTeam(realPath + "/rss/"); // 讀取 WebRoot/rss/ 下所有 OPML
req.setAttribute("rssTemBeanList", reader.getRssTeamBeanList());
req.getRequestDispatcher("/left.jsp").forward(req, resp);
}
}
<%@ page import="java.util.List, com.tgb.rssreader.bean.*" %>
<link rel="StyleSheet" href="style/dtree.css" />
<script src="js/dtree.js"></script>
<div id="leftMenu" style="overflow:auto;">
<script>
d = new dTree('d');
// 根節點
d.add(0, -1, '博客分組', '', '', '', 'img/blog.png');
<%
List teams = (List) request.getAttribute("rssTemBeanList");
int nodeId = 0;
for (RssTeamBean team : teams) {
nodeId++;
int parentId = nodeId;
%>
// 分組節點
d.add(<%=nodeId%>, 0, '<%=team.getTitle()%>', '', '', '',
'img/open.png', 'img/close.png');
<%
for (RssBean rss : team.getRssBeanList()) {
nodeId++;
%>
// 訂閱葉子節點
d.add(<%=nodeId%>, <%=parentId%>, '<%=rss.getTitle()%>',
'<%=rss.getXmlUrl()%>', '<%=rss.getText()%>', 'contenthtml');
<%
}
}
%>
document.write(d);
</script>
</div>
d.add(id, pid, name, url, title, target, icon, iconOpen, open);
// id: 唯一節點ID
// pid: 父節點ID (根節點為 -1 或 0)
// name: 顯示文本
// url: 點擊跳轉地址 (葉子節點為 RSS XML URL)
// title: 鼠標懸停提示
// target: 目標框架名 (此處為中間框架 'contenthtml')
// icon/iconOpen: 關閉/展開狀態圖標
// open: 初始是否展開 (true/false)
<frameset cols="220,*,500">
<frame src="servlet/RssTeamServlet" name="leftFrame" /> <!-- 左側樹 -->
<frame src="middle.jsp" name="middleFrame" /> <-- 中間列表 -->
<frame src="content.jsp" name="contenthtml" /> <-- 右側正文 -->
</frameset>

img/ 目錄可訪問