我們知道對RSS的訪問,最常用的就是通過RSS Feed。也就是一個URL的鏈接,這個鏈接指向一個XML文件,在這個文件中描述了所有RSS解析器必須知道的信息
那么解析器如何讀取這個XML文件并對其解析呢?特別是在當前RSS存在如此多的協(xié)議版本時。Informa的parsers包就提供了這樣的功能,首先來看一下Informa能夠支持的解析版本

可以看到目前Informa能夠支持的協(xié)議版本包括3類:
RSS 0.9.x / 2.0系
RSS 1.0系
Atom 0.3 / 1.0系
OPML 1.1
我們知道在RSS 0.9.x系協(xié)議中還包括了一些中間的版本,包括0.9.2, 0.9.3, 0.9.4等,但是目前Informa只支持0.9.1版本。根據(jù)Informa官網(wǎng)的文檔,這里面最常用的就是FeedParser類,就先研究這個。在這個類的API說明有這樣一段話:
Parser class which allows reading in of RSS news channels. The concrete rules how the XML elements map to our channel object model are delegated to version specific private classes.
從這段話就可以知道,F(xiàn)eedParser是一個facade類,這個類的工作就是根據(jù)讀入的RSS Feed的協(xié)議系和版本“委托(delegate)”給已經(jīng)硬編碼的規(guī)則(concrete rules)指定的解析器,而且這些解析器是私有的。這意味著:用戶不能直接調(diào)用一個諸如RSS_0_91_Parser類的實例來進行解析工作。
★FeedParser
FeedParser的一個重要工作就是加載XML解析引擎,目前比較常用的XML解析引擎有Jdom,Dom4J。Informa目前默認采用的是以Jdom為解析器。Informa允許你自行設置底層的解析器,但這個引擎必須實現(xiàn)SUN J2se API中的org.xml.sax.XMLReader借口。這個功能是通過方法setSaxDriverClassName來實現(xiàn)的。

/** *//**
* Sets the name of SAX2 Driver class to use for parsing. The class should
* implement XMLReader interface and should exist in current class-loading
* paths. The method will check these conditions before accepting specified
* class name.
*
* @param className name of SAX2 Driver class.
*
* @throws ClassNotFoundException if class isn't found in the class path.
* @throws ClassCastException if class isn't implementing XMLReader interface.
*/
public static synchronized void setSaxDriverClassName(String className)

throws ClassNotFoundException
{

// Check if class is available
Class saxDriverClass = Class.forName(className);

// Check if class implements necessary XMLReader interface
Class[] extendsImplements = saxDriverClass.getInterfaces();
boolean found = false;

for (int i = 0; !found && i < extendsImplements.length; i++)
{
Class parent = extendsImplements[i];
found = (parent == XMLReader.class);
}
if (!found)
throw new ClassCastException("Specified class " + className
+ " does not implement XMLReader.");

saxDriverClassName = className;
}
這個方法的步驟如下:
A.接收外界傳遞而來的class name并加載這個類
B.通過反射得到這個類實現(xiàn)的所有接口
C.在循環(huán)中檢查這個類是否實現(xiàn)了XMLReader接口
D.如果不符合條件則拋出異常
關于如何使用Java反射技術得到一個類的內(nèi)部信息,請參考
Sun J2se API---反射
在加載解析器成功后,可以使用parse方法來解析不同的RSS Feed了。Informa提供了若干個重載方法
可以看到parse方法中,必須的參數(shù)有ChannelBuilder和數(shù)據(jù)源。前者是用來接收解析后的結果的,把自己作為參數(shù)注冊到解析器,這樣解析器在解析完后就會調(diào)用該builder來構建相應的channel object。而后者是數(shù)據(jù)源,在SUN SAX API中有一個抽象接口:InputSource,它代表了可以接受的各種XML數(shù)據(jù)源。根據(jù)規(guī)定,SAX必須支持以二進制流(stream),字符流(character),系統(tǒng)ID(URL)為數(shù)據(jù)源,Informa還提供了從字符串URL,文件來構建SAXBuilder的方法。
這些不同數(shù)據(jù)源的parse方法都會調(diào)用另外一個重載方法:parse(ChannelBuilderIF, InputSource, URL)。下面來看這個方法的代碼

