AS3 XML解析的最佳做法
我在解析Flash中的各种XML(特别是FeedBurner RSS文件和YouTube Data API响应)时遇到了一些麻烦。我正在使用" URLLoader"加载XML文件,并在" Event.COMPLETE"创建新的XML对象时使用。 75%的时间可以正常工作,而且我时不时地会遇到这种类型的异常:
TypeError: Error #1085: The element type "link" must be terminated by the matching end-tag "</link>".
我们认为问题在于XML很大,在从URLLoader实际下载XML之前,可能触发了Event.COMPLETE事件。我们提出的唯一解决方案是在事件发生时触发一个计时器,并且在开始解析数据之前实质上是"等待几秒钟"。当然,这不是最好的方法。
是否有任何可靠的方法可以在Flash中解析XML?
2008年9月2日更新我们已经得出以下结论,此时代码中已激发了例外:
data = new XML(mainXMLLoader.data); // calculate the total number of entries. for each (var i in data.channel.item){ _totalEntries++; }
我已在该部分周围放置了try / catch语句,并且当前在发生错误时在屏幕上显示错误消息。我的问题是,如果bytesLoaded == bytesTotal
,一个不完整的文件将如何到达这一点?
我已经用状态报告更新了原始问题;我猜可能还有另一个问题,就是有一种方法可以确定在访问数据之前是否正确解析了XML对象(以防错误是我的循环开始,实际上是在将XML解析为XML之前先开始计算对象的数量)物体)?
@Theo:感谢ignoreWhitespace提示。另外,我们已经确定该事件在事件准备就绪之前已被调用(我们做了一些测试,以跟踪mainXMLLoader.bytesLoaded +" /" + mainXMLLoader.bytesLoaded
解决方案
回答
我们是否尝试过检查加载的字节数与总字节数相同?
URLLoader.bytesLoaded == URLLoader.bytesTotal
那应该告诉我们文件是否已经完成加载,它不会帮助compleate事件触发到很早,但是应该告诉我们是否已读取xml问题。
我不确定它是否可以在域中工作,因为我的xml始终在同一站点上。
回答
正如我们在问题中提到的那样,问题很可能是程序在实际完全下载XML之前就已经在查看XML,我不知道有一种"解析" XML的可靠方法,因为代码很可能没问题,这仅取决于它是否已实际下载。
我们可以尝试使用ProgressEvent.PROGRESS事件来持续监视XML的下载过程,然后按照Re0sless的建议,检查bytesLoaded与bytesTotal并在两个数字相等时开始XML解析,而不是使用Event.COMPLETE事件。
无论使用哪个域,都应该能够很好地获取bytesLoaded和bytesTotal数字,如果可以访问文件,则可以访问其字节信息。
回答
对我而言,最担心的是它可能在加载完成之前触发Event.COMPLETE,这使我想知道加载是否超时。
问题多久发生一次?我们能否在同一时刻获得成功,然后再通过相同的提要而失败?
为了进行测试,请尝试在Event.COMPLETE处理程序方法顶部跟踪URLLoader.bytesLoaded和URLLoader.bytesTotal。如果它们不匹配,则说明该事件正在过早触发。在这种情况下,我们可以侦听URLLoader的progress事件。在处理程序中对照" bytesTotal"检查" bytesLoaded",仅在加载真正完成后才解析XML。当然,这很可能类似于URLLoader在触发Event.COMPLETE
之前所做的工作,但是如果损坏了,我们可以尝试自己滚动。
请让我们知道我们发现了什么。如果可以的话,请粘贴一些源代码。我们也许可以发现一些值得注意的东西。
回答
如果我们可以发布更多代码,我们也许可以找到问题。
要测试的另一件事(除了跟踪" bytesTotal"之外)是在" Event.COMPLETE"处理程序中跟踪加载程序的" data"属性,只是为了查看XML数据是否已正确加载,例如检查是否存在XML数据。 </ link>`。
回答
@Brian Warshaw:此问题仅发生在大约10-20%的时间。有时会打and,只是简单地重新加载应用程序就可以了,而其他时候,我会花半个小时来一次又一次地重新加载应用程序,但无济于事。
这是原始代码(当我问这个问题时):
public class BlogReader extends MovieClip { public static const DOWNLOAD_ERROR:String = "Download_Error"; public static const FEED_PARSED:String = "Feed_Parsed"; private var mainXMLLoader:URLLoader = new URLLoader(); public var data:XML; private var _totalEntries:Number = 0; public function BlogReader(url:String){ mainXMLLoader.addEventListener(Event.COMPLETE, LoadList); mainXMLLoader.addEventListener(IOErrorEvent.IO_ERROR, errorCatch); mainXMLLoader.load(new URLRequest(url)); XML.ignoreWhitespace; } private function errorCatch(e:IOErrorEvent){ trace("Oh noes! Yous gots no internets!"); dispatchEvent(new Event(DOWNLOAD_ERROR)); } private function LoadList(e:Event):void { data = new XML(e.target.data); // calculate the total number of entries. for each (var i in data.channel.item){ _totalEntries++; } dispatchEvent(new Event(FEED_PARSED)); } }
这是我根据Re0sless的原始回复(类似于所提到的一些建议)编写的代码:
public class BlogReader extends MovieClip { public static const DOWNLOAD_ERROR:String = "Download_Error"; public static const FEED_PARSED:String = "Feed_Parsed"; private var mainXMLLoader:URLLoader = new URLLoader(); public var data:XML; protected var _totalEntries:Number = 0; public function BlogReader(url:String){ mainXMLLoader.addEventListener(Event.COMPLETE, LoadList); mainXMLLoader.addEventListener(IOErrorEvent.IO_ERROR, errorCatch); mainXMLLoader.load(new URLRequest(url)); XML.ignoreWhitespace; } private function errorCatch(e:IOErrorEvent){ trace("Oh noes! Yous gots no internets!"); dispatchEvent(e); } private function LoadList(e:Event):void { isDownloadComplete(); } private function isDownloadComplete() { trace (mainXMLLoader.bytesLoaded + "/" + mainXMLLoader.bytesLoaded); if (mainXMLLoader.bytesLoaded == mainXMLLoader.bytesLoaded){ trace ("xml fully loaded"); data = new XML(mainXMLLoader.data); // calculate the total number of entries. for each (var i in data.channel.item){ _totalEntries++; } dispatchEvent(new Event(FEED_PARSED)); } else { trace ("xml not fully loaded, starting timer"); var t:Timer = new Timer(300, 1); t.addEventListener(TimerEvent.TIMER_COMPLETE, loaded); t.start(); } } private function loaded(e:TimerEvent){ trace ("timer finished, trying again"); e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, loaded); e.target.stop(); isDownloadComplete(); } }
我会指出,由于添加了确定mainXMLLoader.bytesLoaded == mainXMLLoader.bytesLoaded
的代码,所以我没有遇到这个问题,很难重现此错误,因此就我所知,我还没有修复任何问题,并且相反,只是添加了无用的代码。
回答
只是附带说明,此语句无效:
XML.ignoreWhitespace;
因为ignoreWhitespace
是一个属性。我们必须像这样将其设置为" true":
XML.ingoreWhitespace = true;
回答
除非加载程序完全加载,否则不应该调用Event.COMPLETE处理程序,这没有任何意义。我们是否确定它实际上没有完全加载(通过查看我们跟踪的" bytesLoaded"与" bytesTotal"值)?如果Event.COMPLETE
事件在bytesLoaded == bytesTotal
之前分派,那是一个错误。
很好,因为它可以与计时器一起使用,但是很奇怪,我们需要它。
回答
我建议我们在https://bugs.adobe.com/flashplayer/上提交错误报告,因为在加载所有字节之前不应该触发该事件。在此期间,我想我们必须忍受计时器。我们也许可以通过监听progress事件来执行相同的操作,这也许可以使我们不必自己处理计时器。
回答
我们可以在XML文档的最后设置一个唯一的元素名称空间,该名称空间的一个属性"值"等于" true"。
//The XML //Flash ignores the line that specifies the XML version and encoding so I have here as well. <parent> <child name="child1" /> <child name="child2" /> <child name="child3" /> <child name="child4" /> <documentEnd value="true" /> </parent> //Sorry about the spacing, but it is difficult to get XML to show. //Flash var loader:URLLoader = new URLLoader(); var request:URLRequest = new URLRequest('pathToXML/xmlFileName.xml'); var xml:XML; //Event Listener with weak reference set to true (5th parameter); //The above comment does not define a required practice, this is to aid with garbage collection. loader.addEventListener(Event.COMPLETE, onXMLLoadComplete, false, 0, true); loader.load(request); function onXMLLoadComplete(e:Event):void { xml = new XML(e.target.data); //Now we check the last element (child) to see if it is documentEnd. if(xml[xml.length()-1].documentEnd.@value == "true") { trace("Woot, it seems your xml made it!"); } else { //Attempt the load again because it seems it failed when it was unable to find documentEnd in the XML Object. loader.load(request); } }
我希望这对我们现在有帮助,但真正的希望是,有足够的人让Adobe知道此问题。无法依靠事件是一件可悲的事情。但是,我必须说,从我对XML的了解来看,它并不是大规模的最佳选择,并且相信这是在我们需要AMFPHP之类的数据进行序列化的时候。
希望这可以帮助!记住这里的想法是我们知道XML中的最后一个子元素/元素是因为我们对其进行了设置!没有理由我们不能访问最后一个孩子/元素,但是如果不能访问,我们必须假定XML确实没有完成,并迫使它再次加载。
回答
有时,RSS服务器页面可能无法吐出正确和有效的XML数据,特别是如果我们不断点击它,那么这可能不是错。我们是否尝试过在Web浏览器(最好使用xml验证器插件)中浏览页面以检查服务器响应始终有效?
我在这里只能看到的另一行是该行:
xml = new XML(event.target.data); //the data should already be XML, so only casting is necessary xml = XML(event.target.data);
我们是否也尝试过将urlloader dataFormat设置为URLLoaderDataFormat.TEXT,还添加了prama-no-cache的url标头和/或者在url中添加了缓存破坏符?
只是一些建议...