Flash. A vueltas con el exclude.xml
No sé si alguna vez os habéis visto en la necesidad de const ruir un archivo mipeliculaflash_exclude.xml, pero es una labor bastante tediosa.
Para el que no lo sepa, es un archivo XML donde se listan las clases que no queremos que se incluyan en un archivo SWF a la hora de compilarlo, normalmente porque las clases se encuentran en un SWF padre y no queremos engordar al hijo con clases ya declaradas.
Más info aquí: http://www.darronschall.com/weblog/archives/000145.cfm
El caso es que me he visto en la necesidad de crear uno bastante grande y como no quería ir metiendo las clases en el XML una a una (no te deja hacer un exclude de package.*) me he construido un método que genera por la ventana de debug el contenido del archivo XML. Luego se eliminan las clases que sí queremos utilizar y queda listo para su uso.
Creo que Sexier hace algo de esto, pero leyendolo por encima se me antojaba muy complicado para algo tan sencillo.
El método en cuestión es este. También se puede modificar para que te muestre todas las clases que está usando tu fichero SWF, a modo de debug.
<code>
public static function generateExcludeXML():Void {
trace("<excludeAssets>");
var showClasses:Function = function(nameSpace:Object, packageName:String):Void {
for (var i:String in nameSpace) {
if (typeof(nameSpace[i])=="function")
trace('\t<asset name="'+packageName+i+'"/>');
else
showClasses(nameSpace[i], packageName+i+".");
}
};
showClasses(_global, "");
trace("</excludeAssets>");
}
</code>
Para quien le pueda servir, saludos
juandelgado
Lo de los exclude es una de las cosas peor gestionadas por Flash. Lo que me sorprendio en su dia es que MTASC no aportara un poco de cordura al asunto, basicamente poder utilizar com.package.*.
Que yo sepa, funciona <em>casi</em> igual que el IDE, lo unico que aporta es que puedes definir mas de un archivo y no tiene que coincidir el nombre. Que no es poco.
Salud!
elSuricatoRojo
Pues mira, a vi mi me va a venir de perlas para otra cosa:
Tienes un classpath genérico con muuchas clases, y quieres saber de todo el clashpath que classes estas utilizando en un proyecto en concrreto para, por ejemplo, cuando le das el código fuente al cliente no darle el classpath entero sino solo las partes que utilizas para su proyecto... (yo soy de los que utiliza el IDE de Flash sin SEPYs ni Eclipses)
elSuricatoRojo
Usuario desconocido
Me alegro que te sirva. El FDT también hace lo mismo pulsando CTRL+U en una clase, te muestra todo el árbol de dependencias, pero no tiene opción de exportarlo por eso me vi obligado a hacer algo así.
Con respecto a lo que comentas, si no le entregas el classpath genérico al cliente el proyecto no lo podrá compilar. ¿No se te ha dado el caso en el que se te quejen por esto? ¿Cómo lo solucionas? ¿O entregas las clases genéricas en un swf ya compilado? No se me ha dado nunca este caso, pero por si acaso...
elSuricatoRojo
Bueno, yo siempre entrego el código fuente y no me quedo tranquilo hasta que sé que son autónomos a la hora de compilar. Tan solo indico en el presupuesto y en los comentarios que las clases genericas las aporto bajo licencia Creative Commons 2.5.
El tema de dar los códigos fuentes o no, depende de cada uno y de cada caso. En el nuestro, los trabajos en Flash que hacemos son de 2 tipos:
1) Web en flash para clientes directos (PYMES). Ni si quiera te piden los códigos fuente. Yo se los doy por costumbre pero no creo que ninguno mis clientes de este tipo haya abierto nunca el CD donde van grabados. En definitiva da igual darselos o no. Si al final ese CD llega a manos de otro Flasero y se beneficia de lo que hay dentro, lo tomaré como una mínima compensación del Karma comparado de lo que yo he aprendido y obtenido de otros.
2) Webs/aplicaciones para agencias interactivas.
En este caso les doy el código fuente principalmente para:
a) Evitar, o conseguir en la medida de lo posible, que cualquier retoque no tengas que hacerlo tu. Para mi son excelentes noticias si sé que una vez entregado el trabajo hay alguien en la agencia que sabe retocar sobre mi codigo y hacer el las tipicas modificaciones de ultima hora. Sé que se pueden cobrar pero para mi es mas rentable cerrar un proyecto y arrancar con otro y poder realizar la "inmersión" en el con el menor número de distracciones... un poco lo que comentaba el Tarda en:
http://www.design-nation.net/es/archivos/003497.php
b) Generar confianza en la agencia ya que esta filosofía a ellos les supone un valor añadido, lo cual ayuda a posicionarte como opción de cofianza y rope un poco el miedo de subcontratar.
c) En caso de impago, aunque hayas dado el código fuente, el que toda la aplicación tire de un classpath con tu dominio puede actuar como prueba de que el trabajo lo has hecho tu.
d) Si consigues que la gente utilice tus genericas y las incorpore a su classpath, de alguna forma, tu nombre como desarrollador se "potencia". Cuando he porbado la éstaica que aportabas antes uno de los nodos que me ha salido es:
<asset name="tv.zarate.Utils.Trace"/>
En definitiva la facturacón que pierdes por tener la llave del proyecto para mi no compensa la sensación de que terminas un proyecto y a otra cosa mariposa... y de nuevo, si hay alguien en la agencia que se aprovecha de código, me alegro sinceramente por el.
un saludo,
elSuricatoOpenSource
Usuario desconocido
Te felicito, me gusta tu filosofía de trabajo, limpia y clara, sí señor :-)
juandelgado
elSuricatoRojo
<asset name="tv.zarate.Utils.Trace"/>
Yeah! : D
Y a mi también me parece una muy buena forma de currar, pragmática y open-source friendly.
Doble yeah!
califa010
Buenas, en base al código de msanchez escribí una clase que hace básicamente lo mismo, pero agrega alguna funcionalidad:
1) Se pueden indicar clases o paquetes (con el comodín "*") para que queden afuera de la lista de clases excluidas.
2) Se puede obtener el listado de exclusión como un array, además del xml formateado que entregaba el código original.
3) Hay un método para obtener el nombre que debe tener el archivo de exclusión para el swf que lo ejecuta.
Brevemente, se usarían así:
<code>
/* obtener el xml formateado con todas las clases menos mx.controls.* y com.gskinner.events.GDispatcher */
ClassUtils.generateExcludeXML(["mx.controls.*","com.gskinner.events.GDispatcher"]);
/* obtener un array con todas las clases compiladas en el swf, excepto las del paquete mx.controls */
ClassUtils.getCompiledClassesArray(["mx.controls.*"]);
/* obtener el nombre del archivo xml de exclusión */
ClassUtils.getExcludeFileName();
</code>
Bueno, si a alguien le sirve, un gusto! Comentarios, sugerencias, críticas y avisos de bugs son muy bienvenidos.
Va la clase.
Salud!
califa010
<code>class ar.com.califa010.utils.ClassUtils {
private function ClassUtils() {
}
/*
basado en función publicada por msanchez en:
https://www.domestika.org/foros/viewtopic.php?t=50969
*/
public static function generateExcludeXML(arrFilters:Array):String {
// trace("ClassUtils::generateExcludeXML()");
var sExcludeXml:String = "<excludeAssets>\n";
var arrClasses:Array = [];
// el scope es la clase porque el método addAssetItem es estático
// los parámetros que se pasan son una ref al array que va guardando todas las
// clases encontradas en _global, y un array de filtros, que se verifica
// antes de agregar una clase a arrClasses
mapClasses(_global, "",ClassUtils,addAssetItem,[arrClasses,arrFilters]);
sExcludeXml += arrClasses.join("\n");
sExcludeXml += "\n</excludeAssets>";
return sExcludeXml;
}
// Devuelve el nombre del xml de exclusión de clases para el swf que lo llame
public static function getExcludeFileName():String {
// tomar la url, limpirla, hacer un split por "/",
// tomar el último elemento, descartar la extensión y concatenar con "_exclude.xml"
var swf:String = _root._url;
var aux_lv:LoadVars = new LoadVars();
aux_lv.decode("swf="+swf);
swf = (aux_lv.swf).split("/").pop().split(".")[0];
delete aux_lv;
return swf + "_exclude.xml";
}
public static function getCompiledClassesArray(arrFilter:Array):Array {
var arrClasses:Array = [];
mapClasses(_global, "",ClassUtils,applyFilter,[arrClasses,arrFilter]);
return arrClasses;
}
private static function formatAssetItem(path:String):String {
return '\t<asset>';
}
private static function addAssetItem(path:String,arr:Array):Void {
// trace("ClassUtils::addAssetItem()");
var arrClasses:Array = arr[0];
var arrFilters:Array = arr[1].slice();
if(!isInFilter(path,arrFilters)) {
arrClasses.push(formatAssetItem(path));
}
}
private static function applyFilter(path:String,arr:Array):Void {
var arrClasses:Array = arr[0];
var arrFilters:Array = arr[1].slice();
if(!isInFilter(path,arrFilters)) {
arrClasses.push(path);
}
}
/*
Esta función indica si el path pasado es igual a alguno de los filtros
pasados por parámetro.
Tiene en cuenta el "*" al buscar coincidencias
*/
private static function isInFilter(srcPath:String,arrFilters:Array):Boolean {
var isFiltered:Boolean = false;
var len:Number = arrFilters.length;
var arrSrcPath:Array = srcPath.split(".");
var filter:Array;
var filterLen:Number;
var leaf:String;
var comparableLength:Number;
for(var i:Number = 0;i<len;i++) {
filter = arrFilters[i].split(".");
filterLen = filter.length;
leaf = String(filter[filterLen-1]);
if(leaf == "*") {
filterLen--;
}
if(arrayEquals(arrSrcPath.slice(0,filterLen),filter.slice(0,filterLen))) {
isFiltered = true;
break;
}
}
return isFiltered;
}
/*
Función genérica interna que recorre todas las clases y llama a una función que se pasa
por parámetro, con el scope indicado, y dos argumentos:
1) el nombre calificado de la clase
2) un array, opcional
*/
private static function mapClasses(nameSpace:Object,packageName:String,scope:Object,
fnc:Function,args:Array):Void {
/*
trace("ClassUtils::mapClasses()");
trace("fnc="+fnc);
trace("scope="+scope);
trace("args="+args);
*/
for(var s:String in nameSpace) {
if (typeof(nameSpace[s])=="function") {
if(args) {
fnc.call(scope,packageName+s,args);
} else {
fnc.call(scope,packageName+s);
}
} else {
mapClasses(nameSpace[s], packageName+s+".",scope,fnc,args);
}
}
}
/*
Originalmente en otra clase, copiado acá para que esta clase no tenga dependencias
Compara el contenido de dos arrays "planos", sin recursividad (no usar con arrays de arrays)
*/
public static function arrayEquals(a:Array,b:Array):Boolean {
var len:Number = a.length;
if(len != b.length) {
return false;
}
var areEqual:Boolean = true;
for(var i:Number = 0;i<len;i++) {
if(a[i] != b[i]) {
areEqual = false;
break;
}
}
return areEqual;
}
}
</code>