AS3 XML 分析的最佳实践

Best Practices for AS3 XML Parsing

提问人:Jeff Winkworth 提问时间:8/20/2008 最后编辑:Adam LearJeff Winkworth 更新时间:3/9/2016 访问量:18190

问:

我在 flash 中解析各种类型的 XML(特别是 FeedBurner RSS 文件和 YouTube 数据 API 响应)时遇到了一些麻烦。我正在使用 a 加载 XML 文件,并在创建新的 XML 对象时。75% 的时间这工作正常,我时不时地会遇到这种类型的异常:URLLoaderEvent.COMPLETE

TypeError: Error #1085: The element type "link" must be terminated by the matching end-tag "</link>".

我们认为问题在于 XML 很大,并且可能在实际从 XML 下载之前触发了该事件。我们想出的唯一解决方案是在事件上设置一个计时器,在开始解析数据之前基本上“等待几秒钟”。当然,这不可能是最好的方法。Event.COMPLETEURLLoader

有没有万无一失的方法可以在Flash中解析XML?

更新:2008 年 9 月 2 日我们得出以下结论,此时在代码中触发了 excption:

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


@Theo:感谢您的 ignoreWhitespace 提示。此外,我们已确定该事件在准备就绪之前被调用(我们做了一些测试跟踪mainXMLLoader.bytesLoaded + "/" + mainXMLLoader.bytesLoaded

XML Flash ActionScript-3 RSS 文件解析

评论


答:

1赞 Re0sless 8/20/2008 #1

您是否尝试过检查加载的字节数是否与总字节数相同?

URLLoader.bytesLoaded == URLLoader.bytesTotal

这应该告诉您文件是否已完成加载,它无助于提前触发 compleate 事件,但它应该告诉您是否是读取的 xml 有问题。

我不确定它是否适用于域,因为我的 xml 始终位于同一站点上。

评论

1赞 matt lohkamp 9/25/2008
旁注:bytesLoaded 在开始加载之前也与 bytesTotal 相同(它们都是 0),所以我会检查 onComplete 条件,其中: URLLoader.bytesLoaded == URLLoader.bytesTotal && bytesTotal > 0
0赞 vanhornRF 8/21/2008 #2

正如您在问题中提到的,问题很可能是您的程序在实际完全下载之前正在查看 XML,我不知道是否有一种万无一失的方法来“解析”XML,因为代码的解析部分很可能很好,这只是它是否实际下载的问题。

您可以尝试使用 ProgressEvent.PROGRESS 事件在下载 XML 时持续监视它,然后按照 Re0sless 的建议,检查 bytesLoaded 与 bytesTotal,并在两个数字相等时开始 XML 分析,而不是使用 Event.COMPLETE 事件。

无论域如何,您都应该能够获得 bytesLoaded 和 bytesTotal 数字,如果您可以访问该文件,则可以访问其字节信息。

1赞 Brian Warshaw 8/21/2008 #3

令我担心的是,它可能会在完成加载之前触发 Event.COMPLETE,这让我想知道加载是否超时。

问题多久发生一次?你能不能在前一刻成功,下一刻就失败了?

出于测试目的,请尝试跟踪处理程序方法顶部的 和 。如果它们不匹配,则表示您知道事件过早触发。如果是这种情况,您可以侦听 URLLoader 的进度事件。在处理程序中检查 ,只有在加载真正完成后才解析 XML。当然,这很可能类似于 URLLoader 在触发之前正在做的事情,但如果它被破坏,您可以尝试滚动自己的。URLLoader.bytesLoadedURLLoader.bytesTotalEvent.COMPLETEbytesLoadedbytesTotalEvent.COMPLETE

请告诉我们您的发现。如果可以的话,请粘贴一些源代码。我们也许能够发现一些值得注意的东西。

0赞 Theo 8/21/2008 #4

如果您可以发布更多代码,我们也许能够找到问题所在。

要测试的另一件事(除了跟踪)是在处理程序中跟踪加载程序的属性,只是为了查看XML数据是否实际正确加载,例如检查是否存在。bytesTotaldataEvent.COMPLETE</link>

0赞 Jeff Winkworth 8/22/2008 #5

@Brian Warshaw:这个问题只发生大约10-20%的时间。有时它会打嗝,只需重新加载应用程序就可以正常工作,其他时候我会花半小时一遍又一遍地重新加载应用程序,但无济于事。

这是原始代码(当我问这个问题时):

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

1赞 Theo 8/22/2008 #6

顺便说一句,这句话没有效果:

XML.ignoreWhitespace;

因为是一个属性。您必须将其设置为如下所示:ignoreWhitespacetrue

XML.ingoreWhitespace = true;
0赞 Theo 8/22/2008 #7

除非加载程序已满载,否则确实不应该调用处理程序,这毫无意义。您是否确认它实际上没有完全加载(通过查看 vs. 您跟踪的值)?如果在此之前调度事件,则是一个错误。Event.COMPLETEbytesLoadedbytesTotalEvent.COMPLETEbytesLoaded == bytesTotal

很好,你已经让它与计时器一起工作,但你需要它很奇怪。

0赞 Theo 8/24/2008 #8

我建议您在 https://bugs.adobe.com/flashplayer/ 提交错误报告,因为在加载所有字节之前,该事件确实不应该触发。与此同时,我想你必须忍受计时器。您可以通过侦听进度事件来做同样的事情,这也许可以使您不必自己处理计时器。

0赞 Brian Hodge 9/25/2008 #9

您可以在 XML 文档的末尾设置一个唯一的元素命名空间,该命名空间具有一个属性“value”等于“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);
   }
}

我希望这对您有所帮助,但真正的希望是让足够多的人知道这个问题。不能依靠事件是一件可悲的事情。不过,我必须说,从我所听说的 XML 来看,它在大规模上并不是很理想,并且相信这是当您需要像 AMFPHP 这样的东西来序列化数据时。

希望这有帮助!请记住,这里的想法是,我们知道 XML 中的最后一个子元素是什么,因为我们设置了它!我们没有理由不能访问最后一个子元素/元素,但如果我们不能,我们必须假设 XML 确实不完整,然后我们强制它再次加载。

0赞 enzuguri 8/17/2009 #10

有时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 添加缓存破坏程序?

只是一些建议...