Рекомендации по синтаксическому анализу AS3 XML

У меня возникли проблемы с синтаксическим анализом различных типов XML во флэш-памяти (в частности, файлов RSS FeedBurner и ответов API данных YouTube). Я использую URLLoader для загрузки XML-файла и Event.COMPLETE создания нового XML-объекта. В 75% случаев это работает нормально, и время от времени я получаю исключения такого типа:

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

Мы думаем, что проблема в том, что XML большой, и, возможно, Event.COMPLETE событие запускается до того, как XML будет фактически загружен из URLLoader . Единственное решение, которое мы придумали, - это включить таймер для события и, по сути, «подождать несколько секунд», прежде чем начать анализ данных. Конечно, это не лучший способ сделать это.

Есть ли надежный способ разобрать XML во Flash?

Обновление от 2 сентября 2008 г. Мы пришли к выводу, что на этом этапе в коде запускается исключение:

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

Ответов (10)

You could set a unique element namespace at the very end of your XML document that has one attribute "value" equal to "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][email protected] == "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 действительно не был полным, и мы принудительно загружаем его снова.

Просто примечание, это утверждение не имеет никакого эффекта:

XML.ignoreWhitespace;

потому что ignoreWhitespace это собственность. Вы должны установить это true так:

XML.ingoreWhitespace = true;

Вы пытались проверить, совпадают ли загруженные байты с общим количеством байтов?

URLLoader.bytesLoaded == URLLoader.bytesTotal

Это должно сообщить вам, завершила ли загрузка файл, это не поможет с ранним запуском события завершения, но должно сообщить вам, была ли прочитана проблема с xml.

Я не уверен, будет ли он работать с доменами, поскольку мой xml всегда находится на одном сайте.

Меня беспокоит то, что он может запускать Event.COMPLETE до того, как завершит загрузку, и это заставляет меня задаться вопросом, истекает ли время загрузки.

Как часто возникает проблема? Можете ли вы добиться успеха в один момент, а затем потерпеть неудачу в следующий раз с той же подачей?

В целях тестирования попробуйте отследить URLLoader.bytesLoaded и URLLoader.bytesTotal вверху Event.COMPLETE метода обработчика. Если они не совпадают, вы знаете, что событие запускается преждевременно. В этом случае вы можете прослушивать событие progress URLLoader. Сравните это bytesLoaded с bytesTotal в вашем обработчике и анализируйте XML только после того, как загрузка действительно завершится. Конечно, это очень похоже на то, что делает URLLoader перед срабатыванием Event.COMPLETE, но если это не работает, вы можете попробовать запустить свой собственный.

Пожалуйста, дайте нам знать, что вы узнали. И, если можно, вставьте какой-нибудь исходный код. Мы могли бы заметить кое-что примечательное.

Как вы упомянули в своем вопросе, проблема, скорее всего, заключается в том, что ваша программа просматривает XML до того, как он был фактически полностью загружен, я не знаю, есть ли надежный способ «проанализировать» XML, потому что часть синтаксического анализа вашего код, скорее всего, в порядке, это просто вопрос того, действительно ли он загружен.

Вы можете попытаться использовать событие ProgressEvent.PROGRESS для постоянного мониторинга XML по мере его загрузки, а затем, как предлагается Re0sless, проверить bytesLoaded против bytesTotal и начать синтаксический анализ XML, когда два числа равны, вместо использования события Event.COMPLETE .

Вы должны иметь возможность получать числа bytesLoaded и bytesTotal нормально независимо от доменов, если вы можете получить доступ к файлу, вы можете получить доступ к его байтовой информации.

Если бы вы могли опубликовать еще код, мы могли бы найти проблему.

Еще одна вещь, которую нужно проверить (помимо трассировки bytesTotal ), - это отслеживать data свойство загрузчика в Event.COMPLETE обработчике, просто чтобы увидеть, действительно ли данные XML были правильно загружены, например, проверьте, есть ли </link> там.

@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, не было ли у меня проблемы, эту ошибку трудно воспроизвести, поэтому, насколько я знаю, я ничего не исправил, а вместо этого просто добавил бесполезный код.

Event.COMPLETE Обработчик действительно не должен быть вызван , если не был полностью загружен загрузчик, это не имеет никакого смысла. Вы подтвердили , что это на самом деле не полностью загружен (смотря на bytesLoaded против bytesTotal значений , которые прослеживают)? Если Event.COMPLETE событие отправлено раньше bytesLoaded == bytesTotal, это ошибка.

Хорошо, что он у вас работает с таймером, но очень странно, что он вам нужен.

Я предлагаю вам отправить отчет об ошибке на https://bugs.adobe.com/flashplayer/ , потому что событие действительно не должно срабатывать до того, как будут загружены все байты. А пока, думаю, тебе придется жить с таймером. Вы могли бы сделать то же самое, вместо этого прослушивая событие progress, что, возможно, могло бы избавить вас от необходимости самостоятельно обрабатывать таймер.

иногда страница сервера RSS может не выдавать правильные и действительные данные XML, особенно если вы постоянно обращаетесь к ней, поэтому это может не быть вашей ошибкой. Вы пытались открыть страницу в веб-браузере (желательно с помощью плагина 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, а также добавить заголовки URL-адресов prama-no-cache и / или добавить блокировку кеша к URL-адресу?

Просто несколько предложений ...