用于基于正则表达式的XML解析的分隔符?

时间:2011-10-08 17:48:35

标签: java regex xml-parsing java.util.scanner

首先,我非常清楚,尝试手写一个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;”?我一般都走错了路?

提前致谢。

3 个答案:

答案 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,那么),您需要做的就是将整个输入转换为字符串。这将消除所有标签和属性,留下元素的文本内容。