org.apache.flex.swf.io.SWFDump.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flex.swf.io.SWFDump.java

Source

/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.flex.swf.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.IOUtils;

import org.apache.flex.abc.ABCParser;
import org.apache.flex.abc.PoolingABCVisitor;
import org.apache.flex.abc.print.ABCDumpVisitor;
import org.apache.flex.compiler.clients.problems.CompilerProblemCategorizer;
import org.apache.flex.compiler.clients.problems.ProblemFormatter;
import org.apache.flex.compiler.clients.problems.ProblemPrinter;
import org.apache.flex.compiler.clients.problems.ProblemQuery;
import org.apache.flex.compiler.clients.problems.WorkspaceProblemFormatter;
import org.apache.flex.compiler.common.VersionInfo;
import org.apache.flex.compiler.internal.workspaces.Workspace;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.swf.Header;
import org.apache.flex.swf.SWF;
import org.apache.flex.swf.SWFFrame;
import org.apache.flex.swf.TagType;
import org.apache.flex.swf.tags.CSMTextSettingsTag;
import org.apache.flex.swf.tags.DefineBinaryDataTag;
import org.apache.flex.swf.tags.DefineBitsJPEG3Tag;
import org.apache.flex.swf.tags.DefineBitsLossless2Tag;
import org.apache.flex.swf.tags.DefineBitsLosslessTag;
import org.apache.flex.swf.tags.DefineBitsTag;
import org.apache.flex.swf.tags.DefineButton2Tag;
import org.apache.flex.swf.tags.DefineButtonCxformTag;
import org.apache.flex.swf.tags.DefineButtonSoundTag;
import org.apache.flex.swf.tags.DefineButtonTag;
import org.apache.flex.swf.tags.DefineEditTextTag;
import org.apache.flex.swf.tags.DefineFont2Tag;
import org.apache.flex.swf.tags.DefineFont3Tag;
import org.apache.flex.swf.tags.DefineFont4Tag;
import org.apache.flex.swf.tags.DefineFontAlignZonesTag;
import org.apache.flex.swf.tags.DefineFontInfoTag;
import org.apache.flex.swf.tags.DefineFontNameTag;
import org.apache.flex.swf.tags.DefineFontTag;
import org.apache.flex.swf.tags.DefineMorphShape2Tag;
import org.apache.flex.swf.tags.DefineMorphShapeTag;
import org.apache.flex.swf.tags.DefineScalingGridTag;
import org.apache.flex.swf.tags.DefineShape3Tag;
import org.apache.flex.swf.tags.DefineShape4Tag;
import org.apache.flex.swf.tags.DefineShapeTag;
import org.apache.flex.swf.tags.DefineSoundTag;
import org.apache.flex.swf.tags.DefineSpriteTag;
import org.apache.flex.swf.tags.DefineTextTag;
import org.apache.flex.swf.tags.DefineVideoStreamTag;
import org.apache.flex.swf.tags.DoABCTag;
import org.apache.flex.swf.tags.EnableDebugger2Tag;
import org.apache.flex.swf.tags.ExportAssetsTag;
import org.apache.flex.swf.tags.FileAttributesTag;
import org.apache.flex.swf.tags.FrameLabelTag;
import org.apache.flex.swf.tags.ICharacterTag;
import org.apache.flex.swf.tags.ITag;
import org.apache.flex.swf.tags.MetadataTag;
import org.apache.flex.swf.tags.PlaceObject2Tag;
import org.apache.flex.swf.tags.PlaceObject3Tag;
import org.apache.flex.swf.tags.PlaceObjectTag;
import org.apache.flex.swf.tags.ProductInfoTag;
import org.apache.flex.swf.tags.RawTag;
import org.apache.flex.swf.tags.RemoveObject2Tag;
import org.apache.flex.swf.tags.RemoveObjectTag;
import org.apache.flex.swf.tags.ScriptLimitsTag;
import org.apache.flex.swf.tags.SetBackgroundColorTag;
import org.apache.flex.swf.tags.SetTabIndexTag;
import org.apache.flex.swf.tags.ShowFrameTag;
import org.apache.flex.swf.tags.SoundStreamBlockTag;
import org.apache.flex.swf.tags.SoundStreamHeadTag;
import org.apache.flex.swf.tags.StartSoundTag;
import org.apache.flex.swf.tags.SymbolClassTag;
import org.apache.flex.swf.tags.Tag;
import org.apache.flex.swf.tags.VideoFrameTag;
import org.apache.flex.swf.types.ButtonRecord;
import org.apache.flex.swf.types.CurvedEdgeRecord;
import org.apache.flex.swf.types.EdgeRecord;
import org.apache.flex.swf.types.FillStyle;
import org.apache.flex.swf.types.FillStyleArray;
import org.apache.flex.swf.types.Filter;
import org.apache.flex.swf.types.FocalGradient;
import org.apache.flex.swf.types.GlyphEntry;
import org.apache.flex.swf.types.GradRecord;
import org.apache.flex.swf.types.IFillStyle;
import org.apache.flex.swf.types.ILineStyle;
import org.apache.flex.swf.types.KerningRecord;
import org.apache.flex.swf.types.LineStyle;
import org.apache.flex.swf.types.LineStyle2;
import org.apache.flex.swf.types.LineStyleArray;
import org.apache.flex.swf.types.MorphFillStyle;
import org.apache.flex.swf.types.MorphGradRecord;
import org.apache.flex.swf.types.MorphLineStyle;
import org.apache.flex.swf.types.RGB;
import org.apache.flex.swf.types.RGBA;
import org.apache.flex.swf.types.Shape;
import org.apache.flex.swf.types.ShapeRecord;
import org.apache.flex.swf.types.ShapeWithStyle;
import org.apache.flex.swf.types.SoundEnvelope;
import org.apache.flex.swf.types.SoundInfo;
import org.apache.flex.swf.types.StraightEdgeRecord;
import org.apache.flex.swf.types.StyleChangeRecord;
import org.apache.flex.swf.types.Styles;
import org.apache.flex.swf.types.TextRecord;
import org.apache.flex.swf.types.ZoneRecord;
import org.apache.flex.utils.Base64;
import org.apache.flex.utils.Trace;

import com.google.common.collect.ImmutableList;

/**
 * Dump a SWF model to XML. Usage: swfdump [-abc] file1.swf
 */
public final class SWFDump {
    /**
     * Dump a SWF at a given URL.
     * 
     * @param url URL of the SWF to dump.
     * @throws IOException
     */
    private void dump(URL url) throws IOException {
        final SWFReader swfReader = new SWFReader();
        final String path = url.getPath();
        try {
            swf = (SWF) swfReader.readFrom(new BufferedInputStream(url.openStream()), path);

            ProblemQuery problemQuery = new ProblemQuery();
            problemQuery.addAll(swfReader.getProblems());
            if (!problemQuery.hasErrors()) {
                dumpHeader(swf.getHeader());
                final ImmutableList<ITag> tags = ImmutableList.copyOf(swfReader.iterator());
                currentFrameIndex = 0;
                if (swf.getFrameCount() > 0)
                    currentFrame = swf.getFrameAt(0);

                for (ITag tag : tags) {
                    dumpTag(tag);
                    if (tag.getTagType() == TagType.ShowFrame) {
                        currentFrameIndex++;
                        if (currentFrameIndex < swf.getFrameCount())
                            currentFrame = swf.getFrameAt(currentFrameIndex);
                    }
                }

                finish();
            }

            printProblems(swfReader.getProblems());
        } finally {
            IOUtils.closeQuietly(swfReader);
        }
    }

    /**
     * Print out problems to standard output.
     * 
     * @param problems
     */
    private void printProblems(Collection<ICompilerProblem> problems) {
        CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer();
        ProblemFormatter formatter = new WorkspaceProblemFormatter(new Workspace(), categorizer);
        ProblemPrinter printer = new ProblemPrinter(formatter, System.err);

        printer.printProblems(problems);
    }

