Лучшие практики для синтаксического анализа 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 ответов
Просто примечание, это утверждение не имеет никакого эффекта:
XML.ignoreWhitespace;
так как ignoreWhitespace
это собственность. Вы должны установить его true
как это:
XML.ingoreWhitespace = true;
Что меня беспокоит, так это то, что он может запускать Event.COMPLETE до того, как он завершит загрузку, и это заставляет меня задаться вопросом, истекает ли время загрузки.
Как часто возникает проблема? Можете ли вы добиться успеха в одно мгновение, а затем потерпеть неудачу в следующий раз с той же подачей?
В целях тестирования попробуйте отследить URLLoader.bytesLoaded
и URLLoader.bytesTotal
в верхней части вашего Event.COMPLETE
метод обработчика. Если они не совпадают, вы знаете, что событие срабатывает преждевременно. Если это так, вы можете прослушивать событие прогресса URLLoader. Проверить bytesLoaded
против bytesTotal
в вашем обработчике и анализируйте XML только после завершения загрузки. Конечно, это очень похоже на то, что делает URLLoader до того, как сработает Event.COMPLETE
, но если это сломано, вы можете попробовать свернуть свое собственное.
Пожалуйста, дайте нам знать, что вы узнали. И если вы могли бы, пожалуйста, вставьте в некоторый исходный код. Мы могли бы заметить что-то заметное.
Вы пробовали проверить, что загруженные байты совпадают с общим количеством байтов?
URLLoader.bytesLoaded == URLLoader.bytesTotal
Это должно сказать вам, если файл завершил загрузку, это не поможет с завершением запуска события на ранней стадии, но это должно сказать вам, если его проблема с xml была прочитана.
Я не уверен, будет ли он работать над доменами, поскольку мой xml всегда находится на одном сайте.
Иногда страница сервера 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-адреса?
Просто несколько предложений...
Я предлагаю вам отправить отчет об ошибке по адресу https://bugs.adobe.com/flashplayer/, потому что событие действительно не должно запускаться до загрузки всех байтов. В то же время, я думаю, вы должны жить с таймером. Вы могли бы сделать то же самое, прослушивая событие прогресса, что может избавить вас от необходимости самостоятельно управлять таймером.
Как вы упомянули в своем вопросе, проблема, скорее всего, заключается в том, что ваша программа просматривает XML до того, как он действительно был полностью загружен, я не знаю, что существует надежный способ "проанализировать" XML, потому что часть вашего синтаксического анализа Код более чем хорош, просто вопрос в том, действительно ли он загружен.
Вы можете попытаться использовать событие ProgressEvent.PROGRESS для постоянного мониторинга XML во время его загрузки, а затем, как рекомендует Re0sless, проверить bytesLoaded против bytesTotal и начать анализ XML, когда два числа равны, вместо использования события Event.COMPLETE.,
Вы должны быть в состоянии получить номера bytesLoaded и bytesTotal очень хорошо независимо от доменов, если вы можете получить доступ к файлу, вы можете получить доступ к его байтовой информации.
@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
это ошибка
Хорошо, что вы работаете с таймером, но очень странно, что вам это нужно.
Если бы вы могли опубликовать еще немного кода, мы могли бы найти проблему.
Еще одна вещь для тестирования (помимо отслеживания bytesTotal
) это проследить data
свойство погрузчика в Event.COMPLETE
обработчик, просто чтобы убедиться, что данные XML действительно были загружены правильно, например, проверьте, что есть </link>
там.
Вы можете установить уникальное пространство имен элемента в самом конце вашего 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);
}
}
Я надеюсь, что это поможет вам на данный момент, но настоящая надежда состоит в том, что достаточное количество людей сообщит Adobe об этой проблеме. Грустно не иметь возможности полагаться на события. Однако я должен сказать, что из того, что я слышал о XML, он не очень оптимален в широком масштабе, и считаю, что это когда вам требуется что-то вроде AMFPHP для сериализации данных.
Надеюсь это поможет! Помните, что идея в том, что мы знаем, какой самый последний дочерний элемент / элемент в XML, потому что мы его установили! Нет никаких причин, по которым мы не можем получить доступ к последнему дочернему элементу / элементу, но если мы не можем, мы должны предположить, что XML действительно не был завершен, и мы заставляем его снова загружаться.