AS3 XML解析的最佳做法

时间:2020-03-05 18:41:05  来源:igfitidea点击:

我在解析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中添加了缓存破坏符?

只是一些建议...