LE Blog

Инженер с поэтической душой

07.05.2009 firtree_right ActionScript: асинхронная замена выражений в строке

Введение

Я снова вернулся к работе над флэшовым проектом. Поэтому немного об ActionScript. Описанная здесь задача сейчас мне не кажется такой сложной, какой она казалась, когда я впервые с ней столкнулся. Но тем не менее.

Задача

Имеется строка, содержащая разметку для замены её составляющих. Одна из разметок: #[some_url] должна быть заменена содержимым этого самого some_url. Для замены с помощью регулярных выражений в ActionScript 3 существует функция String#replace. Но всё, что связано с загрузкой из внешних источников, создает асинхронность. А любая попытка остановить код, сделать паузу, приводит к огромной потере производительности и ошибкам, которые генерирует плеер, когда долго не может завершить вызов. «Как быть?» — спросит меня пытливый читатель.

Решение

Решение состоит в том, чтобы заменять все вхождения ключевого выражения по-очереди и когда всё заменено, создавать событие.

Приведу основную часть. Файл AsyncStringReplaceExample.as

package {
  import flash.events.Event;
  import flash.net.URLLoader;
  import flash.net.URLRequest;

  public class AsyncStringReplaceExample {
    public static const RE_URL:RegExp = /#\[([^\]]+)\]/g;

    private var _str:String;
    private var _currentExpr:String;
    private var _ldr:URLLoader;

    private var _loaded:Boolean;

    public function AsyncStringReplaceExample(str:String) {
      _str = str;
      _ldr = new URLLoader();
      _ldr.addEventListener(Event.COMPLETE, ldrCompleteHandler);
      _loaded = false;
    }

    public function replace():Boolean {
      _loaded = true;
      _str = _str.replace(RE_URL, replaceURL);
      return _loaded;
    }

    public function get string():String {
      return _str;
    }

    private function replaceURL():String {
      if (!_currentExpr) {
        _loaded = false;
        _currentExpr = arguments[0];
        _ldr.load(new URLRequest(arguments[1]));
      }
      return arguments[0];
    }

    private function ldrCompleteHandler(evt:Event):void {
      _str = _str.replace(_currentExpr, evt.target.data);
      _currentExpr = null;
      if (replace()) {
        trace(_str); // здесь желанное событие
      }
    }
  }
}

Теперь остается только использовать написанное нами богатство:

var str:String = "строка для примера\n";
str += "добавим: #[http://some.url/file.txt] или";
str += "ещё добавим: #[http://another.url/another/file.txt]! хватит?";

var asyncString:AsyncStringReplaceExample = new AsyncStringReplaceExample(str);
if (asyncString.replace()) {
  trace(asyncString.string); //не нужно ничего заменять
} else {
  asyncString.addEventListener(......)
}
...
// внутри обрабочика событий
trace(asyncString.string);

Выводы

Сразу видно, что последнее время я забросил ActionScript и занимался больше Ruby. Потому что подсветка синтаксиса в Ruby красивее. Но это не страшно. :)

Упражнения

Мне было бы интересно написать функцию, которая могла бы работать сколь угодно долго. Например, рекурсивную, или долгую по любой другой причине. Но ActionScript принудительно завершает процессы, которые долго не подают признаков жизни.

Как быть?