首先,我非常清楚,尝试手写一个XML解析器是一个糟糕的主意,并且ZA̡͊͠͝LGΌISͮ҉̯͈͕̹̘ͮ҉̯͈͕̹̘͇̹̺Ɲ̴ȳ̳͇̹̺Ɲ̴ȳ̳̘̚̚̚̚̚̚͠͠。。。。等等。
那就是说,我有一个任务,我应该抓住一个网页,删除标签(处理<p>
和<a href>
有点不同),并展示美丽,无标签文本。我不允许使用org.xml.sax包或任何类似的包。
我们班上还没有学过正则表达式,我的大多数同学都在用String.indexOf()
说出不圣洁的咒语。对我来说,破解基于事件的{X,HT} ML解析器似乎要容易得多(更好)。
所以我有一个Scanner
的网页流,并且为了简洁而删除了一些细节:
stream.useDelimiter("\r?\n|\r"); // Use platform-independent newlines
//as delimiter
// 1 2 3 4 5 6 7 8 9 10
String tagRE = "([^<>]*?)(<!?\\s*)(/?)(\\s*)(\\w*)(\\s*[^<>]*?)(/?)(\\s*)(>)([^<>]*)";
//(Reluctant-anything) < whitespace optional-/ whitespace (word) whitespace
//reluctant-anything > (greedy-anything)
fireOpenFileEvent();
Pattern tagPat = Pattern.compile(tagRE);
while(stream.hasNextLine())
{
if(stream.hasNext(tagPat))
{
String toParse = stream.next(tagPat);
Matcher m = tagPat.matcher(toParse);
if(! m.matches()) System.err.println("Impossible non-match!");
fireTextEvent(m.group(1));
String tag = m.group(5);
if(! m.group(7).equals("")) //Self-closing tag
{
fireTagEvent(new XMLElement(tag, false));
fireTagEvent(new XMLElement(tag, true));
}
else
{
fireTagEvent(new XMLElement(tag, m.group(3).equals("/")));
}
fireTextEvent(m.group(10));
}
else //No tags (regex doesn't match). Just plain text
{
fireTextEvent(stream.nextLine);
}
}
fireEOFEvent();
在许多情况下,这种方法很有效,除了一个 - 当一行上有多个标签时。我真的希望Scanner
不会将事情分解为令牌 - 并且对next(pattern)
的调用会根据需要消耗尽可能多的流以匹配。因此,如果某行为<b>Hello World!</b>
,则在一次迭代中匹配<b>Hello World!
,然后在下一次匹配</b>
。相反,它一次处理一行。由于整行与模式不匹配,因此它由else子句处理。并且没有标签被剥离。
那么最好的方法是什么?我可以使用某种神奇的分界符吗?我是否应该使正则表达式匹配任何带有标记的内容,切断第一个标记,然后递归处理字符串的其余部分?我应该尝试一个巨大的黑客,并取代每个“&lt;”用“\ n&lt;”?我一般都走错了路?
提前致谢。
答案 0 :(得分:1)
您使用的是错误的技术。没有“基于正则表达式的解析”这样的东西。解析和XML意味着堆栈,而正则表达式没有。使用正确的XML解析器或@Dabbler建议的XPath。
编辑:我错过了关于课堂作业的部分。在我看来,这不是一个精心设计的作业。你可能不知道解析,你不能使用为此目的提供的工具,结果代码并没有真正教你很多,除了关于indexOf()调用的unholy incantations,...这样做的方法是另一张海报所建议的一次一个字符:注意&lt;字符,开始保存标签名称,停在下一个空格或>,根据需要忽略或处理属性;开始处理内容;如果你打开一个开头&lt ;,推动所有状态并重启;当你点击结束/&gt;流行国家。
答案 1 :(得分:1)
当您调用next(Pattern)
方法时,您已告知扫描程序,下一个标记是下一个分隔符的所有内容;唯一的问题是,令牌是否与模式匹配?这与其他nextXXX()
方法一致(例如,如果下一个标记看起来不像nextInt()
,int
会失败),但每个人都希望next(Pattern)
以不同的方式工作。
我认为您正在寻找的方法是findWithinHorizon()
;它忽略了分隔符,只是找到下一个匹配,与Matcher的find()
方法相同。试试这个:抛弃所有hasNextLine()
,hasNext(Pattern)
的东西,改为使用这个框架:
String lastHit = stream.findWithinHorizon(tagRE, 0); // always use '0'
while (lastHit != null)
{
MatchResult lastMatch = stream.match();
// ...
lastHit = stream.findWithinHorizon(tagRE, 0);
}
填写您的事件触发代码,根据需要调整正则表达式,但不要使用任何Scanner的其他方法(除了打开和关闭流,即)。当你试图做任何复杂的事情时,大多数Scanner的API似乎都会妨碍你。
扫描仪的API可能会臃肿且不直观,但它有一个非常有用的功能:以这种方式使用它,它将继续从流中读取,不仅直到它找到匹配,而且直到它确定没有更长时间匹配可以从相同的起始位置进行。换句话说,它就像Matcher的find()
方法用静态字符串一样工作。在我所知道的所有其他正则表达式中,只有Boost提供了类似的东西。
答案 2 :(得分:0)
您是否必须使用RegEx,还是XPath / XSLT是一个选项?然后,如果您的输入是XML(或XHTML,那么),您需要做的就是将整个输入转换为字符串。这将消除所有标签和属性,留下元素的文本内容。