001: /* ****************************************************************************
002: * DataCompiler.java
003: *
004: * Compile XML directly to SWF bytecodes.
005: * ****************************************************************************/
006:
007: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
008: * Copyright 2001-2006 Laszlo Systems, Inc. All Rights Reserved. *
009: * Use is subject to license terms. *
010: * J_LZ_COPYRIGHT_END *********************************************************/
011:
012: package org.openlaszlo.xml.internal;
013:
014: import java.io.*;
015: import java.util.*;
016:
017: import org.jdom.Attribute;
018: import org.jdom.Comment;
019: import org.jdom.Document;
020: import org.jdom.Element;
021: import org.jdom.JDOMException;
022: import org.jdom.input.SAXBuilder;
023: import org.jdom.output.*;
024:
025: import org.openlaszlo.iv.flash.util.*;
026: import org.openlaszlo.iv.flash.api.action.*;
027: import org.openlaszlo.iv.flash.api.*;
028: import org.openlaszlo.compiler.CompilationError;
029: import org.openlaszlo.utils.ChainedException;
030: import org.openlaszlo.utils.FileUtils;
031: import org.openlaszlo.utils.HashIntTable;
032:
033: import org.xmlpull.v1.XmlPullParser;
034: import org.xmlpull.v1.XmlPullParserException;
035: import org.xmlpull.v1.XmlPullParserFactory;
036:
037: import org.apache.log4j.*;
038:
039: /**
040: * Takes XML in various forms, and serializes it to isomorphic actionscript
041: * bytecode.
042: *
043: * @author Max Carlson, Henry Minsky
044: * @version 1.1
045: */
046: public class DataCompiler extends DataCommon {
047: /* Logger */
048: private static Logger mLogger = Logger
049: .getLogger(DataCompiler.class);
050: private static XmlPullParserFactory factory = null;
051:
052: /** global var to hold the image of the stack when splitting frames */
053: static int MISC_BUFSIZ = 4096;
054: static String STRINGBUF_VAR = "__dcstrbuf__";
055:
056: private static XmlPullParserFactory getXPPFactory() {
057: if (factory == null) {
058: // Set up the XML Parser factory
059: try {
060: String sys = null;
061: try {
062: sys = System
063: .getProperty(XmlPullParserFactory.PROPERTY_NAME);
064: } catch (SecurityException se) {
065: }
066: factory = XmlPullParserFactory.newInstance(sys, null);
067: factory.setNamespaceAware(true);
068: } catch (XmlPullParserException e) {
069: throw new RuntimeException(e.getMessage());
070: }
071: }
072: return factory;
073: }
074:
075: public DataCompiler() {
076: }
077:
078: // compress = false, for back compatibility
079: public static InputStream compile(String x, String headers,
080: int flashVersion, boolean addwrapper, boolean trimWhitespace)
081: throws IOException, DataCompilerException {
082: return compile(x, headers, flashVersion, addwrapper,
083: trimWhitespace, false, false);
084: }
085:
086: public static InputStream compile(String x, String headers,
087: int flashVersion, boolean addwrapper,
088: boolean trimWhitespace, boolean compress)
089: throws IOException, DataCompilerException {
090: return compile(x, headers, flashVersion, addwrapper,
091: trimWhitespace, compress, false);
092: }
093:
094: /**
095: * Compile XML to SWF
096: *
097: * @param x XML string to compile
098: * @return input stream
099: *
100: * The size of the input XML will always be greater than the
101: * output .swf (possibly plus some factor, to be measured), so we can allocate a FlashBuffer
102: * with that max size and be done.
103: */
104:
105: // Change input from String to Reader, for higher efficiency
106: public static InputStream compile(String x, String headers,
107: int flashVersion, boolean addwrapper,
108: boolean trimWhitespace, boolean compress, boolean nsprefix)
109: throws IOException, DataCompilerException {
110: try {
111: // XML Parser for the data body
112: XmlPullParser xppbody = getXPPFactory().newPullParser();
113: xppbody.setInput(new StringReader(x));
114:
115: // XML Parser for the headers
116: XmlPullParser xppheaders = getXPPFactory().newPullParser();
117: if (addwrapper) {
118: xppheaders.setInput(new StringReader(headers));
119: }
120: return getSWF(xppbody, xppheaders, x.length(),
121: flashVersion, addwrapper, trimWhitespace, compress,
122: nsprefix);
123: } catch (XmlPullParserException ex) {
124: throw new DataCompilerException("Parsing XML: "
125: + ex.getMessage());
126: }
127: }
128:
129: public static InputStream compile(String x, int flashVersion)
130: throws IOException, DataCompilerException {
131: try {
132:
133: // XML Parser for the data body
134: XmlPullParser xppbody = getXPPFactory().newPullParser();
135: xppbody.setInput(new StringReader(x));
136:
137: return getSWF(xppbody, null, x.length(), flashVersion,
138: false);
139: } catch (XmlPullParserException ex) {
140: throw new DataCompilerException("Parsing XML: "
141: + ex.getMessage());
142: }
143: }
144:
145: public static InputStream compile(String x, int flashVersion,
146: boolean nsprefix) throws IOException, DataCompilerException {
147: try {
148: // XML Parser for the data body
149: XmlPullParser xppbody = getXPPFactory().newPullParser();
150: xppbody.setInput(new StringReader(x));
151:
152: // defaults
153: // addwrapper = false
154: // compress = false
155: boolean trimWhitespace = false;
156: return getSWF(xppbody, null, x.length(), flashVersion,
157: false, trimWhitespace, false, nsprefix);
158: } catch (XmlPullParserException ex) {
159: throw new DataCompilerException("Parsing XML: "
160: + ex.getMessage());
161: }
162: }
163:
164: /** Create a new string pool buffer */
165: private static DataContext makeDataContext(int flashVersion) {
166: DataContext dc = new DataContext(flashVersion);
167: return dc;
168: }
169:
170: /** Split string into PUSH chunks of less than 64k, and emit string concat
171: instructions to re-assemble them. Leaves string value on stack.
172:
173: */
174: static void splitPushString(String text, Program prog) {
175: // break up into 64K chunks, append to local variable.
176: // To account for multibyte chars, use 10000 to be safe.
177: int index = 0;
178: int len = 0;
179: int nchunks = 0;
180: while (index < text.length()) {
181: if ((text.length() - index) > 10000) {
182: len = 10000;
183: } else {
184: len = text.length() - index;
185: }
186: String chunk = text.substring(index, index + len);
187: index += len;
188:
189: prog.push(chunk);
190: nchunks++;
191: }
192:
193: // concatenate all the strings
194: while (nchunks > 1) {
195: prog.addString();
196: nchunks--;
197: }
198: }
199:
200: static int MAXFRAME_SIZE = 20000;
201:
202: // Initial buffer size for program, with space for string constant pool (max 64k)
203: static int FB_INIT_SIZE = MAXFRAME_SIZE * 2 + 128000;
204:
205: /* These two hardcoded constants are always added first to string pool of a frame, in this order. */
206: private static byte constructor_idx = 0;
207: private static byte textnode_idx = 1;
208:
209: static String getStackTrace(Throwable aThrowable) {
210: final Writer result = new StringWriter();
211: final PrintWriter printWriter = new PrintWriter(result);
212: aThrowable.printStackTrace(printWriter);
213: return result.toString();
214: }
215:
216: /**
217: * Main loop which pulls XPP events and writes swf bytecodes to build the node structure.
218: *
219: * @param xpp XML parser pointing to data
220: * @param programs a list of Flash Programs. Add to it as we create new frames.
221: * enter assuming that root node is on the stack
222: *
223: */
224:
225: /*
226:
227: >>> c("stackarray=['top','middle', 'root']",printInstructions=1)
228: constants 'stackarray' 'middle' 'root' 'top'
229: push 'stackarray' 'root' 'middle' 'top' '3'
230: initArray
231: setVariable
232: >>>
233:
234:
235: >>> c('while(a.length > 0) a.pop()' , printInstructions=1)
236: constants 'a' 'length' 'pop'
237: L1:
238: push '0.0' 'a'
239: getVariable
240: push 'length'
241: getMember
242: lessThan
243: not
244: branchIfTrue 'L0'
245: push '0' 'a'
246: getVariable
247: push 'pop'
248: callMethod
249: pop
250: branch 'L1'
251: L0:
252: >>>
253:
254: */
255:
256: private static final DataContext writeXMLData(XmlPullParser xpp,
257: Vector programs, DataContext dc, int stackdepth,
258: DCOptions ioptions) throws IOException,
259: XmlPullParserException {
260:
261: DCOptions options = (DCOptions) ioptions.clone();
262:
263: int flashVersion = options.flashVersion;
264: boolean splitframes = options.splitframes;
265: boolean addwrapper = options.addwrapper;
266: boolean trimWhitespace = options.trimWhitespace;
267: boolean localdata = options.localdata;
268: boolean nsprefix = options.nsprefix;
269:
270: // Use the program we were passed in, it has the root node on top of stack.
271: Program bodyp = (Program) programs.lastElement();
272: FlashBuffer body = bodyp.body();
273:
274: int eventType = xpp.getEventType();
275: while (eventType != xpp.END_DOCUMENT) {
276: if (eventType == xpp.START_DOCUMENT) {
277: } else if (eventType == xpp.START_TAG) {
278: // makeNodeNoText = function (attrs, name, parent)
279: // dup pointer to PARENT (who is at top of stack)
280: // DUP
281: body._writeByte(Actions.PushDuplicate);
282:
283: // Fold all the attribute key/value pairs into a single PUSH
284: // Build ATTRIBUTE object
285: int nattrs = xpp.getAttributeCount();
286: String eltname = xpp.getName();
287: String eltprefix = xpp.getPrefix();
288: if (nsprefix && eltprefix != null) {
289: eltname = eltprefix + ":" + eltname;
290: }
291:
292: // Check if combined attr+data is > 64k, if so, use individual
293: // pushes with string breaking/merging, rather than compressed merging.
294: int datasize = 0;
295: for (int i = 0; i < nattrs; i++) {
296: String attrname = xpp.getAttributeName(i);
297: String attrprefix = xpp.getAttributePrefix(i);
298: if (nsprefix && attrprefix != null) {
299: attrname = attrprefix + ":" + attrname;
300: }
301: datasize += getByteLength(attrname, dc.encoding) + 2;
302: String attrval = xpp.getAttributeValue(i);
303: datasize += getByteLength(attrval, dc.encoding) + 2;
304: }
305:
306: // If if data is too large, use individual split pushses
307: if (datasize > 65500) {
308: bodyp.push(eltname);
309:
310: for (int i = 0; i < nattrs; i++) {
311: String attrname = xpp.getAttributeName(i);
312: String attrprefix = xpp.getAttributePrefix(i);
313: if (nsprefix && attrprefix != null) {
314: attrname = attrprefix + ":" + attrname;
315: }
316: //System.out.print("Attr " + attrname);
317: bodyp.push(attrname);
318:
319: String attrval = xpp.getAttributeValue(i);
320: splitPushString(attrval, bodyp);
321: }
322:
323: bodyp.push(nattrs);
324: body._writeByte(Actions.InitObject);
325: } else {
326:
327: // We're really squeezing things down, so we are going to merge
328: // the PUSH of the element name with the PUSH of the attribute key/value
329: // data and the attribute count. So the stack will look like
330: // [eltname attrname1 attrval1 attrname2 attrval2 ... ... nattrs]
331: // when we're done
332: body._writeByte(Actions.PushData);
333: // Leave a two byte space for the PUSH length
334: // Mark where we are, so we can rewrite this with correct length later.
335: int push_bufferpos = body.getPos();
336: body._writeWord(0); // placeholder 16-bit length field
337:
338: // Push element NAME
339: _pushMergedStringDataSymbol(eltname, body, dc);
340:
341: // PUSH := {0x96, lenlo, lenhi, 0x00, char, char, char, ...0x00, }
342: for (int i = 0; i < nattrs; i++) {
343: String attrname = xpp.getAttributeName(i);
344: String attrprefix = xpp.getAttributePrefix(i);
345: if (nsprefix && attrprefix != null) {
346: attrname = attrprefix + ":" + attrname;
347: }
348: //System.out.print("Attr " + attrname);
349: _pushMergedStringDataSymbol(attrname, body, dc);
350:
351: String attrval = xpp.getAttributeValue(i);
352: //System.out.println("= " + attrval);
353: _pushMergedStringData(attrval, body, dc);
354: }
355: // create the attrs object; push the attr count
356: body._writeByte(0x07); // INT type
357: body._writeDWord(nattrs);
358:
359: // Now go back and fix up the size arg to the PUSH instruction
360: int total_size = body.getPos()
361: - (push_bufferpos + 2);
362: //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
363: body.writeWordAt(total_size, push_bufferpos);
364:
365: body._writeByte(Actions.InitObject);
366: }
367:
368: // stack now has [parent, name, attrs]
369: // Push # of args and node-instantiator-function name
370: // PUSH 3, _mdn
371: // [PUSHDATA, 9, 0, 0x07, 03 00 00 00 0x08, "_m"]
372: body._writeByte(Actions.PushData);
373: body._writeWord(7);
374: body._writeByte(0x07); // INT type
375: body._writeDWord(3); // '3' integer constant , number of args to node constructor fn
376: body._writeByte(0x08); // SHORT DICTIONARY LOOKUP type
377: body._writeByte(constructor_idx); // index of "_m" string constant
378: body._writeByte(Actions.CallFunction);
379:
380: // We push new node on the stack, so we can reference it as the parent
381: // Stack => [parentnode newnode]
382: // increment the node stack depth
383: stackdepth++;
384: } else if (eventType == xpp.END_TAG) {
385: // Pop the node off the stack.
386: body._writeByte(Actions.Pop);
387: stackdepth--;
388:
389: // Good place to check if this frame has gotten large enough to split.
390:
391: // IMPORTANT: recalculate buffer size to what it
392: // really is (since we've been using the fast
393: // _writeXXX functions which dont' update size
394: // automatically);
395: body.setSize(body.getPos());
396:
397: // When we end a frame, we call initArray (stackdepth) to unload to "__dcns".
398: // Then when we start the next frame, we loop to transfer array contents back to stack
399: if (splitframes && body.getSize() > MAXFRAME_SIZE) {
400: //mLogger.debug("splitting frame "+body.getSize());
401:
402: // dump the stack to a global variable
403: // XXXXX NEED TO COMPENSATE FOR HEADER WRAPPERS!!!!!!!
404: bodyp.push(stackdepth);
405: body.writeByte(Actions.InitArray);
406: bodyp.push("__dcns");
407: body.writeByte(Actions.StackSwap);
408: bodyp.setVar();
409:
410: // prepend the string pool to the program,and add it to the frame list
411: Program withpool = appendStringPool(dc, body);
412: // replace the last program with one that has a string pool.
413: programs
414: .setElementAt(withpool, programs.size() - 1);
415:
416: body = new FlashBuffer(FB_INIT_SIZE);
417: bodyp = new Program(body);
418: programs.add(bodyp);
419:
420: dc = makeDataContext(flashVersion);
421: // Intern the node constructor function names in
422: // the string pool as first entries. Always use
423: // this ordering.
424: addStringConstant(NODE_INSTANTIATOR_FN, dc);
425: addStringConstant(TEXT_INSTANTIATOR_FN, dc);
426:
427: // The code below is basically this:
428: // while(__dcns.length > 0) { __dcns.pop() }
429: // But each pop() leaves the popped data on stack.
430: //L1:
431: //push '0.0' 'a'
432: // == 34
433: bodyp.push(0); // 8
434: bodyp.push("__dcns"); // 4 + 6 +1= 11
435: //getVariable
436: bodyp.getVar(); // 1
437: //push 'length'
438: bodyp.push("length"); // 4 + "length" +1 = 11
439: //getMember
440: bodyp.getMember(); // 1
441: //lessThan
442: bodyp.lessThan(); //1
443: //not
444: bodyp.logicalNot(); // 1
445: // total = (+ 8 11 1 11 1 1 1 ) = 34
446: //branchIfTrue 'L0'
447:
448: // == 5
449: bodyp.jumpIfTrue(34); // 5 bytes //jump L0:
450:
451: // == 34
452: //push '0' 'a'
453: bodyp.push(0); // 4 + data length = 8
454: bodyp.push("__dcns"); // 4 + "__dcns" + 1 = 11
455: //getVariable
456: bodyp.getVar(); // 1
457: //push 'pop'
458: bodyp.push("pop"); // 4 + 1 + 'pop' = 8
459: //callMethod
460: bodyp.callMethod(); // 1
461: //branch 'L1'
462: bodyp.jump(-(34 + 34 + 5)); //5 bytes // jump L1:
463: //L0:
464: }
465:
466: } else if (eventType == xpp.TEXT) {
467: if (xpp.isWhitespace()) {
468: // If the text is all whitespace, then don't create a node.
469: } else {
470: String text = xpp.getText();
471: if (trimWhitespace) {
472: text = text.trim();
473: }
474:
475: // dup pointer to parent (who is at top of stack)
476: // DUP
477: body._writeByte(Actions.PushDuplicate);
478:
479: // Push text
480:
481: if (getByteLength(text, dc.encoding) > 64000) {
482: splitPushString(text, bodyp);
483:
484: bodyp.push(2);
485: bodyp.push(TEXT_INSTANTIATOR_FN);
486: bodyp.callFunction();
487: // Pop the node, because there will be no end tag for it, and it has no children.
488: body._writeByte(Actions.Pop);
489:
490: } else {
491: body._writeByte(Actions.PushData);
492: // Leave a two byte space for the PUSH length
493: // Mark where we are, so we can rewrite this with correct length later.
494: int push_bufferpos = body.getPos();
495: body._writeWord(0); // placeholder 16-bit length field
496:
497: _pushMergedStringData(text, body, dc);
498: // Set up argsnum and function name
499: // PUSH 2, _tdn
500: body._writeByte(0x07); // INT type
501: body._writeDWord(2); // '2' integer constant ; number of args to function
502: body._writeByte(0x08); // SHORT DICTIONARY LOOKUP
503: body._writeByte(textnode_idx); // push function name: index of "_t" string constant
504:
505: // Now go back and fix up the size arg to the PUSH instruction
506: int total_size = body.getPos()
507: - (push_bufferpos + 2);
508: body.writeWordAt(total_size, push_bufferpos);
509: body._writeByte(Actions.CallFunction);
510: // Pop the node, because there will be no end tag for it, and it has no children.
511: body._writeByte(Actions.Pop);
512: }
513:
514: }
515: }
516: eventType = xpp.next();
517: // invariant is that when we start a frame, stack contains parent node list
518: }
519:
520: // return the datacontext, the caller will use it to write the last string pool
521: return dc;
522: }
523:
524: /** Takes the string hash table in DataContext dc, and prepends it to the
525: program in FlashBuffer body. Returns a new program with the stringpool
526: at the start.
527: **/
528: private static Program appendStringPool(DataContext dc,
529: FlashBuffer body) {
530: // Collect the string dictionary data
531: byte pooldata[] = makeStringPool(dc);
532:
533: // sync up size with buffer
534: body.setSize(body.getPos());
535:
536: // 'out' is the main FlashBuffer for composing the output file
537: FlashBuffer out = new FlashBuffer(body.getSize()
538: + pooldata.length + MISC_BUFSIZ);
539: // Write out string constant pool
540: out._writeByte(Actions.ConstantPool);
541: out._writeWord(pooldata.length + 2); // number of bytes in pool data + int (# strings)
542: out._writeWord(dc.cpool.size()); // number of strings in pool
543: out.writeArray(pooldata, 0, pooldata.length); // copy the data
544:
545: // Write out the code to build nodes
546: out.writeArray(body.getBuf(), 0, body.getSize());
547: // This is a program that contains the constant pool plus code
548: Program prog = new Program(out);
549: return prog;
550: }
551:
552: /** default trimWhitespace=false */
553: public static Program makeProgram(Element data, int flashVersion)
554: throws CompilationError {
555:
556: DCOptions options = new DCOptions();
557: options.flashVersion = flashVersion;
558: options.trimWhitespace = false;
559: options.localdata = false;
560:
561: return makeProgram(data, options);
562: }
563:
564: /**
565: Called for compile-time data, don't do frame splitting, and don't add any resultset wrapper
566: */
567: public static Program makeProgram(Element data, int flashVersion,
568: boolean trimWhitespace, boolean localdata, boolean nsprefix)
569: throws CompilationError {
570:
571: DCOptions options = new DCOptions();
572: options.flashVersion = flashVersion;
573: options.trimWhitespace = trimWhitespace;
574: options.localdata = localdata;
575: options.nsprefix = nsprefix;
576: return makeProgram(data, options);
577: }
578:
579: /**
580: Called for compile-time data, don't do frame splitting, and don't add any resultset wrapper
581: */
582: public static Program makeProgram(Element data, DCOptions ioptions)
583: throws CompilationError {
584:
585: DCOptions options = (DCOptions) ioptions.clone();
586: options.splitframes = false;
587: options.addwrapper = false;
588:
589: XMLOutputter outputter = new XMLOutputter();
590: StringWriter sout = new StringWriter();
591: try {
592: outputter.output(data, sout);
593: } catch (IOException ex) {
594: throw new RuntimeException(
595: "DataCompiler makeProgram parsing XML: "
596: + ex.getMessage());
597: }
598: String x = sout.toString();
599: // [TODO 02-10-2003 hqm -- Do we need to catch character conversion errors -> ASCII like we did with JDOM?]
600: try {
601: XmlPullParser xpp = getXPPFactory().newPullParser();
602: xpp.setInput(new StringReader(x));
603: Vector progs = makeProgram(xpp, null, x.length(), options);
604: return ((Program) progs.firstElement());
605: } catch (XmlPullParserException ex) {
606: throw new CompilationError(
607: "DataCompiler makeProgram parsing XML: "
608: + ex.getMessage());
609: } catch (IOException ex) {
610: throw new RuntimeException(
611: "DataCompiler makeProgram parsing XML: "
612: + ex.getMessage());
613: }
614: }
615:
616: /** Bag to hold data compiler options */
617: static class DCOptions implements Cloneable {
618: /** swf target runtime */
619: int flashVersion = 6;
620:
621: /** Split code for building data into multiple frames, to keep player from
622: * getting too sluggish or "player responding slowly" error */
623: boolean splitframes = true;
624:
625: /** add metadata headers info as XML wrapper */
626: boolean addwrapper = false;
627:
628: /** trim whitespace in text content */
629: boolean trimWhitespace = false;
630:
631: /** data inlined at compile time */
632: boolean localdata = false;
633:
634: /** add namespace prefixes to tags and attribute names */
635: boolean nsprefix = false;
636:
637: DCOptions() {
638: }
639:
640: public Object clone() {
641: try {
642: return super .clone();
643: } catch (CloneNotSupportedException e) {
644: throw new RuntimeException(e);
645: }
646: }
647:
648: public String toString() {
649: return ("flashVersion=" + flashVersion + ", "
650: + "splitframes=" + splitframes + ", "
651: + "addwrapper=" + addwrapper + ", "
652: + "trimWhitespace=" + trimWhitespace + ", "
653: + "localdata=" + localdata + ", " + "nsprefix=" + nsprefix);
654: }
655:
656: }
657:
658: /**
659: * Produces a JGenerator Flash Program containing
660: * executable SWF codes to build an XML datasource structure which
661: * represents this XML stream.
662: *
663: * Splits execution across frames when program buffer becomes too large.
664: *
665: *
666: * @param xpp XML XPP parser which is reading from the data content string
667: * @return Vector of one or more (if frame splitting) Flash Programs */
668: public static Vector makeProgram(XmlPullParser xpp,
669: XmlPullParser xpheaders, int xmlsize, DCOptions ioptions)
670: throws IOException, XmlPullParserException {
671: Vector programs = new Vector();
672:
673: DCOptions options = (DCOptions) ioptions.clone();
674:
675: int flashVersion = options.flashVersion;
676: boolean splitframes = options.splitframes;
677: boolean addwrapper = options.addwrapper;
678: boolean trimWhitespace = options.trimWhitespace;
679: boolean localdata = options.localdata;
680: boolean nsprefix = options.nsprefix;
681:
682: // Allocate at least enough room to hold the data nodes and strings, ok to allocate too much;
683: // If we are not splitting frames, allocate enough to hold the whole XML file.
684: FlashBuffer body = new FlashBuffer(splitframes ? FB_INIT_SIZE
685: : (xmlsize * 3) + 128000);
686: Program bodyp = new Program(body);
687: programs.add(bodyp);
688:
689: // Bind the node creation functions to some short local names:
690: // element nodes: _level0._m => _m
691: bodyp.push(new Object[] { "_m", "_level0" });
692: bodyp.getVar();
693: bodyp.push("_m");
694: bodyp.getMember();
695: bodyp.setVar();
696:
697: // text nodes: _level0._t => _t
698: bodyp.push(new Object[] { "_t", "_level0" });
699: bodyp.getVar();
700: bodyp.push("_t");
701: bodyp.getMember();
702: bodyp.setVar();
703:
704: // Build a root node by calling the runtime's root node instantiator
705: // The root node will have $n = 0, and whatever other initial conditions are needed.
706: bodyp.push(0); // Root node creator function takes no args.
707: bodyp.push("_level0");
708: bodyp.getVar();
709: bodyp.push(ROOT_NODE_INSTANTIATOR_FN);
710: bodyp.callMethod();
711: // The root node is now on the stack.
712:
713: if (addwrapper) {
714: // dup root node
715: body.writeByte(Actions.PushDuplicate);
716: // Create and push <resultset> node on stack
717: bodyp.push("resultset");
718: bodyp.push(0);
719: body._writeByte(Actions.InitObject);
720: bodyp.push(3); // 3 args : makeElementNode(attrs, name, parent)
721: bodyp.push(NODE_INSTANTIATOR_FN);
722: bodyp.callFunction();
723: // stack = <root> <resultset>
724:
725: // Push <body> element
726: body.writeByte(Actions.PushDuplicate);
727: bodyp.push("body");
728: bodyp.push(0);
729: body._writeByte(Actions.InitObject); // {}
730: bodyp.push(3); // 3 args : makeElementNode(attrs, name, parent)
731: bodyp.push(NODE_INSTANTIATOR_FN);
732: bodyp.callFunction(); // stack = <root> <resultset> <body>
733: }
734:
735: // Build data, which will get stuck under <body> node
736: DataContext dc = makeDataContext(flashVersion);
737: // Always insert the node constructor function names as the
738: // first entries in string pool in this order.
739: addStringConstant(NODE_INSTANTIATOR_FN, dc);
740: addStringConstant(TEXT_INSTANTIATOR_FN, dc);
741: // If it's a big multiframe dataset, a new dc in a new frame may be returned.
742:
743: // If we are adding a wrapper, the stack has two extra items on it already, <resultset> and <body>.
744: int stackdepth = (addwrapper ? 3 : 1);
745: dc = writeXMLData(xpp, programs, dc, stackdepth, options);
746: // Multiple frames may have been created, so append
747: // finalization code to the last program in the list.
748: bodyp = (Program) programs.lastElement();
749: body = bodyp.body();
750:
751: // Finalization Phase
752: if (addwrapper) {
753: // pop the <body> node
754: bodyp.pop();
755: // stack == <root> <resultset>
756: // Add in <headers>, they will get the <resultset> node as parent,
757: // since it's on top of stack
758: options.splitframes = false;
759: dc = writeXMLData(xpheaders, programs, dc, stackdepth,
760: options);
761: bodyp = (Program) programs.lastElement();
762: body = bodyp.body();
763: bodyp.pop(); // stack == <root>
764: }
765:
766: // Call the node finalize function; takes one arg: the scaffolding root node
767: bodyp.push(1);
768: bodyp.push("_level0");
769: bodyp.getVar();
770: bodyp.push(ROOT_NODE_FINAL_FN);
771: bodyp.callMethod();
772:
773: // The <resultset> node is on the stack now
774:
775: // This is used by the data compiler to compile inline
776: // datasets into an app swf file.
777: body.writeByte(Actions.PushDuplicate);
778: bodyp.push("__lzdataroot");
779: body.writeByte(Actions.StackSwap);
780: bodyp.setVar();
781:
782: // If we're runtime-loaded (non-local) data, we need to call
783: // back to our parent movieclip data loader.
784: if (!localdata) {
785: // Call to _parent.loader.returnData(_parent, data)
786: bodyp.push("_parent");
787: bodyp.getVar();
788: bodyp.push(2);
789: bodyp.push("_parent");
790: bodyp.getVar();
791: bodyp.push("loader");
792: bodyp.getMember();
793: bodyp.push("returnData");
794: bodyp.callMethod();
795: bodyp.pop();
796:
797: if (splitframes) {
798: // add a STOP for multiframe programs, otherwise it loops
799: bodyp.stop();
800: }
801: }
802:
803: // prepend the string pool to the program,and add it to the frame list
804: Program withpool = appendStringPool(dc, body);
805: // add to the list of frame/programs
806: programs.setElementAt(withpool, programs.size() - 1);
807:
808: return programs;
809: }
810:
811: // default nsprefix to false
812: private static FlashFile makeSWF(XmlPullParser xpp,
813: XmlPullParser xpheaders, int xmlsize, int flashVersion,
814: boolean addwrapper, boolean trimWhitespace)
815: throws IOException, XmlPullParserException {
816: return makeSWF(xpp, xpheaders, xmlsize, flashVersion,
817: addwrapper, trimWhitespace, false);
818: }
819:
820: /**
821: * Make SWF
822: *
823: * @param xpp parser which is pointing at XML data
824: * @return FlashFile containing entire SWF with header
825: */
826: private static FlashFile makeSWF(XmlPullParser xpp,
827: XmlPullParser xpheaders, int xmlsize, int flashVersion,
828: boolean addwrapper, boolean trimWhitespace, boolean nsprefix)
829: throws IOException, XmlPullParserException {
830: DCOptions options = new DCOptions();
831: options.flashVersion = flashVersion;
832: options.trimWhitespace = trimWhitespace;
833: options.localdata = false;
834: options.splitframes = true;
835: options.addwrapper = addwrapper;
836: options.nsprefix = nsprefix;
837:
838: // Create FlashFile object nd include action bytes
839: FlashFile file = FlashFile.newFlashFile();
840: Script s = new Script(1);
841: file.setMainScript(s);
842: file.setVersion(flashVersion);
843: Vector progs = makeProgram(xpp, xpheaders, xmlsize, options);
844:
845: // iterate making a frame for each program
846: while (progs.size() > 0) {
847: Program prog = (Program) progs.firstElement();
848: progs.removeElementAt(0); // pop
849: Frame frame = s.newFrame();
850: frame.addFlashObject(new DoAction(prog));
851: }
852:
853: return file;
854: }
855:
856: /** default trimWhitespace to true, for back compatibility */
857: public static InputStream getSWF(XmlPullParser xpp,
858: XmlPullParser xpheaders, int xmlsize, int flashVersion,
859: boolean addwrapper) throws IOException,
860: XmlPullParserException {
861: return getSWF(xpp, xpheaders, xmlsize, flashVersion,
862: addwrapper, true, false, false);
863: }
864:
865: /**
866: * Get XML to output stream SWF
867: *
868: * @param xpp an XPP XML parser which points to the XML data
869: * @param xppheaders an XPP XML parser which points to HTTP or other header XML metadata
870: * @param flashVersion 5 or greater
871: * @param addwrapper Set to true if you pass in a separate HTTP headers string
872: * @param trimWhitespace controls whether whitespace is trimmed in text content
873: * @return swf input stream
874: */
875: public static InputStream getSWF(XmlPullParser xpp,
876: XmlPullParser xpheaders, int xmlsize, int flashVersion,
877: boolean addwrapper, boolean trimWhitespace,
878: boolean compress, boolean nsprefix) throws IOException,
879: XmlPullParserException {
880: // Get inputstream and write to outputstream
881: InputStream input;
882: int i = 0;
883: try {
884: FlashFile file = makeSWF(xpp, xpheaders, xmlsize,
885: flashVersion, addwrapper, trimWhitespace, nsprefix);
886: if (flashVersion > 5) {
887: if (compress) {
888: file.setCompressed(true);
889: }
890: }
891: return file.generate().getInputStream();
892: } catch (IVException ex) {
893: throw new ChainedException(ex);
894: } catch (IOException e) {
895: mLogger.error("io error getting SWF: " + e.getMessage());
896: throw e;
897: }
898: }
899:
900: }
|