    /**
     * Dump a tag.
     * 
     * @param tag the tag to dump.
     */
    private void dumpTag(ITag tag) {
        TagType type = tag.getTagType();

        switch (type) {
        case CSMTextSettings:
            dumpCSMTextSettings((CSMTextSettingsTag) tag);
            break;
        case DoABC:
            dumpDoABC((DoABCTag) tag);
            break;
        case DefineBinaryData:
            dumpDefineBinaryData((DefineBinaryDataTag) tag);
            break;
        case DefineBits:
            dumpDefineBits((DefineBitsTag) tag);
            break;
        case DefineBitsJPEG2:
            dumpDefineBitsJPEG2((DefineBitsTag) tag);
            break;
        case DefineBitsJPEG3:
            dumpDefineBitsJPEG3((DefineBitsJPEG3Tag) tag);
            break;
        case DefineBitsLossless:
            dumpDefineBitsLossless((DefineBitsLosslessTag) tag);
            break;
        case DefineBitsLossless2:
            dumpDefineBitsLossless2((DefineBitsLossless2Tag) tag);
            break;
        case DefineScalingGrid:
            dumpDefineScalingGrid((DefineScalingGridTag) tag);
            break;
        case DefineShape:
            dumpDefineShape((DefineShapeTag) tag);
            break;
        case DefineShape2:
            dumpDefineShape2((DefineShapeTag) tag);
            break;
        case DefineShape3:
            dumpDefineShape3((DefineShape3Tag) tag);
            break;
        case DefineShape4:
            dumpDefineShape4((DefineShape4Tag) tag);
            break;
        case DefineSprite:
            dumpDefineSprite((DefineSpriteTag) tag);
            break;
        case DefineSound:
            dumpDefineSound((DefineSoundTag) tag);
            break;
        case StartSound:
            dumpStartSound((StartSoundTag) tag);
            break;
        case StartSound2:
            // TODO StartSound2
            //              dumpStartSound2(); 
            break;
        case SoundStreamHead:
            dumpSoundStreamHead((SoundStreamHeadTag) tag);
            break;
        case SoundStreamHead2:
            dumpSoundStreamHead2((SoundStreamHeadTag) tag);
            break;
        case SoundStreamBlock:
            dumpSoundStreamBlock((SoundStreamBlockTag) tag);
            break;
        case DefineMorphShape:
            dumpDefineMorphShape((DefineMorphShapeTag) tag);
            break;
        case DefineMorphShape2:
            dumpDefineMorphShape2((DefineMorphShapeTag) tag);
            break;
        case DefineSceneAndFrameLabelData:
            // TODO: no dump routine for this tag.
            //dumpDefineSceneAndFrameLabelData();
            break;
        case DefineFont:
            dumpDefineFont((DefineFontTag) tag);
            break;
        case DefineFontInfo:
            dumpDefineFontInfo((DefineFontInfoTag) tag);
            break;
        case DefineFont2:
            dumpDefineFont2((DefineFont2Tag) tag);
            break;
        case DefineFont3:
            dumpDefineFont3((DefineFont3Tag) tag);
            break;
        case DefineFont4:
            dumpDefineFont4((DefineFont4Tag) tag);
            break;
        case DefineFontAlignZones:
            dumpDefineFontAlignZones((DefineFontAlignZonesTag) tag);
            break;
        case DefineFontName:
            dumpDefineFontName((DefineFontNameTag) tag);
            break;
        case DefineText:
            dumpDefineText((DefineTextTag) tag);
            break;
        case DefineText2:
            dumpDefineText((DefineTextTag) tag);
            break;
        case DefineEditText:
            dumpDefineEditText((DefineEditTextTag) tag);
            break;
        case DefineButton:
            dumpDefineButton((DefineButtonTag) tag);
            break;
        case DefineButton2:
            dumpDefineButton2((DefineButton2Tag) tag);
            break;
        case DefineButtonSound:
            dumpDefineButtonSound((DefineButtonSoundTag) tag);
            break;
        case DefineVideoStream:
            dumpDefineVideoStream((DefineVideoStreamTag) tag);
            break;
        case VideoFrame:
            dumpVideoFrame((VideoFrameTag) tag);
            break;
        case End:
            break;
        case EnableDebugger2:
            dumpEnableDebugger2((EnableDebugger2Tag) tag);
            break;
        case ExportAssets:
            dumpExportAssets((ExportAssetsTag) tag);
            break;
        case FileAttributes:
            dumpFileAttributes((FileAttributesTag) tag);
            break;
        case FrameLabel:
            dumpFrameLabel((FrameLabelTag) tag);
            break;
        case JPEGTables:
            // TODO: handle this tag.
            //dumpJPEGTables();
            break;
        case Metadata:
            dumpMetadata((MetadataTag) tag);
            break;
        case ProductInfo:
            dumpProductInfo((ProductInfoTag) tag);
            break;
        case PlaceObject:
            dumpPlaceObject((PlaceObjectTag) tag);
            break;
        case PlaceObject2:
            dumpPlaceObject2((PlaceObjectTag) tag);
            break;
        case PlaceObject3:
            dumpPlaceObject3((PlaceObjectTag) tag);
            break;
        case RemoveObject:
            dumpRemoveObject((RemoveObjectTag) tag);
            break;
        case RemoveObject2:
            dumpRemoveObject2((RemoveObject2Tag) tag);
            break;
        case ScriptLimits:
            dumpScriptLimits((ScriptLimitsTag) tag);
            break;
        case SetBackgroundColor:
            dumpSetBackgroundColor((SetBackgroundColorTag) tag);
            break;
        case SetTabIndex:
            dumpSetTabIndex((SetTabIndexTag) tag);
            break;
        case ShowFrame:
            dumpShowFrame((ShowFrameTag) tag);
            break;
        case SymbolClass:
            dumpSymbolClass((SymbolClassTag) tag);
            break;
        default:
            assert (tag instanceof RawTag);
            if (tag instanceof RawTag)
                dumpRawTag((RawTag) tag);
            break;
        }

    }

    /**
     * this value should get set after the header is parsed
     */
    @SuppressWarnings("unused")
    private Integer swfVersion = null;

    private boolean abc = false;
    private boolean showActions = true;
    private boolean showOffset = false;
    @SuppressWarnings("unused")
    private boolean showByteCode = false;
    @SuppressWarnings("unused")
    private boolean showDebugSource = false;
    private boolean glyphs = true;
    private boolean external = false;
    private String externalPrefix = null;
    private String externalDirectory = null;
    @SuppressWarnings("unused")
    private boolean decompile;
    @SuppressWarnings("unused")
    private boolean defunc;
    private int indent = 0;
    private boolean tabbedGlyphs = true;
    private SWF swf;

    /**
     * Constructor.
     * 
     * @param out The output stream.
     */
    public SWFDump(PrintWriter out) {
        this.out = out;
    }

    // TODO: We currently don't decode actions.
    //    private void printActions(ActionList list)
    //    {
    //        if (decompile)
    //        {
    //            /*
    //             AsNode node;
    //             try
    //             {
    //             node = new Decompiler(defunc).decompile(list);
    //             new PrettyPrinter(out, indent).list(node);
    //             return;
    //             }
    //             catch (Exception e)
    //             {
    //             indent();
    //             out.println("// error while decompiling.  falling back to disassembler");
    //             }
    //             */
    //        }
    //        
    //        Disassembler disassembler = new Disassembler(out, showOffset, indent);
    //        if (showDebugSource)
    //        {
    //            disassembler.setShowDebugSource(showDebugSource);
    //            disassembler.setComment("// ");
    //        }
    //        list.visitAll(disassembler);
    //    }

    @SuppressWarnings("unused")
    private void setExternal(boolean b, String path) {
        external = b;

        if (external) {
            if (path != null) {
                externalPrefix = baseName(path);
                externalDirectory = dirName(path);
            }

            if (externalPrefix == null)
                externalPrefix = "";
            else
                externalPrefix += "-";
            if (externalDirectory == null)
                externalDirectory = "";
        }
    }

    private void indent() {
        for (int i = 0; i < indent; i++) {
            out.print("  ");
        }
    }

    public void dumpHeader(Header h) {
        swfVersion = h.getVersion();
        out.println("<swf xmlns=\"http://macromedia/2003/swfx\"" + " version=\"" + h.getVersion() + "\""
                + " framerate=\"" + h.getFrameRate() + "\"" + " size=\"" + h.getFrameSize().getWidth() + "x"
                + h.getFrameSize().getHeight() + "\"" + " compressed=\""
                + (h.getCompression() != Header.Compression.NONE) + "\"" + " >");
        indent++;
        indent();
        out.println("<!-- framecount=" + h.getFrameCount() + " length=" + h.getLength() + " -->");
    }

    public void dumpProductInfo(ProductInfoTag productInfo) {
        open(productInfo);
        out.print(" product=\"" + productInfo.getProduct() + "\"");
        out.print(" edition=\"" + productInfo.getEdition() + "\"");
        out.print(" version=\"" + productInfo.getMajorVersion() + "." + productInfo.getMinorVersion() + "\"");
        out.print(" build=\"" + productInfo.getBuild() + "\"");
        out.print(
                " compileDate=\"" + DateFormat.getInstance().format(new Date(productInfo.getCompileDate())) + "\"");
        close();
    }

    public void dumpMetadata(MetadataTag tag) {
        open(tag);
        end();
        indent();
        openCDATA();
        out.println(tag.getMetadata());
        closeCDATA();
        close(tag);
    }

    public void dumpFileAttributes(FileAttributesTag tag) {
        open(tag);
        out.print(" useDirectBlit=\"" + tag.isUseDirectBlit() + "\"");
        out.print(" useGPU=\"" + tag.isUseGPU() + "\"");
        out.print(" hasMetadata=\"" + tag.isHasMetadata() + "\"");
        out.print(" actionScript3=\"" + tag.isAS3() + "\"");
        out.print(" suppressCrossDomainCaching=\"" + tag.isSuppressCrossDomainCaching() + "\"");
        out.print(" swfRelativeUrls=\"" + tag.isSWFRelativeURLs() + "\"");
        out.print(" useNetwork=\"" + tag.isUseNetwork() + "\"");
        close();
    }

    private final PrintWriter out;

    private SWFFrame currentFrame;
    private int currentFrameIndex;

    /**
     * Get the symbol name of a class.
     * 
     * @param tag tag to look up.
     * @return Name of tag in the symbol class table, null if not found.
     */
    public String id(ICharacterTag tag) {
        return String.valueOf(tag.getCharacterID());
    }

    public void setOffsetAndSize(int offset, int size) {
        // Note: 'size' includes the size of the tag's header
        // so it is either length + 2 or length + 6.

        if (showOffset) {
            indent();
            out.println("<!--" + " offset=" + offset + " size=" + size + " -->");
        }
    }

    /**
     * Output the start of XML output for a SWF tag.
     * 
     * @param tag the tag to output.
     */
    private void open(ITag tag) {
        indent();
        out.print("<" + tag.getTagType().toString());
    }

    /**
     * Output the end of XML output for a SWF tag.
     * 
     * @param tag the tag to output.
     */
    private void end() {
        out.println(">");
        indent++;
    }

    private void openCDATA() {
        indent();
        out.print("<![CDATA[");
    }

    private void closeCDATA() {
        out.println("]]>");
    }

    private void close() {
        out.println("/>");
    }

    private void close(Tag tag) {
        indent--;
        indent();
        out.println("</" + tag.getTagType().toString() + ">");
    }

    public void error(String s) {
        indent();
        out.println("<!-- error: " + s + " -->");
    }