/** *//**
* Parse feed from input source with base location set and create channel.
*
* @param cBuilder specific channel builder to use.
* @param inpSource input source of data.
* @param baseLocation base location of feed.
*
* @return parsed channel.
*
* @throws IOException if IO errors occur.
* @throws ParseException if parsing is not possible.
*/
public static ChannelIF parse(ChannelBuilderIF cBuilder,
InputSource inpSource, URL baseLocation) throws IOException,

ParseException
{
// document reading without validation
SAXBuilder saxBuilder = new SAXBuilder(saxDriverClassName);

// turn off DTD loading
saxBuilder.setEntityResolver(new NoOpEntityResolver());


try
{
Document doc = saxBuilder.build(inpSource);
ChannelIF channel = parse(cBuilder, doc);
channel.setLocation(baseLocation);
return channel;

} catch (JDOMException e)
{
throw new ParseException("Problem parsing "
+ inpSource.getSystemId() + ": " + e);
}
}
這個方法首先使用前面指定的解析器引擎來創(chuàng)建SAXBuilder實例,然后關閉DTD加載(節(jié)省時間),接下來讀入數(shù)據(jù)源的數(shù)據(jù),構建出一個XML Document對象。最后把channelBuilder和這個XML Document對象一起作為參數(shù)傳遞給另一個私有的parse方法。當解析成功后返回的channel,其中已經(jīng)包含了該RSS Feed的所有信息。
那么這個私有的parse方法的作用是什么呢?其實這個方法只是簡單的“檢查-委托”作用




if (rootElement.startsWith("rss"))
{
String rssVersion = root.getAttribute("version").getValue();

if (rssVersion.indexOf("0.91") >= 0)
{
logger.info("Channel uses RSS root element (Version 0.91).");
return RSS_0_91_Parser.getInstance().parse(cBuilder, root);

} else if (rssVersion.indexOf("0.92") >= 0)
{
logger.info("Channel uses RSS root element (Version 0.92).");
// logger.warn("RSS 0.92 not fully supported yet, fall back to 0.91.");
// TODO: support RSS 0.92 when aware of all subtle differences.
return RSS_0_91_Parser.getInstance().parse(cBuilder, root);

} else if (rootElement.indexOf("0.93") >= 0)
{
logger.info("Channel uses RSS root element (Version 0.93).");
logger
.warn("RSS 0.93 not fully supported yet, fall back to 0.91.");
// TODO: support RSS 0.93 when aware of all subtle differences.

} else if (rootElement.indexOf("0.94") >= 0)
{
logger.info("Channel uses RSS root element (Version 0.94).");
logger
.warn("RSS 0.94 not fully supported yet, will use RSS 2.0");
// TODO: support RSS 0.94 when aware of all subtle differences.
return RSS_2_0_Parser.getInstance().parse(cBuilder, root);

} else if (rssVersion.indexOf("2.0") >= 0 || rssVersion.equals("2"))
{
logger.info("Channel uses RSS root element (Version 2.0).");
return RSS_2_0_Parser.getInstance().parse(cBuilder, root);

} else
{
throw new UnsupportedFormatException(
"Unsupported RSS version [" + rssVersion + "].");
}
}



簡單總結一下過程就是:
A.檢查是否RSS 0.9.x / 2.0系協(xié)議,委托給相應的解析器
B.檢查是否RSS 1.0協(xié)議,委托給對應的解析器
C.檢查是否Atom 0.3 / 1.0系協(xié)議,委托給相應的解析器
D.如果都不符合則拋出異常
關于這個方法還有一個地方需要加以注意的就是它必須是同步的。因為SAX規(guī)范規(guī)定:
在解析器解析一個XML文檔的過程中,不允許任何其他外部程序訪問XML文檔的內(nèi)容,直到這個文檔解析完畢。所以這個私有的parse方法的聲明中加了關鍵字synchronized,就是為了防止多個線程在解析器還沒有解析完文檔后就開始訪問。
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
posted on 2009-12-29 15:56
Paul Lin 閱讀(1488)
評論(0) 編輯 收藏 所屬分類:
J2SE