关于flash:AS3 XML解析的最佳实践

关于flash:AS3 XML解析的最佳实践

Best Practices for AS3 XML Parsing

我在解析 Flash 中的各种类型的 XML(特别是 FeedBurner RSS 文件和 YouTube 数据 API 响应)时遇到了一些问题。我正在使用 URLLoader 加载 XML 文件,并在 Event.COMPLETE 创建一个新的 XML 对象。 75% 的时间都可以正常工作,而且我时不时会遇到这种类型的异常:

1
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 日更新 我们得出以下结论,此时代码中的 excption 被触发:

1
2
3
4
5
6
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


只是一个旁注,这个声明没有效果:

1
XML.ignoreWhitespace;

因为 ignoreWhitespace 是一个属性。您必须像这样将其设置为 true

1
XML.ingoreWhitespace = true;

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

问题多久发生一次?您能否在同一时间获得成功,然后在下一刻以相同的提要失败?

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

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


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

1
URLLoader.bytesLoaded == URLLoader.bytesTotal

这应该告诉你文件是否已经完成加载,它不会帮助完成事件提前触发,但它应该告诉你它是否是读取 xml 的问题。

我不确定它是否适用于域,因为我的 xml 总是在同一个站点上。


有时 RSS 服务器页面可能无法输出正确且有效的 XML 数据,尤其是在您不断点击它的情况下,因此这可能不是您的错。您是否尝试过在 Web 浏览器中点击页面(最好使用 xml 验证器插件)来检查服务器响应是否始终有效?

我在这里能看到的唯一其他东西是这行:

1
2
3
4
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 中添加缓存破坏器?

只是一些建议...


您可以在 XML 文档的最后设置一个唯一的元素名称空间,其中一个属性"value"等于"true";

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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 确实不完整,我们会强制它再次加载。


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


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

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 的原始回复编写的代码(类似于提到的一些建议):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 是否没有问题的代码 - 也就是说,这个错误很难重现,所以据我所知,我没有修复任何东西,而是添加了无用的代码。


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

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


正如您在问题中提到的那样,问题很可能是您的程序在实际完全下载之前正在查看 XML,我不知道是否有"解析"XML 的可靠方法,因为解析您的部分代码很可能没问题,这只是它是否实际下载的问题。

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

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


推荐阅读