Great talk on Vimeo: Mike Monteiro | F*ck You. Pay Me.
Author: derRaab
Publish Flash layers to single SWFs
In case you need too, this little Flash IDE script will let you publish every single Flash timeline layer as a separate SWF file. Just use unique and URL save layer names and you will get all your SWFs dynamically created as flaFilename + layerName + ".swf"
.
Just save this little code as a .jsfl file and open it with the Flash IDE:
function filterLayers( timeline, layerName ) { var layers = timeline.layers, layer, c, i; for ( i = layers.length - 1; 0 < i; i-- ) { layer = layers[ i ]; if ( layer && layer.layerType !== "folder" && layer.name !== layerName ) { timeline.deleteLayer( i ); } } } function publishNewFile( fileURL, layerName ) { var document = fl.openDocument( fileURL ); if ( document ) { var documentDOM = fl.getDocumentDOM(); if ( documentDOM ) { var timeline = documentDOM.getTimeline(); if ( timeline ) { filterLayers( timeline, layerName ); } } document.publish(); document.save(); document.close(); } } function layerToSWF( layerName, fileURL, fileName, parentDirURL ) { var newFileURL = parentDirURL + layerName + ".fla"; // create new file as copy var success = FLfile.copy( fileURL, newFileURL ); if ( success ) { // publish new file publishNewFile( newFileURL, layerName ); // remove new file (but leave SWF). FLfile.remove( newFileURL ); } else { alert( "Copy from\n" + fileURL +"\nto\n" + newFileURL + "\nfailed." ); } return success; } function timelineToSWFs( timeline, fileURL, fileName, parentDirURL ) { var layers = timeline.layers, layer, i = 0; c = layers.length; for ( i; i < c; i++ ) { layer = layers[ i ]; if ( layer && layer.layerType === "normal" && layer.name ) { if ( ! layerToSWF( layer.name, fileURL, fileName, parentDirURL ) ) { break; } } } } function layersToSWFs() { var fileURL = fl.browseForFileURL( "open" ); if ( fileURL ) { var document = fl.openDocument( fileURL ); if ( document ) { var documentDOM = fl.getDocumentDOM(); if ( documentDOM ) { var timeline = documentDOM.getTimeline(); if ( timeline ) { var parentDirURL = fileURL.split( "/" ); var fileName = parentDirURL.pop(); fileName = fileName.split( "." ); fileName.pop(); fileName = fileName.join( "." ); parentDirURL = parentDirURL.join( "/" ) + "/" + fileName; timelineToSWFs( timeline, fileURL, fileName, parentDirURL ); } } } } } // http://blog.derRaab.com - saves you some time - get yourself some fresh air! ;) layersToSWFs();
Have a nice day!
Supersized Apps with AIR on iOS
One of our latest customer projects was mainly intended as an offline iPad app but should also ship on USB-sticks and run in browsers for all desktops. A perfect fit for Adobe AIR. Read here what we’ve learned.
Adobe AIR is really a powerful tool when it comes to projects for multiple target devices. We’ve already done a couple of projects based on our data driven ActionScript
framework which runs nicely in any browser supporting the latest versions of Flash player, as standalone player for DVDs and USB-sticks and within the desktop version of AIR.
We spent a lot of time improving the iPad version to ensure good render performance and a really low memory footprint.
The project I’m talking about uses loads of assets, currently almost 2 GB of bitmap and video files (will grow) and 6000+ unique “slide” XML files with the actual content. Everything loads fast and smoothly with the browser and even faster on desktops and of course iOS.
Development
Development was pretty straight forward since we did most of the debugging with the local AIR application which is fast and easy to profile. After big milestones we also packaged the iPad version and tested directly on the device. This post is not about how you do this step effectively but keep in mind that thousands of files means long packaging time with ADT and takes XCode a long time pushing the app onto the iPad. Upgrading to a new SSD dropped packaging time significantly so using a really fast drive definitely pays of!
2 GB maximum size
With our apple development certificate it was no problem to install the app on all iPads, but it simply wasn’t possible with the customers release certificates. We figured out that there’s a size restriction for iPad apps which allows only 2 GB but we already had 2.2 GB. Boom. Luckily we where able to encode all the videos again and save almost 500 MB so we could ship for now.
Delivery
The customer uses an enterprise solution for delivering iPad apps to all it’s employees and it turned out that the technical provider restricts the maximum app size to less then 200 MB. Boom again.
AIR on iOS FileStream
bug
So we implemented a rock solid update process which initially loads almost 1.5 GB of files but never completed on the iPad. The app crashed hard but was still open, crashed again hard after restart but completed the download process after another restart. We had a hard time tracking this issue down to it’s real roots and finally catched this error: 3005 - Insufficient system resources.
Google found this Adobe forum thread about it which contained at least a workaround found by Jason Moore:
We did find a workaround , but using open() instead of openAsync().. everything then seems to function correctly.
Please vote for this AIR on iOS bug, it’s from 2011 but still open:
https://bugbase.adobe.com/index.cfm?event=bug&id=3077653
So if you struggle deciding whether AIR is a good fit for your big app I definitely say yes, but hopefully this post helps a little to ask the right questions and avoids running into the same issues.
Updating my MacBook Pro from HDD to Crucial m4 SSD using Time Machine and Mac OS X Lion
Finally I upgraded my Mid 2010 MacBook Pro with a nice Crucial m4 SSD with 512GB. I was thinking about changing my hard drive for month but now the price dropped from 600 to 400 EUR and I went for it.
It took a little while to collect relevant informations so I want to share a workflow:
- Backup your complete system with time machine!
- Swap your drives (ifixit.com) without touching your battery.
- Check if your SSD runs the newest firmware and upgrade if necessary.
- Boot from your OS X Lion recovery partition (hold ALT key on Mac startup).
- Use the disk utility to restore from time machine.
- Go outside or hang out with friends or family!
- Enable trim support in OS X Lion for your SSD.
Done.
Since a couple of days it really makes fun using my MacBook Pro again. It dropped packaging time for a 1,5 GB big IPA file with ADT from 12 to almost 6 minutes. This is a huge benefit, so if you’re still using normal HDDs for daily work I recommend switching to SSD as soon as possible!
UPDATE – added xbench results:
Name | Score | Detail |
---|---|---|
System Info | ||
Xbench Version | 1.3 | |
System Version | 10.7.4 (11E53) | |
Physical RAM | 8192 MB | |
Model | MacBookPro6,2 | |
Drive Type | M4-CT512M4SSD2 | |
Disk Test | 409.65 | |
Sequential | 253.88 | |
Uncached Write | 405.78 | 249.14 MB/sec [4K blocks] |
Uncached Write | 324.52 | 183.61 MB/sec [256K blocks] |
Uncached Read | 128.80 | 37.69 MB/sec [4K blocks] |
Uncached Read | 408.86 | 205.49 MB/sec [256K blocks] |
Random | 1060.10 | |
Uncached Write | 1452.92 | 153.81 MB/sec [4K blocks] |
Uncached Write | 608.33 | 194.75 MB/sec [256K blocks] |
Uncached Read | 2231.07 | 15.81 MB/sec [4K blocks] |
Uncached Read | 1007.15 | 186.88 MB/sec [256K blocks] |
ActionScript performance test for Array, Object, Vector literals and Array.push, Vector.push methods
Yesterday a “tweet” pointed me to a nice article about ActionScript 3.0 optimization. Most of these techniques are quite common within the ActionScript developer scene but one thing caught my attention: A link to Jackson Dunstans blog post about runtime performance and the const
and final
keywords. Since I always tried to use at least final classes I was a little disappointed that my extra work time doesn’t have any performance benefit at runtime.
Due to that circumstance I was curious about some other common techniques like “Use Object and Array Literals Whenever Possible” or “Add Elements to the End of an Array Without Pushing”. I wanted to check performance benefits for myself just to make sure it’s worth the effort. So I wrote a little test script:
var result : String = "Test playerType: " + Capabilities.playerType + " version: " + Capabilities.version + "\n", i : int = 0, c : int = 1000000, array : Array, object : Object, vectorInt : Vector.<int>, startTime : int; startTime = getTimer(); for ( i = 0; i < c; i++ ) { object = new Object(); } result += c + " times: object = new Object() -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { object = {}; } result += c + " times: object = {} -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { array = new Array(); } result += c + " times: array = new Array() -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { array = []; } result += c + " times: array = [] -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { vectorInt = new Vector.<int>(); } result += c + " times: vectorInt = new Vector.<int>() -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { vectorInt = new <int>[]; } result += c + " times: vectorInt = new <int>[] -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { array.push( i ); } result += c + " times: array.push( i ) -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { array[ array.length ] = i; } result += c + " times: array[ array.length ] = i -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { vectorInt.push( i ); } result += c + " times: vectorInt.push( i ) -> duration: " + String( getTimer() - startTime ) + "\n"; startTime = getTimer(); for ( i = 0; i < c; i++ ) { vectorInt[ vectorInt.length ] = i; } result += c + " times: vectorInt[ vectorInt.length ] = i -> duration: " + String( getTimer() - startTime ) + "\n"; var textFormat : TextFormat = new TextFormat(); textFormat.font = "Courier"; textFormat.size = 14; var textField : TextField = new TextField(); textField.autoSize = TextFieldAutoSize.LEFT; textField.background = true; textField.backgroundColor = 0xFFFFFF; textField.defaultTextFormat = textFormat; textField.multiline = true; textField.text = result; addChild( textField );
It turns out that the benefits differ greatly on platform and player type but basically it’s always a good idea to avoid using the .push
methods and new
keyword. While there’s almost no speed difference in Flash Players and Plug-ins, the debug versions are really slow. And check out the blazing fast object creation with AIR on iOS! Or is this a compiler optimization? Here my results:
Flash CS 5 publish
Test playerType: External version: MAC 10,1,52,14
1000000 times: object = new Object() | -> duration: 246 |
1000000 times: object = {} | -> duration: 427 |
1000000 times: array = new Array() | -> duration: 1288 |
1000000 times: array = [] | -> duration: 339 |
1000000 times: vectorInt = new Vector. |
-> duration: 586 |
1000000 times: vectorInt = new |
-> duration: 596 |
1000000 times: array.push( i ) | -> duration: 134 |
1000000 times: array[ array.length ] = i | -> duration: 137 |
1000000 times: vectorInt.push( i ) | -> duration: 113 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 114 |
Flash Player Debugger.app
Test playerType: StandAlone version: MAC 11,1,102,62
1000000 times: object = new Object() | -> duration: 179 |
1000000 times: object = {} | -> duration: 311 |
1000000 times: array = new Array() | -> duration: 1072 |
1000000 times: array = [] | -> duration: 313 |
1000000 times: vectorInt = new Vector. |
-> duration: 397 |
1000000 times: vectorInt = new |
-> duration: 402 |
1000000 times: array.push( i ) | -> duration: 118 |
1000000 times: array[ array.length ] = i | -> duration: 108 |
1000000 times: vectorInt.push( i ) | -> duration: 111 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 89 |
Flash Player.app
Test playerType: StandAlone version: MAC 11,1,102,62
1000000 times: object = new Object() | -> duration: 127 |
1000000 times: object = {} | -> duration: 183 |
1000000 times: array = new Array() | -> duration: 227 |
1000000 times: array = [] | -> duration: 189 |
1000000 times: vectorInt = new Vector. |
-> duration: 215 |
1000000 times: vectorInt = new |
-> duration: 218 |
1000000 times: array.push( i ) | -> duration: 72 |
1000000 times: array[ array.length ] = i | -> duration: 62 |
1000000 times: vectorInt.push( i ) | -> duration: 61 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 42 |
Browser Debug Plug-in (Safari)
Test playerType: PlugIn version: MAC 11,1,102,62
1000000 times: object = new Object() | -> duration: 157 |
1000000 times: object = {} | -> duration: 249 |
1000000 times: array = new Array() | -> duration: 1015 |
1000000 times: array = [] | -> duration: 293 |
1000000 times: vectorInt = new Vector. |
-> duration: 281 |
1000000 times: vectorInt = new |
-> duration: 282 |
1000000 times: array.push( i ) | -> duration: 124 |
1000000 times: array[ array.length ] = i | -> duration: 117 |
1000000 times: vectorInt.push( i ) | -> duration: 124 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 94 |
Browser Plug-in (Google Chrome)
Test playerType: PlugIn version: MAC 11,2,202,235
1000000 times: object = new Object() | -> duration: 147 |
1000000 times: object = {} | -> duration: 146 |
1000000 times: array = new Array() | -> duration: 237 |
1000000 times: array = [] | -> duration: 224 |
1000000 times: vectorInt = new Vector. |
-> duration: 257 |
1000000 times: vectorInt = new |
-> duration: 266 |
1000000 times: array.push( i ) | -> duration: 54 |
1000000 times: array[ array.length ] = i | -> duration: 61 |
1000000 times: vectorInt.push( i ) | -> duration: 70 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 51 |
AIR SDK 3.2
Test playerType: Desktop version: MAC 11,2,202,223
1000000 times: object = new Object() | -> duration: 274 |
1000000 times: object = {} | -> duration: 243 |
1000000 times: array = new Array() | -> duration: 1344 |
1000000 times: array = [] | -> duration: 379 |
1000000 times: vectorInt = new Vector. |
-> duration: 431 |
1000000 times: vectorInt = new |
-> duration: 426 |
1000000 times: array.push( i ) | -> duration: 135 |
1000000 times: array[ array.length ] = i | -> duration: 136 |
1000000 times: vectorInt.push( i ) | -> duration: 153 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 74 |
iOS (iPad 3) debug interpreter
Test playerType: StandAlone version: MAC 11,1,102,62
1000000 times: object = new Object() | -> duration: 4707 |
1000000 times: object = {} | -> duration: 2033 |
1000000 times: array = new Array() | -> duration: 22191 |
1000000 times: array = [] | -> duration: 2993 |
1000000 times: vectorInt = new Vector. |
-> duration: 10235 |
1000000 times: vectorInt = new |
-> duration: 10156 |
1000000 times: array.push( i ) | -> duration: 1728 |
1000000 times: array[ array.length ] = i | -> duration: 1670 |
1000000 times: vectorInt.push( i ) | -> duration: 2056 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 1951 |
iOS (iPad 3) ad-hoc
Test playerType: Desktop version: IOS 11,2,202,223
1000000 times: object = new Object() | -> duration: 1224 |
1000000 times: object = {} | -> duration: 0 !!! |
1000000 times: array = new Array() | -> duration: 5886 |
1000000 times: array = [] | -> duration: 1048 |
1000000 times: vectorInt = new Vector. |
-> duration: 2737 |
1000000 times: vectorInt = new |
-> duration: 2784 |
1000000 times: array.push( i ) | -> duration: 2043 |
1000000 times: array[ array.length ] = i | -> duration: 234 |
1000000 times: vectorInt.push( i ) | -> duration: 1524 |
1000000 times: vectorInt[ vectorInt.length ] = i | -> duration: 643 |
Always worth reading: http://gskinner.com/talks/quick/
My little Flash library cleanup script
Just save this little code as a .jsfl file:
function editItem( lib, item ) { var success = true, linkageIdentifier = item.linkageIdentifier, itemType = item.itemType, path = []; // separate export symbols from assets path.push( ( linkageIdentifier ) ? "_export" : "_asset" ); // don't touch existing folders if ( itemType !== "folder" ) { // separate different item types path.push( itemType ); success = libItemMoveToFolder( lib, path.join( "/" ), item, false ); } return success; } function libItemMoveToFolder( lib, path, item, replace ) { fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) CALL" ); var folderCreated = lib.newFolder( path ), success = false; if ( folderCreated ) { // Can't move symbol names with empty space if ( item.name.indexOf( " " ) ) { // But even this doesn't work - so avoid spaces in item names! item.name = item.name.split( " " ).join( "" ); } success = lib.moveToFolder( path, item.name, replace ); if ( ! success ) { fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) Error Can't move item!" ); } } else { fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) Error Can't create folder!" ); } return success; } function iterateAllItems( lib ) { var items = lib.items, item, c = items.length, i = 0, success; for ( i; i < c; i++ ) { item = items[ i ]; if ( item ) { success = editItem( lib, item ) if ( ! success ) { break; } } } } // Select .fla var avatarFLASource = fl.browseForFileURL( "open"); if ( avatarFLASource ) { // Open .fla var doc = fl.openDocument( avatarFLASource ); if ( doc ) { var lib = doc.library; if ( lib ) { iterateAllItems( lib ); } } }
If you use empty spaces within your item names these items won’t move at all. I was to busy fixing this issue but maybe you know how to solve this little problem?
Avoid iFrame content clipping with CSS transform on iOS
The HTML(5) version of our SCORM based e-learning runtime heavily uses i-frames as template containers. This architecture gives a lot of flexibility but since customers mainly request iPad support we where very unhappy with Safari’s rendering capabilities on iOS. Of course we use CSS transform to get the best animation performance on mobile devices, but in Apple’s mobile browser you’ll likely see huge clipping rectangles during hardware accelerated transitions.
There was not much information about that issue online but at least this entry on stackoverflow suggested there is not much you can do. Well, it took a lot of trail an error testing time but finally we nailed it. The solution is quite simple:
Whenever you use CSS transform with i-frames or it’s parents you also need to apply a basic CSS transform to the body tag of your i-frame content. That’s it. You can also use other contents within the loaded frame HTML page but the body tag seems to be the perfect target.
So this is a little JavaScript code snipped:
// Reference to your iFrame HTML element var iFrameElement = document.getElementById( "yourIFrameID" ); // Avoid clipping by applying simple css transfrom3D to the iframe content itself $( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "translate3d(0,0,0)" ); // Start your iFrame transition (from a visible position) $( iFrameElement ).css( "-webkit-transform", "translate3d(-1000px,0,0)" );
UPDATE: Make sure you start from a visible position!
Don’t forget to remove CSS transform after your animation is done:
// Don't forget to reset after your transition has finished! $( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "none" );
I suggest to use “none” as the default value. We sometimes experienced weird behavior using an empty string…
Have fun coding!
Removing comments in CSS, HTML and ECMAScript (JavaScript)
While working on an Adobe AIR based source code editor I was looking for an easy way to remove comments from different kind of source codes. What first seemed like an easy regular expression turned out to be much more complex because comment pre- and suffixes can also occur in string literals or regular expressions and our old friend Internet Explorer allows HTML
conditional comments and JavaScript
conditional compilation. Great.
First I had a look at YUICompressor
s source code and Google Page Speed but then I decided to port a script for removing JavaScript
comments created by James Padolsey.
I just improved the recognition for regular expressions a little and since this code should work with all ECMAScript
based source codes I named my class ECMAScriptParser
instead of JavaScriptParser
:
package de.superclass.parser { /** * @author Markus Raab (superclass.de | blog.derRaab.com) */ public class ECMAScriptParser { /** * Removes inline and block comments from an ECMAScript source string. * * This is a port of James Padolsey with an slightly improved RegExp recognition. * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript * * @param ecmaScript ECMAScript source string * @param removeCondComp Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not. * @return ECMAScript source string without comments */ public static function removeComments( ecmaScript : String, removeCondComp : Boolean = false ) : String { var modeSingleQuote : Boolean = false; var modeDoubleQuote : Boolean = false; var modeRegExp : Boolean = false; var modeBlockComment : Boolean = false; var modeLineComment : Boolean = false; var modeCondComp : Boolean = false; var vector : Vector.<String> = Vector.<String>( ( '__' + ecmaScript + '__' ).split( '' ) ); vector.fixed = true; var c : int = vector.length; for ( var i : int = 0; i < c; i++ ) { var string : String = vector[ i ]; if ( modeRegExp ) { if ( string === '/' && vector[ i - 1 ] !== '\\' ) { modeRegExp = false; } continue; } if ( modeSingleQuote ) { if ( string === "'" && vector[ i - 1 ] !== '\\' ) { modeSingleQuote = false; } continue; } if ( modeDoubleQuote ) { if ( string === '"' && vector[ i - 1 ] !== '\\' ) { modeDoubleQuote = false; } continue; } if ( modeBlockComment ) { if ( string === '*' && vector[ i + 1 ] === '/' ) { vector[ i + 1 ] = ''; modeBlockComment = false; } vector[ i ] = ''; continue; } if ( modeLineComment) { string = vector[ i + 1 ]; if ( string === '\n' || string === '\r' ) { modeLineComment = false; } vector[ i ] = ''; continue; } if ( modeCondComp ) { if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' ) { modeCondComp = false; } continue; } if ( string === '"' ) { modeDoubleQuote = true; continue; } if ( string === "'" ) { modeSingleQuote = true; continue; } if ( string === '/' ) { if ( ! removeCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' ) { modeCondComp = true; continue; } if ( vector[ i + 1 ] === '*' ) { vector[ i ] = ''; modeBlockComment = true; continue; } if ( vector[ i + 1 ] === '/' ) { vector[ i ] = ''; modeLineComment = true; continue; } for ( var k : int = i - 1; true; k-- ) { string = vector[ k ]; if ( string !== ' ' ) { if ( string === '=' ) { modeRegExp = true; } break; } } } } return vector.join( '' ).slice( 2, -2 ); } } }
Allright – CSS doesn’t allow inline or conditional comments so I removed this script functionality and created a CSSParser
class:
package de.superclass.parser { /** * @author Markus Raab (superclass.de | blog.derRaab.com) */ public class CSSParser { /** * Removes comments from a CSS string. * * This is a shrinked port of James Padolseys script. * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript * * @param css CSS string * @return CSS string without comments */ public static function removeComments( css : String) : String { var modeSingleQuote : Boolean = false; var modeDoubleQuote : Boolean = false; var modeBlockComment : Boolean = false; var vector : Vector.<String> = Vector.<String>( ( '__' + css + '__' ).split( '' ) ); vector.fixed = true; var c : int = vector.length; for ( var i : int = 0; i < c; i++ ) { var string : String = vector[ i ]; if ( modeSingleQuote ) { if ( string === "'" && vector[ i - 1 ] !== '\\' ) { modeSingleQuote = false; } continue; } if ( modeDoubleQuote ) { if ( string === '"' && vector[ i - 1 ] !== '\\' ) { modeDoubleQuote = false; } continue; } if ( modeBlockComment ) { if ( string === '*' && vector[ i + 1 ] === '/' ) { vector[ i + 1 ] = ''; modeBlockComment = false; } vector[ i ] = ''; continue; } if ( string === '"' ) { modeDoubleQuote = true; continue; } if ( string === "'" ) { modeSingleQuote = true; continue; } if ( string === '/' ) { if ( vector[ i + 1 ] === '*' ) { vector[ i ] = ''; modeBlockComment = true; continue; } } } return vector.join( '' ).slice( 2, -2 ); } } }
Good. Lastly I extended the code to remove all comments within HTML
source code, which can also contain JavaScript
and of course CSS
. This is the HTMLParser
:
package de.superclass.parser { /** * @author Markus Raab (superclass.de | blog.derRaab.com) */ public class HTMLParser { /** * Removes all HTML, CSS and ECMAScript comments from a HTML string. * * This is an extended port of James Padolseys script with an improved RegExp recognition. * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript * * @param html HTML string * @param removeHTMLCondComment Optional (default=false) - Whether to remove Internet Explorers HTML conditional comments or not. * @param removeJSCondComp Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not. * @return HTML string without comments */ public static function removeComments( html : String, removeHTMLCondComment : Boolean = false, removeJSCondComp : Boolean = false ) : String { var modeSingleQuote : Boolean = false; var modeDoubleQuote : Boolean = false; var modeRegExp : Boolean = false; var modeBlockComment : Boolean = false; var modeLineComment : Boolean = false; var modeJSCondComp : Boolean = false; var modeHTMLComment : Boolean = false; var modeHTMLCondComment : Boolean = false; var vector : Vector.<String> = Vector.<String>( ( '__' + html + '__' ).split( '' ) ); vector.fixed = true; var c : int = vector.length; for ( var i : int = 0; i < c; i++ ) { var string : String = vector[ i ]; if ( modeRegExp ) { if ( string === '/' && vector[ i - 1 ] !== '\\' ) { modeRegExp = false; } continue; } if ( modeSingleQuote ) { if ( string === "'" && vector[ i - 1 ] !== '\\' ) { modeSingleQuote = false; } continue; } if ( modeDoubleQuote ) { if ( string === '"' && vector[ i - 1 ] !== '\\') { modeDoubleQuote = false; } continue; } if ( modeBlockComment ) { if ( string === '*' && vector[ i + 1 ] === '/') { vector[ i + 1 ] = ''; modeBlockComment = false; } vector[ i ] = ''; continue; } if ( modeLineComment) { string = vector[ i + 1 ]; if ( string === '\n' || string === '\r' ) { modeLineComment = false; } vector[ i ] = ''; continue; } if ( modeJSCondComp ) { if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' ) { modeJSCondComp = false; } continue; } if ( modeHTMLComment ) { // --> if ( string === '-' && vector[ i + 1 ] === '-' && vector[ i + 2 ] === '>' ) { vector[ i + 1 ] = ''; vector[ i + 2 ] = ''; modeHTMLComment = false; } vector[ i ] = ''; continue; } if ( modeHTMLCondComment ) { if ( string === 'i' && vector[ i + 1 ] === 'f' && vector[ i + 2 ] === ']' && vector[ i + 3 ] === '-' && vector[ i + 4 ] === '-' && vector[ i + 5 ] === '>' ) // if]--> { modeHTMLCondComment = false; i += 5; } continue; } if ( string === '<' && vector[ i + 1 ] === '!' && vector[ i + 2 ] === '-' && vector[ i + 3 ] === '-' ) // <!-- { if ( ! removeHTMLCondComment && vector[ i + 4 ] === '[' && vector[ i + 5 ] === 'i' && vector[ i + 6 ] === 'f' ) // <!--[if { modeHTMLCondComment = true; } else { vector[ i ] = ''; modeHTMLComment = true; } } if ( string === '"' ) { modeDoubleQuote = true; continue; } if ( string === "'" ) { modeSingleQuote = true; continue; } if ( string === '/' ) { if ( ! removeJSCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' ) { modeJSCondComp = true; continue; } if ( vector[ i + 1 ] === '*' ) { vector[ i ] = ''; modeBlockComment = true; continue; } if ( vector[ i + 1 ] === '/' ) { vector[ i ] = ''; modeLineComment = true; continue; } for ( var k : int = i - 1; true; k-- ) { string = vector[ k ]; if ( string !== ' ' ) { if ( string === '=' ) { modeRegExp = true; } break; } } } } return vector.join( '' ).slice( 2, -2 ); } } }
Let me know if I made a mistake but it seems to work quite well. Or do you know a good library that already does this kind of optimizations?
Well, that’s it for now. Have fun coding!
Adobe Tech Summit 2011 Berlin Notes
Currently I’m all day long busy learning more about JavaScript, webkit, iOS, web apps, browser differences and all the HTML 5 stuff while porting our SCORM based e-learning runtime. That kept me from following Adobe MAX and all the news regarding it so I took the opportunity to catch up a little bit by spending a whole day at Adobe Tech Summit 2011 Berlin (german). So here I’m simply sharing my notes:
CSS / HTML / JavaScript
Have a look at Adobe Edge at Adobe Labs. It seems to be a really helpful CSS/JavaScript animation tool and also a good research base for tweaks Adobe uses to be as cross browser as possible. If I understood Mark Enders right, Adobe Edge currently doesn’t use CSS transform
for animations but for enabling hardware acceleration by just setting transformZ
. If that works I’ll also use this approach.
Check out jQueryMobile and it’s virtual mouse events in particular. We had trouble with the onclick
event on iOS so I need more information on that.
Just some links worth having a look at (again): Adobe Creative Cloud, Typekit, PhoneGap, The Expressive Web. Follow Paul Rouget and his work on the upcoming Mozilla Dev Tools and it’s really great features.
AIR / Flash / Flex
We all know Stage3D and hope to get it on devices soon, but for now we can look forward to Adobe Flex SDK 4.6 which includes a set of touch device components that will help to create touch AIR apps in almost no time. Native extensions for AIR 3.0 will also improve development for touch devices since we can then access every native device functionality. But for now we can at least develop hardware accelerated 2D stuff for the desktop using the Starling framework
Have fun coding!
Ilias SCORM 2004 can’t read timestamp
If you’re working with Ilias LMS you might run into this issue as well, so I’ll share this here.
We’re using SCORM 2004 with it’s cmi.comments_from_learner
but it turned out that it wasn’t possible to read a previously set timestamp
value during the next SCO visit. Did nobody ever read a timestamp
again? Anyway, since we strongly needed this functionality I had to figure out where the problem was. After making definitely sure it wasn’t our mistake I downloaded a fresh copy of the latest stable Ilias version (4.1.7) and tried to investigate how this system works.
A timestamp
value yyyy-mm-ddThh:mm:ss.msZ
was stored using MySQL datetime
and therefore read back as yyyy-mm-dd hh:mm:ss
. Time zone designator and 2 digits accuracy are SCORM 2004 optional, but the missing ‘T’ separator killed the data conversion.
So let’s have a look at the related original JavaScript function:
function setItemValue (key, dest, source, destkey) { if (source && source.hasOwnProperty(key)) { var d = source[key]; var temp=d; if (!isNaN(parseFloat(d)) && !(/[A-Za-z]/.test(d))) { d = Number(d); } else if (d==="true") { d = true; } else if (d==="false") { d = false; } //special handling for titles - no conversion if (key == "title") { d=temp; } dest[destkey ? destkey : key] = d; } }
And here is my temporary fix:
function setItemValue (key, dest, source, destkey) { if (source && source.hasOwnProperty(key)) { var d = String( source[key] ); if ( destkey === "timestamp" ) { // Fixed timestamp casting to NaN! // 26.09.2011 - derRaab - http://blog.derRaab.com // // a) No conversion // b) Ilias delivers date string without 'T': yyyy-mm-dd hh:mm:ss -> yyyy-mm-ddThh:mm:ss if ( d.length > 10 && d.charAt( 10 ) === " ") { d = d.split( " " ).join( "T" ); } } else if ( key === "title" ) { //special handling for titles - no conversion } else { // 26.09.2011 - derRaab - http://blog.derRaab.com // Use Number() instead of parseFloat() if (!isNaN(Number(d)) && !(/[A-Za-z]/.test(d))) { d = Number(d); } else if (d==="true") { d = true; } else if (d==="false") { d = false; } } dest[destkey ? destkey : key] = d; } }
Please note that I replaced the use of parseFloat()
with Number()
– I’m investigating another problem with the cmi.location characterstring
– but that’s fixed for now.
I reported these issues to the Ilias development team and they fixed at least the ‘T’ separator problem for Ilias 4.2.0:
http://www.ilias.de/mantis/view.php?id=7846
http://www.ilias.de/mantis/view.php?id=7847
You must be logged in to post a comment.