    public void dumpRawTag(RawTag tag) {
        indent();
        out.println("<!-- unknown tag=" + tag.getTagType().getValue() + " length="
                + (tag.getTagBody() != null ? tag.getTagBody().length : 0) + " -->");
    }

    public void dumpShowFrame(ShowFrameTag tag) {
        open(tag);
        close();
    }

    public void dumpDefineShape(DefineShapeTag tag) {
        printDefineShape(tag);
    }

    private void printDefineShape(DefineShapeTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" bounds=\"" + tag.getShapeBounds() + "\"");
        if (tag.getTagType() == TagType.DefineShape4) {
            DefineShape4Tag shape4Tag = (DefineShape4Tag) tag;
            out.print(" edgebounds=\"" + shape4Tag.getEdgeBounds() + "\"");
            out.print(" usesNonScalingStrokes=\"" + shape4Tag.isUsesNonScalingStrokes() + "\"");
            out.print(" usesScalingStrokes=\"" + shape4Tag.isUsesScalingStrokes() + "\"");
        }

        end();

        printShapeWithStyles(tag.getShapes());

        close(tag);
    }

    static final char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
            'E', 'F' };

    /**
     * @param color to print.
     * @return string formatted as #RRGGBB
     */
    public String printColor(RGB color) {
        StringBuilder b = new StringBuilder();
        b.append('#');
        int red = color.getRed();
        b.append(digits[(red >> 4) & 15]);
        b.append(digits[red & 15]);
        int green = color.getGreen();
        b.append(digits[(green >> 4) & 15]);
        b.append(digits[green & 15]);
        int blue = color.getBlue();
        b.append(digits[(blue >> 4) & 15]);
        b.append(digits[blue & 15]);

        if (color instanceof RGBA) {
            int alpha = ((RGBA) color).getAlpha();
            b.append(digits[(alpha >> 4) & 15]);
            b.append(digits[alpha & 15]);
        }

        return b.toString();
    }

    public void dumpPlaceObject(PlaceObjectTag tag) {
        open(tag);
        out.print(" idref=\"" + idRef(tag.getCharacter()) + "\"");
        out.print(" depth=\"" + tag.getDepth() + "\"");
        out.print(" matrix=\"" + tag.getMatrix() + "\"");
        if (tag.getColorTransform() != null)
            out.print(" colorXform=\"" + tag.getColorTransform() + "\"");
        close();
    }

    public void dumpRemoveObject(RemoveObjectTag tag) {
        open(tag);
        out.print(" idref=\"" + idRef(tag.getCharacter()) + "\"");
        close();
    }

    public void outputBase64(byte[] data) {
        Base64.Encoder e = new Base64.Encoder(1024);

        indent();
        int remain = data.length;
        while (remain > 0) {
            int block = 1024;
            if (block > remain)
                block = remain;
            e.encode(data, data.length - remain, block);
            out.print(e.drain());
            remain -= block;
        }
        out.println(e.flush());
    }

    public void dumpDefineBits(DefineBitsTag tag) {
        if (tag.getData() == null) {
            out.println("<!-- warning: no JPEG table tag found. -->");
        }

        open(tag);
        out.print(" id=\"" + id(tag) + "\"");

        //        if (external)
        //        {
        //            String path = externalDirectory
        //            + externalPrefix
        //            + "image"
        //            + dictionary.getId(tag)
        //            + ".jpg";
        //            
        //            out.println(" src=\"" + path + "\" />");
        //            try
        //            {
        //                FileOutputStream image = new FileOutputStream(path, false);
        //                SwfImageUtils.JPEG jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data);
        //                jpeg.write(image);
        //                image.close();
        //            }
        //            catch (IOException e)
        //            {
        //                out.println("<!-- error: unable to write external asset file " + path + "-->");
        //            }
        //        }
        //        else
        {
            out.print(" encoding=\"base64\"");
            end();
            outputBase64(tag.getData());
            close(tag);
        }
    }

    public void dumpDefineButton(DefineButtonTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        end();
        if (showActions) {
            openCDATA();
            // todo print button records
            outputBase64(tag.getActions());
            closeCDATA();
        } else {
            // TODO: dump out raw action bytes
            //            out.println("<!-- " + tag.condActions[0].actionList.size() + " action(s) elided -->");
        }
        close(tag);
    }

    public void dumpSetBackgroundColor(SetBackgroundColorTag tag) {
        open(tag);
        out.print(" color=\"" + printColor(tag.getColor()) + "\"");
        close();
    }

    public void dumpDefineFont(DefineFontTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        end();

        if (glyphs) {
            for (int i = 0; i < tag.getGlyphShapeTable().length; i++) {
                indent();
                out.println("<glyph>");

                Shape shape = tag.getGlyphShapeTable()[i];
                indent++;
                printShapeWithTabs(shape);
                indent--;

                indent();
                out.println("</glyph>");
            }
        }
        close(tag);
    }

    public void dumpDefineText(DefineTextTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" bounds=\"" + tag.getTextBounds() + "\"");
        out.print(" matrix=\"" + tag.getTextMatrix() + "\"");

        end();

        for (TextRecord tr : tag.getTextRecords()) {
            printTextRecord(tr, tag.getTagType().getValue());
        }

        close(tag);
    }

    // TODO: decode actions
    //    public void doAction(DoAction tag)
    //    {
    //        open(tag);
    //        end();
    //        
    //        if (showActions)
    //        {
    //            openCDATA();
    //            printActions(tag.actionList);
    //            closeCDATA();
    //        }
    //        else
    //        {
    //            out.println("<!-- " + tag.actionList.size() + " action(s) elided -->");
    //        }
    //        close(tag);
    //    }

    public void dumpDefineFontInfo(DefineFontInfoTag tag) {
        open(tag);
        out.print(" idref=\"" + idRef(tag.getFontTag()) + "\"");
        out.print(" ansi=\"" + tag.isFontFlagsANSI() + "\"");
        out.print(" italic=\"" + tag.isFontFlagsItalic() + "\"");
        out.print(" bold=\"" + tag.isFontFlagsBold() + "\"");
        out.print(" wideCodes=\"" + tag.isFontFlagsWideCodes() + "\"");
        out.print(" smallText=\"" + tag.isFontFlagsSmallText() + "\"");
        out.print(" name=\"" + escape(tag.getFontName()) + "\"");
        out.print(" shiftJIS=\"" + tag.isFontFlagsShiftJIS() + "\"");
        end();
        indent();
        int[] codeTable = tag.getCodeTable();
        for (int i = 0; i < codeTable.length; i++) {
            out.print((int) codeTable[i]);
            if ((i + 1) % 16 == 0) {
                out.println();
                indent();
            } else {
                out.print(' ');
            }
        }
        if (codeTable.length % 16 != 0) {
            out.println();
            indent();
        }
        close(tag);
    }

    public void dumpDefineSound(DefineSoundTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" format=\"" + tag.getSoundFormat() + "\"");
        out.print(" rate=\"" + tag.getSoundRate() + "\"");
        out.print(" size=\"" + tag.getSoundSize() + "\"");
        out.print(" type=\"" + tag.getSoundType() + "\"");
        out.print(" sampleCount=\"" + tag.getSoundSampleCount() + "\"");
        out.print(" soundDataSize=\"" + tag.getSoundData().length + "\"");
        end();
        openCDATA();
        outputBase64(tag.getSoundData());
        closeCDATA();
        close(tag);
    }

    public void dumpStartSound(StartSoundTag tag) {
        open(tag);
        out.print(" soundid=\"" + idRef(tag.getSoundTag()) + "\"");
        printSoundInfo(tag.getSoundInfo());
        close(tag);
    }

    private void printSoundInfo(SoundInfo info) {
        out.print(" syncStop=\"" + info.isSyncStop() + "\"");
        out.print(" syncNoMultiple=\"" + info.isSyncNoMultiple() + "\"");
        if (info.getInPoint() != 0) {
            out.print(" inPoint=\"" + info.getInPoint() + "\"");
        }
        if (info.getOutPoint() != 0) {
            out.print(" outPoint=\"" + info.getOutPoint() + "\"");
        }
        if (info.getLoopCount() != 0) {
            out.print(" loopCount=\"" + info.getLoopCount() + "\"");
        }
        end();

        SoundEnvelope[] envelopes = info.getEnvelopeRecords();
        if (envelopes != null && envelopes.length > 0) {
            openCDATA();
            for (int i = 0; i < envelopes.length; i++) {
                out.println("pos44      =\"" + envelopes[i].getPos44() + "\"");
                out.println("left level =\"" + envelopes[i].getLeftLevel() + "\"");
                out.println("right level=\"" + envelopes[i].getRightLevel() + "\"");
            }
            closeCDATA();
        }
    }

    public void dumpDefineButtonSound(DefineButtonSoundTag tag) {
        open(tag);
        out.print(" buttonId=\"" + idRef(tag.getButtonTag()) + "\"");
        close();
    }

    public void dumpSoundStreamHead(SoundStreamHeadTag tag) {
        open(tag);
        close();
    }

    public void dumpSoundStreamBlock(SoundStreamBlockTag tag) {
        open(tag);
        close();
    }

    public void dumpDefineBinaryData(DefineBinaryDataTag tag) {
        open(tag);
        out.println(" id=\"" + id(tag) + "\" length=\"" + tag.getData().length + "\" />");
    }

    public void dumpDefineBitsLossless(DefineBitsLosslessTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\" width=\"" + tag.getBitmapWidth() + "\" height=\"" + tag.getBitmapHeight()
                + "\"");

        if (external) {
            String path = externalDirectory + externalPrefix + "image" + id(tag) + ".bitmap";

            out.println(" src=\"" + path + "\" />");
            try {
                FileOutputStream image = new FileOutputStream(path, false);
                image.write(tag.getData());
                image.close();
            } catch (IOException e) {
                out.println("<!-- error: unable to write external asset file " + path + "-->");
            }
        } else {
            out.print(" encoding=\"base64\"");
            end();
            outputBase64(tag.getData());
            close(tag);
        }
    }

    public void dumpDefineBitsJPEG2(DefineBitsTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");

        if (external) {
            String path = externalDirectory + externalPrefix + "image" + id(tag) + ".jpg";

            out.println(" src=\"" + path + "\" />");
            try {
                FileOutputStream image = new FileOutputStream(path, false);
                image.write(tag.getData());
                image.close();
            } catch (IOException e) {
                out.println("<!-- error: unable to write external asset file " + path + "-->");
            }
        } else {
            out.print(" encoding=\"base64\"");
            end();
            outputBase64(tag.getData());
            close(tag);
        }
    }

    public void dumpDefineShape2(DefineShapeTag tag) {
        printDefineShape(tag);
    }

    public void dumpDefineButtonCxform(DefineButtonCxformTag tag) {
        open(tag);
        out.print(" buttonId=\"" + idRef(tag.getButtonTag()) + "\"");
        close();
    }

    // TODO: Handle the Protect Tag
    //    public void protect(GenericTag tag)
    //    {
    //        open(tag);
    //        if (tag.data != null)
    //            out.print(" password=\"" + hexify(tag.data) + "\"");
    //        close();
    //    }

    public void dumpPlaceObject2(PlaceObjectTag tag) {
        dumpPlaceObject23(tag);
    }

    public void dumpPlaceObject3(PlaceObjectTag tag) {
        dumpPlaceObject23(tag);
    }

    public void dumpPlaceObject23(PlaceObjectTag tag) {
        PlaceObject2Tag tag2 = tag instanceof PlaceObject2Tag ? (PlaceObject2Tag) tag : null;
        PlaceObject3Tag tag3 = tag instanceof PlaceObject3Tag ? (PlaceObject3Tag) tag : null;

        if (tag != null || (tag2 != null && tag2.isHasCharacter())) {
            if (tag.getCharacter() != null && currentFrame != null
                    && currentFrame.getSymbolName(tag.getCharacter()) != null) {
                indent();
                out.println("<!-- instance of " + idRef(tag.getCharacter()) + " -->");
            }
        }

        open(tag);
        if (tag3 != null && tag3.isHasClassName())
            out.print(" className=\"" + tag3.getClassName() + "\"");
        if (tag3 != null && tag3.isHasImage())
            out.print(" hasImage=\"true\" ");
        if (tag != null || (tag2 != null && tag2.isHasCharacter()))
            out.print(" idref=\"" + idRef(tag.getCharacter()) + "\"");
        if (tag2 != null && tag2.isHasName())
            out.print(" name=\"" + tag2.getName() + "\"");
        out.print(" depth=\"" + tag.getDepth() + "\"");
        if (tag2 != null && tag2.isHasClipDepth())
            out.print(" clipDepth=\"" + tag2.getClipDepth() + "\"");
        if (tag3 != null && tag3.isHasCacheAsBitmap())
            out.print(" cacheAsBitmap=\"true\"");
        if (tag2 != null && tag2.isHasRatio())
            out.print(" ratio=\"" + tag2.getRatio() + "\"");
        if (tag2 != null && tag2.isHasColorTransform())
            out.print(" cxform=\"" + tag2.getColorTransform() + "\"");
        else if (tag.getColorTransform() != null)
            out.print(" cxform=\"" + tag.getColorTransform() + "\"");
        if (tag.getMatrix() != null || (tag2 != null && tag2.isHasMatrix()))
            out.print(" matrix=\"" + tag.getMatrix() + "\"");
        if (tag3 != null && tag3.isHasBlendMode())
            out.print(" blendmode=\"" + tag3.getBlendMode() + "\"");
        if (tag3 != null && tag3.isHasFilterList()) {
            // todo - pretty print this once we actually care
            out.print(" filters=\"");
            for (Filter filter : tag3.getSurfaceFilterList()) {
                out.print(filter.getFilterID() + " ");
            }
            out.print("\"");
        }

        // TODO: decode clip actions
        //        if (tag3 != null && tag3.isHasClipActions())
        //        {
        //            end();
        //            Iterator it = tag.clipActions.clipActionRecords.iterator();
        //            
        //            openCDATA();
        //            for (ClipActions )
        //            {
        //                ClipActionRecord record = (ClipActionRecord)it.next();
        //                indent();
        //                out.println("onClipEvent(" + printClipEventFlags(record.eventFlags) +
        //                            (record.hasKeyPress() ? "<" + record.keyCode + ">" : "") +
        //                            ") {");
        //                indent++;
        //                if (showActions)
        //                {
        //                    printActions(record.actionList);
        //                }
        //                else
        //                {
        //                    indent();
        //                    out.println("// " + record.actionList.size() + " action(s) elided");
        //                }
        //                indent--;
        //                indent();
        //                out.println("}");
        //            }
        //            closeCDATA();
        //            close(tag);
        //        }
        //        else
        {
            close();
        }
    }

    public void dumpRemoveObject2(RemoveObject2Tag tag) {
        open(tag);
        out.print(" depth=\"" + tag.getDepth() + "\"");
        close();
    }

    public void dumpDefineShape3(DefineShape3Tag tag) {
        printDefineShape(tag);
    }

    public void dumpDefineShape4(DefineShape4Tag tag) {
        printDefineShape(tag);
    }

    private void printShapeWithStyles(ShapeWithStyle shapes) {
        printFillStyles(shapes.getFillStyles());
        printLineStyles(shapes.getLineStyles());
        printShape(shapes);
    }

    @SuppressWarnings("unused")
    private void printMorphLineStyles(MorphLineStyle[] lineStyles) {
        for (int i = 0; i < lineStyles.length; i++) {
            MorphLineStyle lineStyle = lineStyles[i];
            indent();
            out.print("<linestyle ");
            out.print("startColor=\"" + printColor(lineStyle.getStartColor()) + "\" ");
            out.print("endColor=\"" + printColor(lineStyle.getStartColor()) + "\" ");
            out.print("startWidth=\"" + lineStyle.getStartWidth() + "\" ");
            out.print("endWidth=\"" + lineStyle.getEndWidth() + "\" ");
            out.println("/>");
        }
    }

    private void printLineStyles(LineStyleArray linestyles) {
        for (ILineStyle lineStyle : linestyles) {
            indent();
            out.print("<linestyle ");
            if (lineStyle instanceof LineStyle) {
                LineStyle ls = (LineStyle) lineStyle;
                String color = printColor(ls.getColor());
                out.print("color=\"" + color + "\" ");
                out.print("width=\"" + ls.getWidth() + "\" ");
            }

            if (lineStyle instanceof LineStyle2) {
                LineStyle2 lineStyle2 = (LineStyle2) lineStyle;

                if (lineStyle2.getJoinStyle() == LineStyle2.JS_MITER_JOIN) {
                    out.print("miterLimit=\"" + lineStyle2.getMiterLimitFactor() + "\" ");
                }

                if (lineStyle2.isHasFillFlag()) {
                    out.println(">");
                    indent();
                    FillStyleArray fillStyles = new FillStyleArray(1);
                    fillStyles.add(lineStyle2.getFillType());
                    printFillStyles(fillStyles);
                    indent();
                    out.println("</linestyle>");
                } else {
                    out.println("/>");
                }
            } else {
                out.println("/>");
            }
        }
    }

    private void printFillStyles(FillStyleArray fillstyles) {
        for (IFillStyle iFillStyle : fillstyles) {
            indent();
            out.print("<fillstyle");

            int fillStyleType;
            FillStyle fillStyle = (FillStyle) iFillStyle;
            fillStyleType = fillStyle.getFillStyleType();

            out.print(" type=\"" + fillStyleType + "\"");
            if (fillStyleType == FillStyle.SOLID_FILL) {
                out.print(" color=\"" + printColor(fillStyle.getColor()) + "\"");
            }
            if ((fillStyleType & FillStyle.LINEAR_GRADIENT_FILL) != 0) {
                if (fillStyleType == FillStyle.RADIAL_GRADIENT_FILL)
                    out.print(" typeName=\"radial\"");
                else if (fillStyleType == FillStyle.FOCAL_RADIAL_GRADIENT_FILL)
                    out.print(" typeName=\"focal\" focalPoint=\""
                            + ((FocalGradient) fillStyle.getGradient()).getFocalPoint() + "\"");
                // todo print linear or radial or focal
                out.print(" gradient=\"" + formatGradient(fillStyle.getGradient().getGradientRecords()) + "\"");
                out.print(" matrix=\"" + fillStyle.getGradientMatrix() + "\"");
            }
            if ((fillStyleType & FillStyle.REPEATING_BITMAP_FILL) != 0) {
                // todo print tiled or clipped
                out.print(" idref=\"" + idRef(fillStyle.getBitmapCharacter()) + "\"");
                out.print(" matrix=\"" + fillStyle.getBitmapMatrix() + "\"");
            }
            out.println(" />");
        }
    }

    @SuppressWarnings("unused")
    private void printMorphFillStyles(MorphFillStyle[] fillStyles) {
        for (int i = 0; i < fillStyles.length; i++) {
            MorphFillStyle fillStyle = fillStyles[i];
            indent();
            out.print("<fillstyle");
            out.print(" type=\"" + fillStyle.getFillStyleType() + "\"");
            if (fillStyle.getFillStyleType() == FillStyle.SOLID_FILL) {
                out.print(" startColor=\"" + printColor(fillStyle.getStartColor()) + "\"");
                out.print(" endColor=\"" + printColor(fillStyle.getEndColor()) + "\"");
            }

            if ((fillStyle.getFillStyleType() & FillStyle.LINEAR_GRADIENT_FILL) != 0) {
                // todo print linear or radial
                out.print(" gradient=\""
                        + formatMorphGradient(fillStyle.getGradient().toArray(new MorphGradRecord[0])) + "\"");
                out.print(" startMatrix=\"" + fillStyle.getStartGradientMatrix() + "\"");
                out.print(" endMatrix=\"" + fillStyle.getEndGradientMatrix() + "\"");
                if (fillStyle.getFillStyleType() == FillStyle.FOCAL_RADIAL_GRADIENT_FILL) {
                    out.print(" ratio1 =\"" + fillStyle.getRatio1());
                    out.print(" ratio2 =\"" + fillStyle.getRatio2());
                }
            }

            if ((fillStyle.getFillStyleType() & FillStyle.REPEATING_BITMAP_FILL) != 0) {
                // todo print tiled or clipped
                out.print(" idref=\"" + idRef(fillStyle.getBitmap()) + "\"");
                out.print(" startMatrix=\"" + fillStyle.getStartBitmapMatrix() + "\"");
                out.print(" endMatrix=\"" + fillStyle.getEndBitmapMatrix() + "\"");
            }
            out.println(" />");
        }
    }

    private String formatGradient(List<GradRecord> records) {
        StringBuilder b = new StringBuilder();
        int i = 0;
        for (GradRecord record : records) {
            b.append(record.getRatio());
            b.append(' ');
            b.append(printColor(record.getColor()));
            if (i + 1 < records.size())
                b.append(' ');
            i++;
        }
        return b.toString();
    }

    private String formatMorphGradient(MorphGradRecord[] records) {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < records.length; i++) {
            b.append(records[i].getStartRatio());
            b.append(',');
            b.append(records[i].getEndRatio());
            b.append(' ');
            b.append(printColor(records[i].getStartColor()));
            b.append(',');
            b.append(printColor(records[i].getEndColor()));
            if (i + 1 < records.length)
                b.append(' ');
        }
        return b.toString();
    }

    private void printShape(Shape shapes) {
        if (shapes == null)
            return;

        Styles styles = null;

        if (shapes instanceof ShapeWithStyle) {
            styles = new Styles(((ShapeWithStyle) shapes).getFillStyles(),
                    ((ShapeWithStyle) shapes).getLineStyles());
        }

        for (ShapeRecord shape : shapes.getShapeRecords()) {
            indent();
            if (shape instanceof StyleChangeRecord) {
                StyleChangeRecord styleChange = (StyleChangeRecord) shape;
                out.print("<styleChange ");
                if (styleChange.isStateMoveTo()) {
                    out.print("dx=\"" + styleChange.getMoveDeltaX() + "\" dy=\"" + styleChange.getMoveDeltaY()
                            + "\" ");
                }
                if (styleChange.isStateFillStyle0()) {
                    out.print("fillStyle0=\"" + (styles.getFillStyles().indexOf(styleChange.getFillstyle0()) + 1)
                            + "\" ");
                }
                if (styleChange.isStateFillStyle1()) {
                    out.print("fillStyle1=\"" + (styles.getFillStyles().indexOf(styleChange.getFillstyle1()) + 1)
                            + "\" ");
                }
                if (styleChange.isStateLineStyle()) {
                    out.print("lineStyle=\"" + (styles.getFillStyles().indexOf(styleChange.getLinestyle()) + 1)
                            + "\" ");
                }
                if (styleChange.isStateNewStyles()) {
                    out.println(">");
                    indent++;
                    printFillStyles(styleChange.getStyles().getFillStyles());
                    printLineStyles(styleChange.getStyles().getLineStyles());
                    indent--;
                    indent();
                    out.println("</styleChange>");
                    styles = styleChange.getStyles();
                } else {
                    out.println("/>");
                }
            } else {
                EdgeRecord edge = (EdgeRecord) shape;
                if (edge instanceof StraightEdgeRecord) {
                    StraightEdgeRecord straightEdge = (StraightEdgeRecord) edge;
                    out.println("<line dx=\"" + straightEdge.getDeltaX() + "\" dy=\"" + straightEdge.getDeltaY()
                            + "\" />");
                } else {
                    CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord) edge;
                    out.print("<curve ");
                    out.print("cdx=\"" + curvedEdge.getControlDeltaX() + "\" cdy=\"" + curvedEdge.getControlDeltaY()
                            + "\" ");
                    out.print("dx=\"" + curvedEdge.getAnchorDeltaX() + "\" dy=\"" + curvedEdge.getAnchorDeltaY()
                            + "\" ");
                    out.println("/>");
                }
            }
        }
    }

    private void printShapeWithTabs(Shape shapes) {
        if (shapes == null)
            return;

        int startX = 0;
        int startY = 0;

        int x = 0;
        int y = 0;

        for (ShapeRecord shape : shapes.getShapeRecords()) {
            indent();
            if (shape instanceof StyleChangeRecord) {
                StyleChangeRecord styleChange = (StyleChangeRecord) shape;

                // No longer print out number of bits in MoveDeltaX and 
                // MoveDeltaY. The StyleChangeRecord record does not preserve
                // this information.
                out.print("SCR\t");
                if (styleChange.isStateMoveTo()) {
                    out.print(styleChange.getMoveDeltaX() + "\t" + styleChange.getMoveDeltaY());

                    if (startX == 0 && startY == 0) {
                        startX = styleChange.getMoveDeltaX();
                        startY = styleChange.getMoveDeltaY();
                    }

                    x = styleChange.getMoveDeltaX();
                    y = styleChange.getMoveDeltaY();

                    out.print("\t\t");
                }
            } else {
                EdgeRecord edge = (EdgeRecord) shape;
                if (edge instanceof StraightEdgeRecord) {
                    StraightEdgeRecord straightEdge = (StraightEdgeRecord) edge;
                    out.print("SER" + "\t");
                    out.print(straightEdge.getDeltaX() + "\t" + straightEdge.getDeltaY());
                    x += straightEdge.getDeltaX();
                    y += straightEdge.getDeltaY();
                    out.print("\t\t");
                } else {
                    CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord) edge;
                    out.print("CER" + "\t");
                    out.print(curvedEdge.getControlDeltaX() + "\t" + curvedEdge.getControlDeltaY() + "\t");
                    out.print(curvedEdge.getAnchorDeltaX() + "\t" + curvedEdge.getAnchorDeltaY());
                    x += (curvedEdge.getControlDeltaX() + curvedEdge.getAnchorDeltaX());
                    y += (curvedEdge.getControlDeltaY() + curvedEdge.getAnchorDeltaY());
                }
            }

            out.println("\t\t" + x + "\t" + y);
        }
    }

    // TODO: decode ClipActionRecords
    //    private String printClipEventFlags(int flags)
    //    {
    //        StringBuilder b = new StringBuilder();
    //        
    //        if ((flags & ClipActionRecord.unused31) != 0) b.append("res31,");
    //        if ((flags & ClipActionRecord.unused30) != 0) b.append("res30,");
    //        if ((flags & ClipActionRecord.unused29) != 0) b.append("res29,");
    //        if ((flags & ClipActionRecord.unused28) != 0) b.append("res28,");
    //        if ((flags & ClipActionRecord.unused27) != 0) b.append("res27,");
    //        if ((flags & ClipActionRecord.unused26) != 0) b.append("res26,");
    //        if ((flags & ClipActionRecord.unused25) != 0) b.append("res25,");
    //        if ((flags & ClipActionRecord.unused24) != 0) b.append("res24,");
    //        
    //        if ((flags & ClipActionRecord.unused23) != 0) b.append("res23,");
    //        if ((flags & ClipActionRecord.unused22) != 0) b.append("res22,");
    //        if ((flags & ClipActionRecord.unused21) != 0) b.append("res21,");
    //        if ((flags & ClipActionRecord.unused20) != 0) b.append("res20,");
    //        if ((flags & ClipActionRecord.unused19) != 0) b.append("res19,");
    //        if ((flags & ClipActionRecord.construct) != 0) b.append("construct,");
    //        if ((flags & ClipActionRecord.keyPress) != 0) b.append("keyPress,");
    //        if ((flags & ClipActionRecord.dragOut) != 0) b.append("dragOut,");
    //        
    //        if ((flags & ClipActionRecord.dragOver) != 0) b.append("dragOver,");
    //        if ((flags & ClipActionRecord.rollOut) != 0) b.append("rollOut,");
    //        if ((flags & ClipActionRecord.rollOver) != 0) b.append("rollOver,");
    //        if ((flags & ClipActionRecord.releaseOutside) != 0) b.append("releaseOutside,");
    //        if ((flags & ClipActionRecord.release) != 0) b.append("release,");
    //        if ((flags & ClipActionRecord.press) != 0) b.append("press,");
    //        if ((flags & ClipActionRecord.initialize) != 0) b.append("initialize,");
    //        if ((flags & ClipActionRecord.data) != 0) b.append("data,");
    //        
    //        if ((flags & ClipActionRecord.keyUp) != 0) b.append("keyUp,");
    //        if ((flags & ClipActionRecord.keyDown) != 0) b.append("keyDown,");
    //        if ((flags & ClipActionRecord.mouseUp) != 0) b.append("mouseUp,");
    //        if ((flags & ClipActionRecord.mouseDown) != 0) b.append("mouseDown,");
    //        if ((flags & ClipActionRecord.mouseMove) != 0) b.append("moseMove,");
    //        if ((flags & ClipActionRecord.unload) != 0) b.append("unload,");
    //        if ((flags & ClipActionRecord.enterFrame) != 0) b.append("enterFrame,");
    //        if ((flags & ClipActionRecord.load) != 0) b.append("load,");
    //        if (b.length() > 1)
    //        {
    //            b.setLength(b.length() - 1);
    //        }
    //        return b.toString();
    //    }

    public void defineText2(DefineTextTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        end();

        for (TextRecord tr : tag.getTextRecords()) {
            printTextRecord(tr, tag.getTagType().getValue());
        }

        close(tag);
    }

    public void printTextRecord(TextRecord tr, int tagCode) {
        indent();
        out.print("<textRecord ");
        if (tr.isStyleFlagsHasFont()) {
            out.print(" font=\"" + id(tr.getFontTag()) + "\"");
            out.print(" height=\"" + tr.getTextHeight() + "\"");
        }

        if (tr.isStyleFlagsHasXOffset())
            out.print(" xOffset=\"" + tr.getxOffset() + "\"");

        if (tr.isStyleFlagsHasYOffset())
            out.print(" yOffset=\"" + tr.getyOffset() + "\"");

        if (tr.isStyleFlagsHasColor())
            out.print(" color=\"" + printColor(tr.getTextColor()) + "\"");
        out.println(">");

        indent++;
        printGlyphEntries(tr);
        indent--;
        indent();
        out.println("</textRecord>");

    }

    private void printGlyphEntries(TextRecord tr) {
        indent();
        GlyphEntry[] entries = tr.getGlyphEntries();
        for (int i = 0; i < entries.length; i++) {
            GlyphEntry ge = entries[i];
            out.print(ge.getGlyphIndex());
            if (ge.getGlyphAdvance() >= 0)
                out.print('+');
            out.print(ge.getGlyphAdvance());
            out.print(' ');
            if ((i + 1) % 10 == 0) {
                out.println();
                indent();
            }
        }
        if (entries.length % 10 != 0)
            out.println();
    }

    public void dumpDefineButton2(DefineButton2Tag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" trackAsMenu=\"" + tag.isTrackAsMenu() + "\"");
        end();

        for (int i = 0; i < tag.getCharacters().length; i++) {
            ButtonRecord record = tag.getCharacters()[i];
            indent();
            out.println("<buttonRecord " + "idref=\"" + record.getCharacterID() + "\" " + //TODO: print symbol name
                    "depth=\"" + record.getPlaceDepth() + "\" " + "matrix=\"" + record.getPlaceMatrix() + "\" "
                    + "stateHitTest=\"" + record.isStateHitTest() + "\" " + "stateDown=\"" + record.isStateDown()
                    + "\" " + "stateOver=\"" + record.isStateOver() + "\" " + "stateUp=\"" + record.isStateUp()
                    + "\" " + "/>");
            // todo print optional cxforma
        }

        // print conditional actions
        // TODO: print actions
        //        if (tag.condActions.length > 0 && showActions)
        //        {
        //            indent();
        //            out.println("<buttonCondAction>");
        //            openCDATA();
        //            for (int i = 0; i < tag.condActions.length; i++)
        //            {
        //                ButtonCondAction cond = tag.condActions[i];
        //                indent();
        //                out.println("on(" + cond + ") {");
        //                indent++;
        //                printActions(cond.actionList);
        //                indent--;
        //                indent();
        //                out.println("}");
        //            }
        //            closeCDATA();
        //            indent();
        //            out.println("</buttonCondAction>");
        //        }

        close(tag);
    }

    public void dumpDefineBitsJPEG3(DefineBitsJPEG3Tag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");

        // TODO: We don't have a JEB encoder
        //        if (external)
        //        {
        //            String path = externalDirectory
        //                + externalPrefix
        //                + "image"
        //                + id(tag)
        //                + ".jpg";
        //    
        //            out.println(" src=\"" + path + "\" />");
        //
        //            try
        //            {
        //                FileOutputStream image = new FileOutputStream(path, false);
        //                SwfImageUtils.JPEG jpeg = null;
        //
        //                if (tag.jpegTables != null)
        //                {
        //                    jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data);
        //                }
        //                else
        //                {
        //                    jpeg = new SwfImageUtils.JPEG(tag.data, true);
        //                }
        //
        //                jpeg.write(image);
        //                image.close();
        //            }
        //            catch (IOException e)
        //            {
        //                out.println("<!-- error: unable to write external asset file " + path + "-->");
        //            }
        //        }
        //        else
        {
            out.print(" encoding=\"base64\"");
            end();
            outputBase64(tag.getData());
            close(tag);
        }
    }

    public void dumpDefineBitsLossless2(DefineBitsLossless2Tag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");

        if (external) {
            String path = externalDirectory + externalPrefix + "image" + id(tag) + ".bitmap";

            out.println(" src=\"" + path + "\" />");
            try {
                FileOutputStream image = new FileOutputStream(path, false);
                image.write(tag.getData());
                image.close();
            } catch (IOException e) {
                out.println("<!-- error: unable to write external asset file " + path + "-->");
            }
        } else {
            out.print(" encoding=\"base64\"");
            end();
            outputBase64(tag.getData());
            close(tag);
        }
    }

    String escape(String s) {
        if (s == null)
            return null;

        StringBuilder b = new StringBuilder(s.length());
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
            case '<':
                b.append("&lt;");
                break;
            case '>':
                b.append("&gt;");
                break;
            case '&':
                b.append("&amp;");
                break;
            case '"':
                b.append("&quot;");
                break;
            default: {
                // Non-printable low ASCII characters are invalid in XML
                if ((c >= 0 && c <= 8) || (c >= 11 && c <= 12) || (c >= 14 && c <= 31)) {
                    b.append("?");
                } else {
                    b.append(c);
                }
            }
            }
        }

        return b.toString();
    }

    public void dumpDefineEditText(DefineEditTextTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");

        if (tag.isHasFont()) {
            out.print(" fontId=\"" + id(tag.getFontTag()) + "\"");
            out.print(" fontName=\"" + escape(idRef(tag.getFontTag())) + "\"");
            out.print(" fontHeight=\"" + tag.getFontHeight() + "\"");
        } else if (tag.isHasFontClass()) {
            out.print(" fontClass=\"" + tag.getFontClass() + "\"");
            out.print(" fontHeight=\"" + tag.getFontHeight() + "\"");
        }

        out.print(" bounds=\"" + tag.getBounds() + "\"");

        if (tag.isHasTextColor())
            out.print(" color=\"" + printColor(tag.getTextColor()) + "\"");

        out.print(" html=\"" + tag.isHtml() + "\"");
        out.print(" autoSize=\"" + tag.isAutoSize() + "\"");
        out.print(" border=\"" + tag.isBorder() + "\"");

        if (tag.isHasMaxLength())
            out.print(" maxLength=\"" + tag.getMaxLength() + "\"");

        out.print(" multiline=\"" + tag.isMultiline() + "\"");
        out.print(" noSelect=\"" + tag.isNoSelect() + "\"");
        out.print(" password=\"" + tag.isPassword() + "\"");
        out.print(" readOnly=\"" + tag.isReadOnly() + "\"");
        out.print(" useOutlines=\"" + tag.isUseOutlines() + "\"");
        out.print(" varName=\"" + escape(tag.getVariableName()) + "\"");
        out.print(" wordWrap=\"" + tag.isWordWrap() + "\"");

        if (tag.isHasLayout()) {
            out.print(" align=\"" + tag.getAlign() + "\"");
            out.print(" indent=\"" + tag.getIndent() + "\"");
            out.print(" leading=\"" + tag.getLeading() + "\"");
            out.print(" leftMargin=\"" + tag.getLeftMargin() + "\"");
            out.print(" rightMargin=\"" + tag.getRightMargin() + "\"");
        }
        end();
        if (tag.isHasText()) {
            indent();
            out.println("<text>");
            openCDATA();
            out.print(tag.getInitialText());
            closeCDATA();
            indent();
            out.println("</text>");
        }
        close(tag);
    }

    public void dumpDefineSprite(DefineSpriteTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        end();
        indent();
        out.println("<!-- sprite framecount=" + tag.getFrameCount() + " -->");

        // Dump list of nested tags in the sprite.
        for (ITag controlTag : tag.getControlTags())
            dumpTag(controlTag);

        close(tag);
    }

    public void finish() {
        --indent;
        indent();
        out.println("</swf>");
    }

    public void dumpFrameLabel(FrameLabelTag tag) {
        open(tag);
        out.print(" label=\"" + tag.getName() + "\"");
        if (tag.isNamedAnchorTag())
            out.print(" anchor=\"" + "true" + "\"");
        close();
    }

    public void dumpSoundStreamHead2(SoundStreamHeadTag tag) {
        open(tag);
        out.print(" playbackRate=\"" + tag.getPlaybackSoundRate() + "\"");
        out.print(" playbackSize=\"" + tag.getPlaybackSoundSize() + "\"");
        out.print(" playbackType=\"" + tag.getPlaybackSoundType() + "\"");
        out.print(" compression=\"" + tag.getStreamSoundCompression() + "\"");
        out.print(" streamRate=\"" + tag.getStreamSoundRate() + "\"");
        out.print(" streamSize=\"" + tag.getStreamSoundSize() + "\"");
        out.print(" streamType=\"" + tag.getStreamSoundRate() + "\"");
        out.print(" streamSampleCount=\"" + tag.getStreamSoundSampleCount() + "\"");

        if (tag.getStreamSoundCompression() == 2) {
            out.print(" latencySeek=\"" + tag.getLatencySeek() + "\"");
        }
        close();
    }

    public void dumpDefineScalingGrid(DefineScalingGridTag tag) {
        open(tag);
        out.print(" idref=\"" + id(tag.getCharacter()) + "\"");
        out.print(" grid=\"" + tag.getSplitter() + "\"");
        close();
    }

    public void dumpDefineMorphShape(DefineMorphShapeTag tag) {
        dumpDefineMorphShape2(tag);
    }

    public void dumpDefineMorphShape2(DefineMorphShapeTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" startBounds=\"" + tag.getStartBounds() + "\"");
        out.print(" endBounds=\"" + tag.getEndBounds() + "\"");
        if (tag.getTagType() == TagType.DefineMorphShape2) {
            DefineMorphShape2Tag tag2 = (DefineMorphShape2Tag) tag;

            out.print(" startEdgeBounds=\"" + tag2.getStartEdgeBounds() + "\"");
            out.print(" endEdgeBounds=\"" + tag2.getEndEdgeBounds() + "\"");
            out.print(" usesNonScalingStrokes=\"" + tag2.isUsesNonScalingStrokes() + "\"");
            out.print(" usesScalingStrokes=\"" + tag2.isUsesScalingStrokes() + "\"");
        }
        end();

        // TODO: dump line styles and fill styles
        //        printMorphLineStyles(tag.lineStyles);
        //        printMorphFillStyles(tag.fillStyles);

        indent();
        out.println("<start>");
        indent++;
        printShape(tag.getStartEdges());
        indent--;
        indent();
        out.println("</start>");

        indent();
        out.println("<end>");
        indent++;
        printShape(tag.getEndEdges());
        indent--;
        indent();
        out.println("</end>");

        close(tag);
    }

    public void dumpDefineFont2(DefineFont2Tag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" font=\"" + escape(tag.getFontName()) + "\"");
        out.print(" numGlyphs=\"" + tag.getNumGlyphs() + "\"");
        out.print(" italic=\"" + tag.isFontFlagsItalic() + "\"");
        out.print(" bold=\"" + tag.isFontFlagsBold() + "\"");
        out.print(" ansi=\"" + tag.isFontFlagsANSI() + "\"");
        out.print(" wideOffsets=\"" + tag.isFontFlagsWideCodes() + "\"");
        out.print(" wideCodes=\"" + tag.isFontFlagsWideCodes() + "\"");
        out.print(" shiftJIS=\"" + tag.isFontFlagsShiftJIS() + "\"");
        out.print(" langCode=\"" + tag.getLanguageCode() + "\"");
        out.print(" hasLayout=\"" + tag.isFontFlagsHasLayout() + "\"");
        out.print(" ascent=\"" + tag.getFontAscent() + "\"");
        out.print(" descent=\"" + tag.getFontDescent() + "\"");
        out.print(" leading=\"" + tag.getFontLeading() + "\"");
        out.print(" kerningCount=\"" + tag.getKerningCount() + "\"");

        out.print(" codepointCount=\"" + tag.getCodeTable().length + "\"");

        if (tag.isFontFlagsHasLayout()) {
            out.print(" advanceCount=\"" + tag.getFontAdvanceTable().length + "\"");
            out.print(" boundsCount=\"" + tag.getFontBoundsTable().length + "\"");
        }
        end();

        if (glyphs && tag.isFontFlagsHasLayout()) {
            for (int i = 0; i < tag.getFontKerningTable().length; i++) {
                KerningRecord rec = tag.getFontKerningTable()[i];
                indent();
                out.println("<kerningRecord adjustment=\"" + rec.getAdjustment() + "\" code1=\"" + rec.getCode1()
                        + "\" code2=\"" + rec.getCode2() + "\" />");
            }

            for (int i = 0; i < tag.getGlyphShapeTable().length; i++) {
                indent();
                out.print("<glyph");
                out.print(" codepoint=\"" + (tag.getCodeTable()[i])
                        + (isPrintable((char) tag.getCodeTable()[i])
                                ? escape(("(" + (char) tag.getCodeTable()[i] + ")"))
                                : "(?)")
                        + "\"");
                if (tag.isFontFlagsHasLayout()) {
                    out.print(" advance=\"" + tag.getFontAdvanceTable()[i] + "\"");
                    out.print(" bounds=\"" + tag.getFontBoundsTable()[i] + "\"");
                }
                out.println(">");

                Shape shape = tag.getGlyphShapeTable()[i];
                indent++;
                if (tabbedGlyphs)
                    printShapeWithTabs(shape);
                else
                    printShape(shape);
                indent--;
                indent();
                out.println("</glyph>");
            }
        }

        close(tag);
    }

    public void dumpDefineFont3(DefineFont3Tag tag) {
        dumpDefineFont2(tag);
    }

    public void dumpDefineFont4(DefineFont4Tag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        out.print(" font=\"" + escape(tag.getFontName()) + "\"");
        out.print(" hasFontData=\"" + tag.isFontFlagsHasFontData() + "\"");
        out.print(" italic=\"" + tag.isFontFlagsItalic() + "\"");
        out.print(" bold=\"" + tag.isFontFlagsBold() + "\"");
        end();

        if (glyphs && tag.isFontFlagsHasFontData()) {
            outputBase64(tag.getFontData());
        }

        close(tag);
    }

    public void dumpDefineFontAlignZones(DefineFontAlignZonesTag tag) {
        open(tag);
        out.print(" fontID=\"" + id(tag.getFontTag()) + "\"");
        out.print(" CSMTableHint=\"" + tag.getCsmTableHint() + "\"");
        out.println(">");
        indent++;
        indent();
        out.println("<ZoneTable length=\"" + tag.getZoneTable().length + "\">");
        indent++;
        if (glyphs) {
            for (int i = 0; i < tag.getZoneTable().length; i++) {
                ZoneRecord record = tag.getZoneTable()[i];
                indent();
                out.print("<ZoneRecord num=\"" + record.getNumZoneData() + "\" maskX=\"" + record.isZoneMaskX()
                        + "\" maskY=\"" + record.isZoneMaskY() + "\">");
                out.print(record.getZoneData0().getData() + " " + record.getZoneData1().getData() + " ");
                out.println("</ZoneRecord>");
            }
        }
        indent--;
        indent();
        out.println("</ZoneTable>");
        close(tag);
    }

    public void dumpCSMTextSettings(CSMTextSettingsTag tag) {
        open(tag);
        String textID = tag.getTextTag() == null ? "0" : id(tag.getTextTag());
        out.print(" textID=\"" + textID + "\"");
        out.print(" useFlashType=\"" + tag.getUseFlashType() + "\"");
        out.print(" gridFitType=\"" + tag.getGridFit() + "\"");
        out.print(" thickness=\"" + tag.getThickness() + "\"");
        out.print(" sharpness=\"" + tag.getSharpness() + "\"");
        close();
    }

    public void dumpDefineFontName(DefineFontNameTag tag) {
        open(tag);
        out.print(" fontID=\"" + id(tag.getFontTag()) + "\"");
        if (tag.getFontName() != null) {
            out.print(" name=\"" + tag.getFontName() + "\"");
        }
        if (tag.getFontCopyright() != null) {
            out.print(" copyright=\"" + escape(tag.getFontCopyright()) + "\"");
        }

        close();
    }

    private boolean isPrintable(char c) {
        int i = c & 0xFFFF;
        if (i < ' ' || i == '<' || i == '&' || i == '\'')
            return false;
        else
            return true;
    }

    public void dumpExportAssets(ExportAssetsTag tag) {
        open(tag);
        end();

        for (String name : tag.getCharacterNames()) {
            indent();
            out.println("<Export idref=\"" + tag.getCharacterTagByName(name).getCharacterID() + "\" name=\"" + name
                    + "\" />");
        }

        close(tag);
    }

    public void dumpSymbolClass(SymbolClassTag tag) {
        open(tag);
        end();

        for (String symbolName : tag.getSymbolNames()) {
            indent();
            out.println("<Symbol idref=\"" + tag.getSymbol(symbolName).getCharacterID() + "\" className=\""
                    + symbolName + "\" />");
        }

        if (currentFrameIndex == 0 && swf.getTopLevelClass() != null) {
            indent();
            out.println("<Symbol idref=\"0\" className=\"" + swf.getTopLevelClass() + "\" />");
        }

        close(tag);
    }

    // TODO: handle ImportAssets tag.
    //    public void dumpImportAssets(ImportAssets tag)
    //    {
    //        open(tag);
    //        out.print(" url=\"" + tag.url + "\"");
    //        end();
    //        
    //        Iterator it = tag.importRecords.iterator();
    //        while (it.hasNext())
    //        {
    //            ImportRecord record = (ImportRecord)it.next();
    //            indent();
    //            out.println("<Import name=\"" + record.name + "\" id=\"" + dictionary.getId(record) + "\" />");
    //        }
    //        
    //        close(tag);
    //    }
    //    
    //    public void dumpImportAssets2(ImportAssets tag)
    //    {
    //        // TODO: add support for tag.downloadNow and SHA1...
    //        importAssets(tag);
    //    }

    public void dumpEnableDebugger(EnableDebugger2Tag tag) {
        open(tag);
        out.print(" password=\"" + tag.getPassword() + "\"");
        close();
    }

    // TODO Decode actions

    //    public void dumpDoInitAction(DoInitAction tag)
    //    {
    //        if (tag.sprite != null && tag.sprite.name != null)
    //        {
    //            indent();
    //            out.println("<!-- init " + tag.sprite.name + " " + dictionary.getId(tag.sprite) + " -->");
    //        }
    //        
    //        open(tag);
    //        if (tag.sprite != null)
    //            out.print(" idref=\"" + idRef(tag.sprite) + "\"");
    //        end();
    //        
    //        if (showActions)
    //        {
    //            openCDATA();
    //            printActions(tag.actionList);
    //            closeCDATA();
    //        }
    //        else
    //        {
    //            indent();
    //            out.println("<!-- " + tag.actionList.size() + " action(s) elided -->");
    //        }
    //        close(tag);
    //    }

    private String idRef(ICharacterTag tag) {
        if (tag == null) {
            // if tag is null then it isn't in the dict -- the SWF is invalid.
            // lets be lax and print something; Matador generates invalid SWF sometimes.
            return "-1";
        } else if (currentFrame == null || currentFrame.getSymbolName(tag) == null) {
            // just print the character id since no name was exported
            return String.valueOf(id(tag));
        } else {
            return currentFrame.getSymbolName(tag);
        }
    }

    public void dumpDefineVideoStream(DefineVideoStreamTag tag) {
        open(tag);
        out.print(" id=\"" + id(tag) + "\"");
        close();
    }

    public void dumpVideoFrame(VideoFrameTag tag) {
        open(tag);
        out.print(" streamId=\"" + idRef(tag.getStreamTag()) + "\"");
        out.print(" frame=\"" + tag.getFrameNum() + "\"");
        close();
    }

    public void dumpDefineFontInfo2(DefineFontInfoTag tag) {
        dumpDefineFontInfo(tag);
    }

    public void dumpEnableDebugger2(EnableDebugger2Tag tag) {
        open(tag);
        out.print(" password=\"" + tag.getPassword() + "\"");
        out.print(" reserved=\"0x" + Integer.toHexString(EnableDebugger2Tag.RESERVED_FIELD_VALUE) + "\"");
        close();
    }

    // TODO: handle DebugID tag
    //    public void dumpDebugID(DebugID tag)
    //    {
    //        open(tag);
    //        out.print(" uuid=\"" + tag.uuid + "\"");
    //        close();
    //    }

    public void dumpScriptLimits(ScriptLimitsTag tag) {
        open(tag);
        out.print(" scriptRecursionLimit=\"" + tag.getMaxRecursionDepth() + "\"" + " scriptTimeLimit=\""
                + tag.getScriptTimeoutSeconds() + "\"");
        close();
    }

    public void dumpSetTabIndex(SetTabIndexTag tag) {
        open(tag);
        out.print(" depth=\"" + tag.getDepth() + "\"");
        out.print(" index=\"" + tag.getTabIndex() + "\"");
        close();
    }

    public void dumpDoABC(DoABCTag tag) {
        if (abc) {
            open(tag);
            end();
            ABCParser parser = new ABCParser(tag.getABCData());
            PoolingABCVisitor printer = new ABCDumpVisitor(out);
            parser.parseABC(printer);
            close(tag);
        } else {
            open(tag);
            if (tag.getTagType() == TagType.DoABC)
                out.print(" name=\"" + tag.getName() + "\"");
            close();
        }
    }

    @SuppressWarnings("unused")
    private String hexify(byte[] id) {
        StringBuilder b = new StringBuilder(id.length * 2);
        for (int i = 0; i < id.length; i++) {
            b.append(Character.forDigit((id[i] >> 4) & 15, 16));
            b.append(Character.forDigit(id[i] & 15, 16));
        }
        return b.toString().toUpperCase();
    }

    public static String baseName(String path) {
        int start = path.lastIndexOf(File.separatorChar);

        if (File.separatorChar != '/') {
            // some of us are grouchy about unix paths not being
            // parsed since they are totally legit at the system
            // level of win32.
            int altstart = path.lastIndexOf('/');
            if ((start == -1) || (altstart > start))
                start = altstart;
        }

        if (start == -1)
            start = 0;
        else
            ++start;

        int end = path.lastIndexOf('.');

        if (end == -1)
            end = path.length();

        if (start > end)
            end = path.length();

        return path.substring(start, end);

    }

    public static String dirName(String path) {
        int end = path.lastIndexOf(File.pathSeparatorChar);

        if (File.pathSeparatorChar != '/') {
            // some of us are grouchy about unix paths not being
            // parsed since they are totally legit at the system
            // level of win32.
            int altend = path.lastIndexOf('/');
            if ((end == -1) || (altend < end))
                end = altend;
        }

        if (end == -1)
            return "";
        else
            ++end;

        return path.substring(0, end);
    }

    // options
    static boolean abcOption = false;
    static boolean encodeOption = false;
    static boolean showActionsOption = true;
    static boolean showOffsetOption = false;
    static boolean showByteCodeOption = false;
    static boolean showDebugSourceOption = false;
    static boolean glyphsOption = true;
    static boolean externalOption = false;
    static boolean decompileOption = true;
    static boolean defuncOption = true;
    static boolean saveOption = false;
    static boolean tabbedGlyphsOption = true;

    /**
     * SWFDump will dump a SWF file as XML.
     * 
     * Usage: swfdump [-abc] file1.swf
     */
    public static void main(String[] args) throws IOException {
        // This message should not be localized.
        System.err.println("Apache SWF Dump Utility");
        System.err.println(VersionInfo.buildMessage());
        System.err.println("");

        if (args.length == 0) {
            // TODO: decide which options to implement.
            //            System.err.println("Usage: swfdump [-encode] [-asm] [-abc] [-showbytecode] [-showdebugsource] [-showoffset] [-noglyphs] [-save file.swf] [-nofunctions] [-out file.swfx] file1.swf ...");
            System.err.println("Usage: swfdump [-abc] file1.swf");
            System.exit(1);
        }

        int index = 0;
        PrintWriter out = null;
        String outfile = null;

        while ((index < args.length) && (args[index].startsWith("-"))) {
            if (args[index].equals("-encode")) {
                encodeOption = true;
                ++index;
            } else if (args[index].equals("-save")) {
                ++index;
                saveOption = true;
                outfile = args[index++];
            } else if (args[index].equals("-decompile")) {
                decompileOption = true;
                ++index;
            } else if (args[index].equals("-nofunctions")) {
                defuncOption = false;
                ++index;
            } else if (args[index].equals("-asm")) {
                decompileOption = false;
                ++index;
            } else if (args[index].equals("-abc")) {
                abcOption = true;
                ++index;
            } else if (args[index].equals("-noactions")) {
                showActionsOption = false;
                ++index;
            } else if (args[index].equals("-showoffset")) {
                showOffsetOption = true;
                ++index;
            } else if (args[index].equals("-showbytecode")) {
                showByteCodeOption = true;
                ++index;
            } else if (args[index].equals("-showdebugsource")) {
                showDebugSourceOption = true;
                ++index;
            } else if (args[index].equals("-noglyphs")) {
                glyphsOption = false;
                ++index;
            } else if (args[index].equals("-out")) {
                if (index + 1 == args.length) {
                    System.err.println("-out requires a filename or - for stdout");
                    System.exit(1);
                }
                if (!args[index + 1].equals("-")) {

                    outfile = args[index + 1];
                    out = new PrintWriter(outfile, "UTF-8");
                }
                index += 2;
            } else if (args[index].equals("-external")) {
                externalOption = true;
                ++index;
            } else if (args[index].equalsIgnoreCase("-tabbedGlyphs")) {
                tabbedGlyphsOption = true;
                ++index;
            } else {
                System.err.println("unknown argument " + args[index]);
                ++index;
            }
        }

        if (out == null)
            out = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), true);

        File f = new File(args[index]);
        URL[] urls = new URL[0];
        File currentFile = f;
        try {
            if (!f.exists()) {
                urls = new URL[] { new URL(args[index]) };
            } else {
                if (f.isDirectory()) {
                    File[] list = listFiles(f);
                    urls = new URL[list.length];
                    for (int i = 0; i < list.length; i++) {
                        currentFile = list[i];
                        urls[i] = toURL(list[i]);
                    }
                } else {
                    urls = new URL[] { toURL(f) };
                }
            }
        } catch (MalformedURLException e) {
            System.err.println("Unable to open " + currentFile);
        }

        for (int i = 0; i < urls.length; i++) {
            try {
                URL url = urls[i];
                if (saveOption) {
                    InputStream in = new BufferedInputStream(url.openStream());
                    try {
                        OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(outfile));
                        try {
                            int c;
                            while ((c = in.read()) != -1) {
                                fileOut.write(c);
                            }
                        } finally {
                            fileOut.close();
                        }
                    } finally {
                        in.close();
                    }
                }

                dumpSwf(out, url, outfile);

                out.flush();
            } catch (Error e) {
                if (Trace.error)
                    e.printStackTrace();

                System.err.println("");
                System.err.println("An unrecoverable error occurred.  The given file " + urls[i] + " may not be");
                System.err.println("a valid swf.");
            } catch (FileNotFoundException e) {
                System.err.println("Error: " + e.getMessage());
                System.exit(1);
            }
        }
    }

    private static void dumpSwf(PrintWriter out, URL url, String outfile) throws IOException {
        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        out.println("<!-- Parsing swf " + url + " -->");
        SWFDump swfDump = new SWFDump(out);

        // TODO: Disable options
        //        swfDump.showActions = showActionsOption;
        //        swfDump.showOffset = showOffsetOption;
        //        swfDump.showByteCode = showByteCodeOption;
        //        swfDump.showDebugSource = showDebugSourceOption;
        //        swfDump.glyphs = glyphsOption;
        //        swfDump.setExternal(externalOption, outfile);
        //        swfDump.decompile = decompileOption;
        swfDump.abc = abcOption;
        //        swfDump.defunc = defuncOption;
        //        swfDump.tabbedGlyphs = tabbedGlyphsOption;

        swfDump.dump(url);
    }

    public static URL toURL(File f) throws MalformedURLException {
        String s = f.getAbsolutePath();
        if (File.separatorChar != '/') {
            s = s.replace(File.separatorChar, '/');
        }
        if (!s.startsWith("/")) {
            s = "/" + s;
        }
        if (!s.endsWith("/") && f.isDirectory()) {
            s = s + "/";
        }
        return new URL("file", "", s);
    }

    public static File[] listFiles(File dir) {
        String[] fileNames = dir.list();

        if (fileNames == null) {
            return null;
        }

        File[] fileList = new File[fileNames.length];
        for (int i = 0; i < fileNames.length; i++) {
            fileList[i] = new File(dir.getPath(), fileNames[i]);
        }
        return fileList;
    }

}