Java 內建正則引擎不支援遞迴/巢狀結構描述(Perl、.NET、PHP 支援)。若需從 HTML 字串提取巢狀標籤內容(如 <div>...</div> 內含另一組 <div>),直接用正則會失敗或誤匹配。
public class Tag {
private String value; // 標籤字串,如 ""
private int beginPos; // 起始索引
private int endPos; // 結束索引
public Tag(String value, int beginPos, int endPos) {
this.value = value; this.beginPos = beginPos; this.endPos = endPos;
}
// getter/setter 略
}
核心演算法:get 方法
public List get(String data, String stag, String etag) {
Stack work = new Stack<>(); // 匹配用堆疊
List allTags = new ArrayList<>(); // 所有標籤位置記錄
// 1. 轉義正則元字元
String nstag = stag.replaceAll("([\\*\\.\\+\\(\\)\\[\\]\\?\\{\\}\\^\\$\\|\\\\])", "\\\\$1");
String netag = etag.replaceAll("([\\*\\.\\+\\(\\)\\[\\]\\?\\{\\}\\^\\$\\|\\\\])", "\\\\$1");
String reg = "((?:" + nstag + ")|(?:" + netag + "))";
// 2. 找出所有標籤位置
Pattern p = Pattern.compile(reg, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(data);
while (m.find()) {
allTags.add(new Tag(m.group(0), m.start(), m.end()));
}
// 3. 堆疊匹配提取內容
List result = new ArrayList<>();
for (Tag t : allTags) {
if (stag.equalsIgnoreCase(t.getValue())) {
work.push(t); // 起始標籤入堆疊
} else if (etag.equalsIgnoreCase(t.getValue())) {
if (work.empty()) throw new RuntimeException("pos " + t.getBeginPos() + " tag not match start tag.");
Tag otag = work.pop(); // 配對起始標籤
if (work.empty()) { // 堆疊空 → 最外層匹配完成
result.add(data.substring(otag.getEndPos(), t.getBeginPos()));
}
}
}
if (!work.empty()) throw new RuntimeException("tag " + work.pop().getValue() + " not match.");
return result;
}
實戰測試
String data = "abcd1234";
List res = get(data, "", "");
// 返回兩個元素:
// 1. "abcd"
// 2. "1234"
關鍵細節
- 元字元轉義:第 16-17 行自動為標籤字串中的正則特殊字元加反斜線,避免編譯錯誤
- 大小寫不敏感:
Pattern.CASE_INSENSITIVE 適應 HTML 大小寫不一
- 錯誤檢測:堆疊最終非空或提前空,均拋出異常提示位置
適用場景
HTML/XML 片段解析、模板引擎標籤提取、程式碼區塊分析等所有「巢狀成對標記」場景。