com.jswiff.xml.TagXMLWriter.java Source code

Java tutorial

Introduction

Here is the source code for com.jswiff.xml.TagXMLWriter.java

Source

/*
 * JSwiff is an open source Java API for Macromedia Flash file generation
 * and manipulation
 *
 * Copyright (C) 2004-2006 Ralf Terdic (contact@jswiff.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.jswiff.xml;

import com.jswiff.swfrecords.BlendMode;
import com.jswiff.swfrecords.ButtonCondAction;
import com.jswiff.swfrecords.ButtonRecord;
import com.jswiff.swfrecords.CXform;
import com.jswiff.swfrecords.KerningRecord;
import com.jswiff.swfrecords.LangCode;
import com.jswiff.swfrecords.MorphFillStyles;
import com.jswiff.swfrecords.MorphLineStyles;
import com.jswiff.swfrecords.Shape;
import com.jswiff.swfrecords.TextRecord;
import com.jswiff.swfrecords.tags.DefineBits;
import com.jswiff.swfrecords.tags.DefineBitsJPEG2;
import com.jswiff.swfrecords.tags.DefineBitsJPEG3;
import com.jswiff.swfrecords.tags.DefineBitsLossless;
import com.jswiff.swfrecords.tags.DefineBitsLossless2;
import com.jswiff.swfrecords.tags.DefineButton;
import com.jswiff.swfrecords.tags.DefineButton2;
import com.jswiff.swfrecords.tags.DefineButtonCXform;
import com.jswiff.swfrecords.tags.DefineButtonSound;
import com.jswiff.swfrecords.tags.DefineEditText;
import com.jswiff.swfrecords.tags.DefineFont;
import com.jswiff.swfrecords.tags.DefineFont2;
import com.jswiff.swfrecords.tags.DefineFont3;
import com.jswiff.swfrecords.tags.DefineFontAlignment;
import com.jswiff.swfrecords.tags.DefineFontInfo;
import com.jswiff.swfrecords.tags.DefineFontInfo2;
import com.jswiff.swfrecords.tags.DefineMorphShape;
import com.jswiff.swfrecords.tags.DefineMorphShape2;
import com.jswiff.swfrecords.tags.DefineShape;
import com.jswiff.swfrecords.tags.DefineShape2;
import com.jswiff.swfrecords.tags.DefineShape3;
import com.jswiff.swfrecords.tags.DefineShape4;
import com.jswiff.swfrecords.tags.DefineSound;
import com.jswiff.swfrecords.tags.DefineSprite;
import com.jswiff.swfrecords.tags.DefineText;
import com.jswiff.swfrecords.tags.DefineText2;
import com.jswiff.swfrecords.tags.DefineVideoStream;
import com.jswiff.swfrecords.tags.DoAction;
import com.jswiff.swfrecords.tags.DoInitAction;
import com.jswiff.swfrecords.tags.EnableDebugger;
import com.jswiff.swfrecords.tags.EnableDebugger2;
import com.jswiff.swfrecords.tags.ExportAssets;
import com.jswiff.swfrecords.tags.FlashTypeSettings;
import com.jswiff.swfrecords.tags.FrameLabel;
import com.jswiff.swfrecords.tags.FreeCharacter;
import com.jswiff.swfrecords.tags.ImportAssets;
import com.jswiff.swfrecords.tags.ImportAssets2;
import com.jswiff.swfrecords.tags.JPEGTables;
import com.jswiff.swfrecords.tags.MalformedTag;
import com.jswiff.swfrecords.tags.PlaceObject;
import com.jswiff.swfrecords.tags.PlaceObject2;
import com.jswiff.swfrecords.tags.PlaceObject3;
import com.jswiff.swfrecords.tags.Protect;
import com.jswiff.swfrecords.tags.RemoveObject;
import com.jswiff.swfrecords.tags.RemoveObject2;
import com.jswiff.swfrecords.tags.Scale9Grid;
import com.jswiff.swfrecords.tags.ScriptLimits;
import com.jswiff.swfrecords.tags.SetTabIndex;
import com.jswiff.swfrecords.tags.ShowFrame;
import com.jswiff.swfrecords.tags.SoundStreamBlock;
import com.jswiff.swfrecords.tags.SoundStreamHead;
import com.jswiff.swfrecords.tags.SoundStreamHead2;
import com.jswiff.swfrecords.tags.StartSound;
import com.jswiff.swfrecords.tags.Tag;
import com.jswiff.swfrecords.tags.TagConstants;
import com.jswiff.swfrecords.tags.UnknownTag;
import com.jswiff.swfrecords.tags.VideoFrame;
import com.jswiff.util.Base64;
import com.jswiff.util.StringUtilities;

import org.dom4j.Element;

import java.util.Iterator;

/*
 * Writes SWF tags to XML.
 */
class TagXMLWriter {
    static void writeTag(Element parentElement, Tag tag) {
        int tagCode = tag.getCode();
        switch (tagCode) {
        case TagConstants.DEFINE_BITS:
            writeDefineBits(parentElement, (DefineBits) tag);
            break;
        case TagConstants.DEFINE_BITS_JPEG_2:
            writeDefineBitsJPEG2(parentElement, (DefineBitsJPEG2) tag);
            break;
        case TagConstants.DEFINE_BITS_JPEG_3:
            writeDefineBitsJPEG3(parentElement, (DefineBitsJPEG3) tag);
            break;
        case TagConstants.DEFINE_BITS_LOSSLESS:
            writeDefineBitsLossless(parentElement, (DefineBitsLossless) tag);
            break;
        case TagConstants.DEFINE_BITS_LOSSLESS_2:
            writeDefineBitsLossless2(parentElement, (DefineBitsLossless2) tag);
            break;
        case TagConstants.DEFINE_BUTTON:
            writeDefineButton(parentElement, (DefineButton) tag);
            break;
        case TagConstants.DEFINE_BUTTON_2:
            writeDefineButton2(parentElement, (DefineButton2) tag);
            break;
        case TagConstants.DEFINE_BUTTON_C_XFORM:
            writeDefineButtonCXform(parentElement, (DefineButtonCXform) tag);
            break;
        case TagConstants.DEFINE_BUTTON_SOUND:
            writeDefineButtonSound(parentElement, (DefineButtonSound) tag);
            break;
        case TagConstants.DEFINE_EDIT_TEXT:
            writeDefineEditText(parentElement, (DefineEditText) tag);
            break;
        case TagConstants.DEFINE_FONT:
            writeDefineFont(parentElement, (DefineFont) tag);
            break;
        case TagConstants.DEFINE_FONT_2:
            writeDefineFont2(parentElement, (DefineFont2) tag);
            break;
        case TagConstants.DEFINE_FONT_3:
            writeDefineFont3(parentElement, (DefineFont3) tag);
            break;
        case TagConstants.DEFINE_FONT_INFO:
            writeDefineFontInfo(parentElement, (DefineFontInfo) tag);
            break;
        case TagConstants.DEFINE_FONT_INFO_2:
            writeDefineFontInfo2(parentElement, (DefineFontInfo2) tag);
            break;
        case TagConstants.DEFINE_FONT_ALIGNMENT:
            writeDefineFontAlignment(parentElement, (DefineFontAlignment) tag);
            break;
        case TagConstants.FLASHTYPE_SETTINGS:
            writeFlashTypeSettings(parentElement, (FlashTypeSettings) tag);
            break;
        case TagConstants.DEFINE_MORPH_SHAPE:
            writeDefineMorphShape(parentElement, (DefineMorphShape) tag);
            break;
        case TagConstants.DEFINE_MORPH_SHAPE_2:
            writeDefineMorphShape2(parentElement, (DefineMorphShape2) tag);
            break;
        case TagConstants.DEFINE_SHAPE:
            writeDefineShape(parentElement, (DefineShape) tag);
            break;
        case TagConstants.DEFINE_SHAPE_2:
            writeDefineShape2(parentElement, (DefineShape2) tag);
            break;
        case TagConstants.DEFINE_SHAPE_3:
            writeDefineShape3(parentElement, (DefineShape3) tag);
            break;
        case TagConstants.DEFINE_SHAPE_4:
            writeDefineShape4(parentElement, (DefineShape4) tag);
            break;
        case TagConstants.DEFINE_SOUND:
            writeDefineSound(parentElement, (DefineSound) tag);
            break;
        case TagConstants.DEFINE_SPRITE:
            writeDefineSprite(parentElement, (DefineSprite) tag);
            break;
        case TagConstants.DEFINE_TEXT:
            writeDefineText(parentElement, (DefineText) tag);
            break;
        case TagConstants.DEFINE_TEXT_2:
            writeDefineText2(parentElement, (DefineText2) tag);
            break;
        case TagConstants.DEFINE_VIDEO_STREAM:
            writeDefineVideoStream(parentElement, (DefineVideoStream) tag);
            break;
        case TagConstants.DO_ACTION:
            writeDoAction(parentElement, (DoAction) tag);
            break;
        case TagConstants.DO_INIT_ACTION:
            writeDoInitAction(parentElement, (DoInitAction) tag);
            break;
        case TagConstants.ENABLE_DEBUGGER:
            writeEnableDebugger(parentElement, (EnableDebugger) tag);
            break;
        case TagConstants.ENABLE_DEBUGGER_2:
            writeEnableDebugger2(parentElement, (EnableDebugger2) tag);
            break;
        case TagConstants.EXPORT_ASSETS:
            writeExportAssets(parentElement, (ExportAssets) tag);
            break;
        case TagConstants.FRAME_LABEL:
            writeFrameLabel(parentElement, (FrameLabel) tag);
            break;
        case TagConstants.FREE_CHARACTER:
            writeFreeCharacter(parentElement, (FreeCharacter) tag);
            break;
        case TagConstants.IMPORT_ASSETS:
            writeImportAssets(parentElement, (ImportAssets) tag);
            break;
        case TagConstants.IMPORT_ASSETS_2:
            writeImportAssets2(parentElement, (ImportAssets2) tag);
            break;
        case TagConstants.JPEG_TABLES:
            writeJPEGTables(parentElement, (JPEGTables) tag);
            break;
        case TagConstants.MALFORMED:
            writeMalformedTag(parentElement, (MalformedTag) tag);
            break;
        case TagConstants.PLACE_OBJECT:
            writePlaceObject(parentElement, (PlaceObject) tag);
            break;
        case TagConstants.PLACE_OBJECT_2:
            writePlaceObject2(parentElement, (PlaceObject2) tag);
            break;
        case TagConstants.PLACE_OBJECT_3:
            writePlaceObject3(parentElement, (PlaceObject3) tag);
            break;
        case TagConstants.PROTECT:
            writeProtect(parentElement, (Protect) tag);
            break;
        case TagConstants.REMOVE_OBJECT:
            writeRemoveObject(parentElement, (RemoveObject) tag);
            break;
        case TagConstants.REMOVE_OBJECT_2:
            writeRemoveObject2(parentElement, (RemoveObject2) tag);
            break;
        case TagConstants.SCRIPT_LIMITS:
            writeScriptLimits(parentElement, (ScriptLimits) tag);
            break;
        case TagConstants.SET_TAB_INDEX:
            writeSetTabIndex(parentElement, (SetTabIndex) tag);
            break;
        case TagConstants.SHOW_FRAME:
            writeShowFrame(parentElement, (ShowFrame) tag);
            break;
        case TagConstants.SCALE_9_GRID:
            writeScale9Grid(parentElement, (Scale9Grid) tag);
            break;
        case TagConstants.SOUND_STREAM_BLOCK:
            writeSoundStreamBlock(parentElement, (SoundStreamBlock) tag);
            break;
        case TagConstants.SOUND_STREAM_HEAD:
            writeSoundStreamHead(parentElement, (SoundStreamHead) tag);
            break;
        case TagConstants.SOUND_STREAM_HEAD_2:
            writeSoundStreamHead2(parentElement, (SoundStreamHead2) tag);
            break;
        case TagConstants.START_SOUND:
            writeStartSound(parentElement, (StartSound) tag);
            break;
        case TagConstants.VIDEO_FRAME:
            writeVideoFrame(parentElement, (VideoFrame) tag);
            break;
        default:
            writeUnknownTag(parentElement, (UnknownTag) tag);
        }
    }

    private static String getSoundFormatString(byte format) {
        switch (format) {
        case DefineSound.FORMAT_ADPCM:
            return "adpcm";
        case DefineSound.FORMAT_MP3:
            return "mp3";
        case DefineSound.FORMAT_NELLYMOSER:
            return "nellymoser";
        case DefineSound.FORMAT_UNCOMPRESSED:
            return "uncompressed";
        case DefineSound.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN:
            return "uncompressedle";
        default:
            throw new IllegalArgumentException("Illegal sound format: " + format);
        }
    }

    private static String getSoundRateString(byte rate) {
        switch (rate) {
        case DefineSound.RATE_5500_HZ:
            return "5500";
        case DefineSound.RATE_11000_HZ:
            return "11000";
        case DefineSound.RATE_22000_HZ:
            return "22000";
        case DefineSound.RATE_44000_HZ:
            return "44000";
        default:
            throw new IllegalArgumentException("Illegal sound rate: " + rate);
        }
    }

    private static void writeDefineBits(Element parentElement, DefineBits tag) {
        Element element = parentElement.addElement("definebits");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addElement("jpegdata").addText(Base64.encode(tag.getJpegData()));
    }

    private static void writeDefineBitsJPEG2(Element parentElement, DefineBitsJPEG2 tag) {
        Element element = parentElement.addElement("definebitsjpeg2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addElement("jpegdata").addText(Base64.encode(tag.getJpegData()));
    }

    private static void writeDefineBitsJPEG3(Element parentElement, DefineBitsJPEG3 tag) {
        Element element = parentElement.addElement("definebitsjpeg3");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addElement("jpegdata").addText(Base64.encode(tag.getJpegData()));
        element.addElement("alphadata").addText(Base64.encode(tag.getBitmapAlphaData()));
    }

    private static void writeDefineBitsLossless(Element parentElement, DefineBitsLossless tag) {
        Element element = parentElement.addElement("definebitslossless");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        String format;
        switch (tag.getFormat()) {
        case DefineBitsLossless.FORMAT_8_BIT_COLORMAPPED:
            format = "8bit";
            break;
        case DefineBitsLossless.FORMAT_15_BIT_RGB:
            format = "15bit";
            break;
        case DefineBitsLossless.FORMAT_24_BIT_RGB:
            format = "24bit";
            break;
        default:
            throw new IllegalArgumentException("Illegal lossless bitmap format: " + tag.getFormat());
        }
        element.addAttribute("format", format);
        element.addAttribute("width", Integer.toString(tag.getWidth()));
        element.addAttribute("height", Integer.toString(tag.getHeight()));
        RecordXMLWriter.writeZlibBitmapData(element, tag.getZlibBitmapData());
    }

    private static void writeDefineBitsLossless2(Element parentElement, DefineBitsLossless2 tag) {
        Element element = parentElement.addElement("definebitslossless2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        String format;
        switch (tag.getFormat()) {
        case DefineBitsLossless2.FORMAT_8_BIT_COLORMAPPED:
            format = "8bit";
            break;
        case DefineBitsLossless2.FORMAT_32_BIT_RGBA:
            format = "32bit";
            break;
        default:
            throw new IllegalArgumentException("Illegal lossless bitmap format: " + tag.getFormat());
        }
        element.addAttribute("format", format);
        element.addAttribute("width", Integer.toString(tag.getWidth()));
        element.addAttribute("height", Integer.toString(tag.getHeight()));
        RecordXMLWriter.writeZlibBitmapData(element, tag.getZlibBitmapData());
    }

    private static void writeDefineButton(Element parentElement, DefineButton tag) {
        Element element = parentElement.addElement("definebutton");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        Element charsElement = element.addElement("chars");
        ButtonRecord[] characters = tag.getCharacters();
        for (int i = 0; i < characters.length; i++) {
            RecordXMLWriter.writeButtonRecord(charsElement, characters[i]);
        }
        RecordXMLWriter.writeActionBlock(element, tag.getActions());
    }

    private static void writeDefineButton2(Element parentElement, DefineButton2 tag) {
        Element element = parentElement.addElement("definebutton2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        if (tag.isTrackAsMenu()) {
            element.addAttribute("trackasmenu", "true");
        }
        Element charsElement = element.addElement("chars");
        ButtonRecord[] characters = tag.getCharacters();
        for (int i = 0; i < characters.length; i++) {
            RecordXMLWriter.writeButtonRecord(charsElement, characters[i]);
        }
        Element actionsElement = element.addElement("actions");
        ButtonCondAction[] actions = tag.getActions();
        if (actions != null) {
            for (int i = 0; i < actions.length; i++) {
                RecordXMLWriter.writeButtonCondAction(actionsElement, actions[i]);
            }
        }
    }

    private static void writeDefineButtonCXform(Element parentElement, DefineButtonCXform tag) {
        Element element = parentElement.addElement("definebuttoncxform");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeCXForm(element, tag.getColorTransform());
    }

    private static void writeDefineButtonSound(Element parentElement, DefineButtonSound tag) {
        Element element = parentElement.addElement("definebuttonsound");
        element.addAttribute("buttonid", Integer.toString(tag.getButtonId()));
        if (tag.getOverUpToIdleSoundId() != 0) {
            Element overUpToIdle = element.addElement("overuptoidle");
            overUpToIdle.addAttribute("soundid", Integer.toString(tag.getOverUpToIdleSoundId()));
            RecordXMLWriter.writeSoundInfo(overUpToIdle, tag.getOverUpToIdleSoundInfo());
        }
        if (tag.getIdleToOverUpSoundId() != 0) {
            Element idleToOverUp = element.addElement("idletooverup");
            idleToOverUp.addAttribute("soundid", Integer.toString(tag.getIdleToOverUpSoundId()));
            RecordXMLWriter.writeSoundInfo(idleToOverUp, tag.getIdleToOverUpSoundInfo());
        }
        if (tag.getOverUpToOverDownSoundId() != 0) {
            Element overUpToOverDown = element.addElement("overuptooverdown");
            overUpToOverDown.addAttribute("soundid", Integer.toString(tag.getOverUpToOverDownSoundId()));
            RecordXMLWriter.writeSoundInfo(overUpToOverDown, tag.getOverUpToOverDownSoundInfo());
        }
        if (tag.getOverDownToOverUpSoundId() != 0) {
            Element overDownToOverUp = element.addElement("overdowntooverup");
            overDownToOverUp.addAttribute("soundid", Integer.toString(tag.getOverDownToOverUpSoundId()));
            RecordXMLWriter.writeSoundInfo(overDownToOverUp, tag.getOverDownToOverUpSoundInfo());
        }
    }

    private static void writeDefineEditText(Element parentElement, DefineEditText tag) {
        Element element = parentElement.addElement("defineedittext");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        if (tag.isWordWrap()) {
            element.addAttribute("wordwrap", "true");
        }
        if (tag.isMultiline()) {
            element.addAttribute("multiline", "true");
        }
        if (tag.isPassword()) {
            element.addAttribute("password", "true");
        }
        if (tag.isReadOnly()) {
            element.addAttribute("readonly", "true");
        }
        if (tag.isAutoSize()) {
            element.addAttribute("autosize", "true");
        }
        if (tag.isNoSelect()) {
            element.addAttribute("noselect", "true");
        }
        if (tag.isBorder()) {
            element.addAttribute("border", "true");
        }
        if (tag.isHtml()) {
            element.addAttribute("html", "true");
        }
        if (tag.isUseOutlines()) {
            element.addAttribute("useoutlines", "true");
        }
        if (tag.hasMaxLength()) {
            element.addAttribute("maxlength", Integer.toString(tag.getMaxLength()));
        }
        String var = tag.getVariableName();
        if ((var != null) && (var.length() > 0)) {
            element.addAttribute("variable", var);
        }
        RecordXMLWriter.writeRect(element, "bounds", tag.getBounds());
        if (tag.hasText()) {
            element.addElement("initialtext").addText(tag.getInitialText());
        }
        if (tag.hasTextColor()) {
            RecordXMLWriter.writeRGBA(element, "color", tag.getTextColor());
        }
        if (tag.hasFont()) {
            Element font = element.addElement("font");
            font.addAttribute("fontid", Integer.toString(tag.getFontId()));
            font.addAttribute("height", Integer.toString(tag.getFontHeight()));
        }
        if (tag.hasLayout()) {
            Element layout = element.addElement("layout");
            String align;
            switch (tag.getAlign()) {
            case DefineEditText.ALIGN_LEFT:
                align = "left";
                break;
            case DefineEditText.ALIGN_CENTER:
                align = "center";
                break;
            case DefineEditText.ALIGN_JUSTIFY:
                align = "justify";
                break;
            case DefineEditText.ALIGN_RIGHT:
                align = "right";
                break;
            default:
                throw new IllegalArgumentException("Illegal text alignment: " + tag.getAlign());
            }
            layout.addAttribute("align", align);
            layout.addAttribute("leftmargin", Integer.toString(tag.getLeftMargin()));
            layout.addAttribute("rightmargin", Integer.toString(tag.getRightMargin()));
            layout.addAttribute("indent", Integer.toString(tag.getIndent()));
            layout.addAttribute("leading", Integer.toString(tag.getLeading()));
        }
    }

    private static void writeDefineFont(Element parentElement, DefineFont tag) {
        Element element = parentElement.addElement("definefont");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        Element glyphShapeTableElement = element.addElement("glyphshapetable");
        Shape[] glyphShapeTable = tag.getGlyphShapeTable();
        for (int i = 0; i < glyphShapeTable.length; i++) {
            RecordXMLWriter.writeShape(glyphShapeTableElement, glyphShapeTable[i]);
        }
    }

    private static void writeDefineFont2(Element parentElement, DefineFont2 tag) {
        Element element = parentElement.addElement("definefont2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("fontname", tag.getFontName());
        if (tag.isANSI()) {
            element.addAttribute("ansi", "true");
        } else if (tag.isShiftJIS()) {
            element.addAttribute("shiftjis", "true");
        }
        if (tag.isBold()) {
            element.addAttribute("bold", "true");
        }
        if (tag.isItalic()) {
            element.addAttribute("italic", "true");
        }
        if (tag.isSmallText()) {
            element.addAttribute("smalltext", "true");
        }
        writeLanguage(element, tag.getLanguageCode());
        Shape[] table = tag.getGlyphShapeTable();
        if (table != null) {
            Element glyphShapeTableElement = element.addElement("glyphshapetable");
            char[] codeTable = tag.getCodeTable();
            for (int i = 0; i < table.length; i++) {
                Element glyphElement = glyphShapeTableElement.addElement("glyph");
                glyphElement.addAttribute("char", Character.toString(codeTable[i]));
                RecordXMLWriter.writeShape(glyphElement, table[i]);
                if (tag.hasLayout()) {
                    glyphElement.addAttribute("advance", Short.toString(tag.getAdvanceTable()[i]));
                    RecordXMLWriter.writeRect(glyphElement, "bounds", tag.getBoundsTable()[i]);
                }
            }
        }
        if (tag.hasLayout()) {
            Element layout = element.addElement("layout");
            layout.addAttribute("ascent", Integer.toString(tag.getAscent()));
            layout.addAttribute("descent", Integer.toString(tag.getDescent()));
            layout.addAttribute("leading", Integer.toString(tag.getLeading()));
            KerningRecord[] kerningTable = tag.getKerningTable();
            if ((kerningTable != null) && (kerningTable.length > 0)) {
                Element kerningTableElement = layout.addElement("kerningtable");
                for (int i = 0; i < kerningTable.length; i++) {
                    KerningRecord record = kerningTable[i];
                    Element recordElement = kerningTableElement.addElement("kerningrecord");
                    recordElement.addAttribute("left", Character.toString(record.getLeft()));
                    recordElement.addAttribute("right", Character.toString(record.getRight()));
                    recordElement.addAttribute("adjust", Short.toString(record.getAdjustment()));
                }
            }
        }
    }

    private static void writeDefineFont3(Element parentElement, DefineFont3 tag) {
        Element element = parentElement.addElement("definefont3");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("fontname", tag.getFontName());
        if (tag.isBold()) {
            element.addAttribute("bold", "true");
        }
        if (tag.isItalic()) {
            element.addAttribute("italic", "true");
        }
        if (tag.isSmallText()) {
            element.addAttribute("smalltext", "true");
        }
        writeLanguage(element, tag.getLanguageCode());
        Shape[] table = tag.getGlyphShapeTable();
        if (table != null) {
            Element glyphShapeTableElement = element.addElement("glyphshapetable");
            char[] codeTable = tag.getCodeTable();
            for (int i = 0; i < table.length; i++) {
                Element glyphElement = glyphShapeTableElement.addElement("glyph");
                glyphElement.addAttribute("char", Character.toString(codeTable[i]));
                RecordXMLWriter.writeShape(glyphElement, table[i]);
                if (tag.hasLayout()) {
                    glyphElement.addAttribute("advance", Short.toString(tag.getAdvanceTable()[i]));
                    RecordXMLWriter.writeRect(glyphElement, "bounds", tag.getBoundsTable()[i]);
                }
            }
        }
        if (tag.hasLayout()) {
            Element layout = element.addElement("layout");
            layout.addAttribute("ascent", Integer.toString(tag.getAscent()));
            layout.addAttribute("descent", Integer.toString(tag.getDescent()));
            layout.addAttribute("leading", Integer.toString(tag.getLeading()));
            KerningRecord[] kerningTable = tag.getKerningTable();
            if ((kerningTable != null) && (kerningTable.length > 0)) {
                Element kerningTableElement = layout.addElement("kerningtable");
                for (int i = 0; i < kerningTable.length; i++) {
                    KerningRecord record = kerningTable[i];
                    Element recordElement = kerningTableElement.addElement("kerningrecord");
                    recordElement.addAttribute("left", Character.toString(record.getLeft()));
                    recordElement.addAttribute("right", Character.toString(record.getRight()));
                    recordElement.addAttribute("adjust", Short.toString(record.getAdjustment()));
                }
            }
        }
    }

    private static void writeDefineFontAlignment(Element parentElement, DefineFontAlignment tag) {
        Element element = parentElement.addElement("definefontalignment");
        element.addAttribute("fontid", Integer.toString(tag.getFontId()));
        switch (tag.getThickness()) {
        case DefineFontAlignment.THIN:
            element.addAttribute("thickness", "thin");
            break;
        case DefineFontAlignment.MEDIUM:
            element.addAttribute("thickness", "medium");
            break;
        case DefineFontAlignment.THICK:
            element.addAttribute("thickness", "thick");
            break;
        }
        RecordXMLWriter.writeAlignmentZones(element, tag.getAlignmentZones());
    }

    private static void writeDefineFontInfo(Element parentElement, DefineFontInfo tag) {
        Element element = parentElement.addElement("definefontinfo");
        element.addAttribute("fontid", Integer.toString(tag.getFontId()));
        element.addAttribute("fontname", tag.getFontName());
        if (tag.isANSI()) {
            element.addAttribute("ansi", "true");
        } else if (tag.isShiftJIS()) {
            element.addAttribute("shiftjis", "true");
        }
        if (tag.isBold()) {
            element.addAttribute("bold", "true");
        }
        if (tag.isItalic()) {
            element.addAttribute("italic", "true");
        }
        if (tag.isSmallText()) {
            element.addAttribute("smalltext", "true");
        }
        char[] codeTable = tag.getCodeTable();
        for (int i = 0; i < codeTable.length; i++) {
            element.addElement("char").addText(Character.toString(codeTable[i]));
        }
    }

    private static void writeDefineFontInfo2(Element parentElement, DefineFontInfo2 tag) {
        Element element = parentElement.addElement("definefontinfo2");
        element.addAttribute("fontid", Integer.toString(tag.getFontId()));
        element.addAttribute("fontname", tag.getFontName());
        if (tag.isBold()) {
            element.addAttribute("bold", "true");
        }
        if (tag.isItalic()) {
            element.addAttribute("italic", "true");
        }
        if (tag.isSmallText()) {
            element.addAttribute("smalltext", "true");
        }
        writeLanguage(element, tag.getLangCode());
        char[] codeTable = tag.getCodeTable();
        for (int i = 0; i < codeTable.length; i++) {
            element.addElement("char").addText(Character.toString(codeTable[i]));
        }
    }

    private static void writeDefineMorphShape(Element parentElement, DefineMorphShape tag) {
        Element element = parentElement.addElement("definemorphshape");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        Shape startShape = tag.getStartShape();
        Shape endShape = tag.getEndShape();
        MorphLineStyles morphLineStyles = tag.getMorphLineStyles();
        MorphFillStyles morphFillStyles = tag.getMorphFillStyles();
        boolean zeroOffset = ((startShape == null) || (endShape == null) || (morphLineStyles == null)
                || (morphFillStyles == null));
        Element startElement = element.addElement("start");
        RecordXMLWriter.writeRect(startElement, "bounds", tag.getStartBounds());
        Element endElement = element.addElement("end");
        RecordXMLWriter.writeRect(endElement, "bounds", tag.getEndBounds());
        if (!zeroOffset) {
            RecordXMLWriter.writeShape(startElement, startShape);
            RecordXMLWriter.writeShape(endElement, endShape);
            RecordXMLWriter.writeMorphLineStyles(element, morphLineStyles);
            RecordXMLWriter.writeMorphFillStyles(element, morphFillStyles);
        }
    }

    private static void writeDefineMorphShape2(Element parentElement, DefineMorphShape2 tag) {
        Element element = parentElement.addElement("definemorphshape2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        Shape startShape = tag.getStartShape();
        Shape endShape = tag.getEndShape();
        MorphLineStyles morphLineStyles = tag.getMorphLineStyles();
        MorphFillStyles morphFillStyles = tag.getMorphFillStyles();
        boolean zeroOffset = ((startShape == null) || (endShape == null) || (morphLineStyles == null)
                || (morphFillStyles == null));
        Element startElement = element.addElement("start");
        RecordXMLWriter.writeRect(startElement, "shapebounds", tag.getStartShapeBounds());
        RecordXMLWriter.writeRect(startElement, "edgebounds", tag.getStartEdgeBounds());
        Element endElement = element.addElement("end");
        RecordXMLWriter.writeRect(endElement, "shapebounds", tag.getEndShapeBounds());
        RecordXMLWriter.writeRect(endElement, "edgebounds", tag.getEndEdgeBounds());
        if (!zeroOffset) {
            RecordXMLWriter.writeShape(startElement, startShape);
            RecordXMLWriter.writeShape(endElement, endShape);
            RecordXMLWriter.writeMorphLineStyles(element, morphLineStyles);
            RecordXMLWriter.writeMorphFillStyles(element, morphFillStyles);
        }
    }

    private static void writeDefineShape(Element parentElement, DefineShape tag) {
        Element element = parentElement.addElement("defineshape");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "bounds", tag.getShapeBounds());
        RecordXMLWriter.writeShapeWithStyle(element, tag.getShapes());
    }

    private static void writeDefineShape2(Element parentElement, DefineShape2 tag) {
        Element element = parentElement.addElement("defineshape2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "bounds", tag.getShapeBounds());
        RecordXMLWriter.writeShapeWithStyle(element, tag.getShapes());
    }

    private static void writeDefineShape3(Element parentElement, DefineShape3 tag) {
        Element element = parentElement.addElement("defineshape3");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "bounds", tag.getShapeBounds());
        RecordXMLWriter.writeShapeWithStyle(element, tag.getShapes());
    }

    private static void writeDefineShape4(Element parentElement, DefineShape4 tag) {
        Element element = parentElement.addElement("defineshape4");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "shapebounds", tag.getShapeBounds());
        RecordXMLWriter.writeRect(element, "edgebounds", tag.getEdgeBounds());
        RecordXMLWriter.writeShapeWithStyle(element, tag.getShapes());
    }

    private static void writeDefineSound(Element parentElement, DefineSound tag) {
        Element element = parentElement.addElement("definesound");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("format", getSoundFormatString(tag.getFormat()));
        element.addAttribute("rate", getSoundRateString(tag.getRate()));
        if (tag.is16BitSample()) {
            element.addAttribute("sample16bit", "true");
        }
        if (tag.isStereo()) {
            element.addAttribute("stereo", "true");
        }
        element.addAttribute("samplecount", Long.toString(tag.getSampleCount()));
        element.addText(Base64.encode(tag.getSoundData()));
    }

    private static void writeDefineSprite(Element parentElement, DefineSprite tag) {
        Element element = parentElement.addElement("definesprite");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        for (Iterator it = tag.getControlTags().iterator(); it.hasNext();) {
            Tag controlTag = (Tag) it.next();
            writeTag(element, controlTag);
        }
    }

    private static void writeDefineText(Element parentElement, DefineText tag) {
        Element element = parentElement.addElement("definetext");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "bounds", tag.getTextBounds());
        RecordXMLWriter.writeMatrix(element, "matrix", tag.getTextMatrix());
        Element textRecordsElement = element.addElement("textrecords");
        TextRecord[] textRecords = tag.getTextRecords();
        for (int i = 0; i < textRecords.length; i++) {
            RecordXMLWriter.writeTextRecord(textRecordsElement, textRecords[i]);
        }
    }

    private static void writeDefineText2(Element parentElement, DefineText2 tag) {
        Element element = parentElement.addElement("definetext2");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "bounds", tag.getTextBounds());
        RecordXMLWriter.writeMatrix(element, "matrix", tag.getTextMatrix());
        Element textRecordsElement = element.addElement("textrecords");
        TextRecord[] textRecords = tag.getTextRecords();
        for (int i = 0; i < textRecords.length; i++) {
            RecordXMLWriter.writeTextRecord(textRecordsElement, textRecords[i]);
        }
    }

    private static void writeDefineVideoStream(Element parentElement, DefineVideoStream tag) {
        Element element = parentElement.addElement("definevideostream");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("numframes", Integer.toString(tag.getNumFrames()));
        element.addAttribute("width", Integer.toString(tag.getWidth()));
        element.addAttribute("height", Integer.toString(tag.getHeight()));
        switch (tag.getDeblocking()) {
        case DefineVideoStream.DEBLOCKING_OFF:
            element.addAttribute("deblocking", "off");
            break;
        case DefineVideoStream.DEBLOCKING_ON:
            element.addAttribute("deblocking", "on");
            break;
        case DefineVideoStream.DEBLOCKING_PACKET:
            element.addAttribute("deblocking", "packet");
            break;
        default:
            throw new IllegalArgumentException("Illegal video deblocking setting: " + tag.getDeblocking());
        }
        if (tag.isSmoothing()) {
            element.addAttribute("smoothing", "true");
        }
        switch (tag.getCodecId()) {
        case DefineVideoStream.CODEC_SCREEN_VIDEO:
            element.addAttribute("codec", "screenvideo");
            break;
        case DefineVideoStream.CODEC_SORENSON_H263:
            element.addAttribute("codec", "h263");
            break;
        case DefineVideoStream.CODEC_VP6:
            element.addAttribute("codec", "vp6");
            break;
        case DefineVideoStream.CODEC_VP6_ALPHA:
            element.addAttribute("codec", "vp6alpha");
            break;
        case DefineVideoStream.CODEC_SCREEN_VIDEO_V2:
            element.addAttribute("codec", "screenvideov2");
            break;
        case DefineVideoStream.CODEC_UNDEFINED:
            element.addAttribute("codec", "undefined");
            break;
        default:
            throw new IllegalArgumentException("Illegal video codec ID: " + tag.getCodecId());
        }
    }

    private static void writeDoAction(Element parentElement, DoAction tag) {
        Element element = parentElement.addElement("doaction");
        RecordXMLWriter.writeActionBlock(element, tag.getActions());
    }

    private static void writeDoInitAction(Element parentElement, DoInitAction tag) {
        Element element = parentElement.addElement("doinitaction");
        element.addAttribute("spriteid", Integer.toString(tag.getSpriteId()));
        RecordXMLWriter.writeActionBlock(element, tag.getInitActions());
    }

    private static void writeEnableDebugger(Element parentElement, EnableDebugger tag) {
        Element element = parentElement.addElement("enabledebugger");
        String password = tag.getPassword();
        if (password != null) {
            element.addAttribute("password", password);
        }
    }

    private static void writeEnableDebugger2(Element parentElement, EnableDebugger2 tag) {
        Element element = parentElement.addElement("enabledebugger2");
        String password = tag.getPassword();
        if (password != null) {
            element.addAttribute("password", password);
        }
    }

    private static void writeExportAssets(Element parentElement, ExportAssets tag) {
        Element element = parentElement.addElement("exportassets");
        ExportAssets.ExportMapping[] mappings = tag.getExportMappings();
        for (int i = 0; i < mappings.length; i++) {
            ExportAssets.ExportMapping mapping = mappings[i];
            Element mappingElement = element.addElement("exportmapping");
            mappingElement.addAttribute("charid", Integer.toString(mapping.getCharacterId()));
            mappingElement.addAttribute("name", mapping.getName());
        }
    }

    private static void writeFlashTypeSettings(Element parentElement, FlashTypeSettings tag) {
        Element element = parentElement.addElement("flashtypesettings");
        element.addAttribute("textid", Integer.toString(tag.getTextId()));
        element.addAttribute("flashtype", Boolean.toString(tag.isFlashType()));
        switch (tag.getGridFit()) {
        case FlashTypeSettings.GRID_FIT_NONE:
            element.addAttribute("gridfit", "none");
            break;
        case FlashTypeSettings.GRID_FIT_PIXEL:
            element.addAttribute("gridfit", "pixel");
            break;
        case FlashTypeSettings.GRID_FIT_SUBPIXEL:
            element.addAttribute("gridfit", "subpixel");
            break;
        }
        element.addAttribute("thickness", StringUtilities.doubleToString(tag.getThickness()));
        element.addAttribute("sharpness", StringUtilities.doubleToString(tag.getSharpness()));
    }

    private static void writeFrameLabel(Element parentElement, FrameLabel tag) {
        Element element = parentElement.addElement("framelabel");
        element.addAttribute("name", tag.getName());
        if (tag.isNamedAnchor()) {
            element.addAttribute("namedanchor", "true");
        }
    }

    private static void writeFreeCharacter(Element parentElement, FreeCharacter tag) {
        Element element = parentElement.addElement("freecharacter");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
    }

    private static void writeImportAssets(Element parentElement, ImportAssets tag) {
        Element element = parentElement.addElement("importassets");
        element.addAttribute("url", tag.getUrl());
        ImportAssets.ImportMapping[] mappings = tag.getImportMappings();
        for (int i = 0; i < mappings.length; i++) {
            ImportAssets.ImportMapping mapping = mappings[i];
            Element mappingElement = element.addElement("importmapping");
            mappingElement.addAttribute("name", mapping.getName());
            mappingElement.addAttribute("charid", Integer.toString(mapping.getCharacterId()));
        }
    }

    private static void writeImportAssets2(Element parentElement, ImportAssets2 tag) {
        Element element = parentElement.addElement("importassets2");
        element.addAttribute("url", tag.getUrl());
        ImportAssets.ImportMapping[] mappings = tag.getImportMappings();
        for (int i = 0; i < mappings.length; i++) {
            ImportAssets.ImportMapping mapping = mappings[i];
            Element mappingElement = element.addElement("importmapping");
            mappingElement.addAttribute("name", mapping.getName());
            mappingElement.addAttribute("charid", Integer.toString(mapping.getCharacterId()));
        }
    }

    private static void writeJPEGTables(Element parentElement, JPEGTables tag) {
        Element element = parentElement.addElement("jpegtables");
        element.addElement("jpegdata").addText(Base64.encode(tag.getJpegData()));
    }

    private static void writeLanguage(Element parentElement, LangCode langCode) {
        if (langCode != null) {
            switch (langCode.getLanguageCode()) {
            case LangCode.LATIN:
                parentElement.addAttribute("language", "latin");
                break;
            case LangCode.UNDEFINED:
                parentElement.addAttribute("language", "undefined");
                break;
            case LangCode.JAPANESE:
                parentElement.addAttribute("language", "japanese");
                break;
            case LangCode.KOREAN:
                parentElement.addAttribute("language", "korean");
                break;
            case LangCode.SIMPLIFIED_CHINESE:
                parentElement.addAttribute("language", "simpchinese");
                break;
            case LangCode.TRADITIONAL_CHINESE:
                parentElement.addAttribute("language", "tradchinese");
                break;
            default:
                throw new IllegalArgumentException("Illegal language code: " + langCode.getLanguageCode());
            }
        }
    }

    private static void writeMalformedTag(Element parentElement, MalformedTag tag) {
        Element element = parentElement.addElement("malformedtag");
        element.addAttribute("code", Integer.toString(tag.getCode()));
        Exception exception = tag.getException();
        Element exceptionElement = element.addElement("exception");
        exceptionElement.addAttribute("class", exception.getClass().getName());
        exceptionElement.addAttribute("message", exception.toString());
        element.addElement("data").addText(Base64.encode(tag.getData()));
    }

    private static void writePlaceObject(Element parentElement, PlaceObject tag) {
        Element element = parentElement.addElement("placeobject");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
        RecordXMLWriter.writeMatrix(element, tag.getMatrix());
        CXform colorTransform = tag.getColorTransform();
        if (colorTransform != null) {
            RecordXMLWriter.writeCXForm(element, colorTransform);
        }
    }

    private static void writePlaceObject2(Element parentElement, PlaceObject2 tag) {
        Element element = parentElement.addElement("placeobject2");
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
        if (tag.hasCharacter()) {
            element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        }
        if (tag.hasName()) {
            element.addAttribute("name", tag.getName());
        }
        if (tag.isMove()) {
            element.addAttribute("move", "true");
        }
        if (tag.hasMatrix()) {
            RecordXMLWriter.writeMatrix(element, tag.getMatrix());
        }
        if (tag.hasColorTransform()) {
            RecordXMLWriter.writeCXFormWithAlpha(element, tag.getColorTransform());
        }
        if (tag.hasRatio()) {
            element.addAttribute("ratio", Integer.toString(tag.getRatio()));
        }
        if (tag.hasClipDepth()) {
            element.addAttribute("clipdepth", Integer.toString(tag.getClipDepth()));
        }
        if (tag.hasClipActions()) {
            RecordXMLWriter.writeClipActions(element, tag.getClipActions());
        }
    }

    private static void writePlaceObject3(Element parentElement, PlaceObject3 tag) {
        Element element = parentElement.addElement("placeobject3");
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
        if (tag.hasCharacter()) {
            element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        }
        if (tag.hasName()) {
            element.addAttribute("name", tag.getName());
        }
        if (tag.isMove()) {
            element.addAttribute("move", "true");
        }
        if (tag.hasMatrix()) {
            RecordXMLWriter.writeMatrix(element, tag.getMatrix());
        }
        if (tag.hasColorTransform()) {
            RecordXMLWriter.writeCXFormWithAlpha(element, tag.getColorTransform());
        }
        if (tag.hasRatio()) {
            element.addAttribute("ratio", Integer.toString(tag.getRatio()));
        }
        if (tag.hasClipDepth()) {
            element.addAttribute("clipdepth", Integer.toString(tag.getClipDepth()));
        }
        if (tag.hasClipActions()) {
            RecordXMLWriter.writeClipActions(element, tag.getClipActions());
        }
        if (tag.hasBlendMode()) {
            element.addAttribute("blendmode", BlendMode.getDescription(tag.getBlendMode()));
        }
        if (tag.isCacheAsBitmap()) {
            element.addAttribute("cacheasbitmap", "true");
        }
        if (tag.hasFilters()) {
            RecordXMLWriter.writeFilters(element, tag.getFilters());
        }
    }

    private static void writeProtect(Element parentElement, Protect tag) {
        Element element = parentElement.addElement("protect");
        String password = tag.getPassword();
        if (password != null) {
            element.addElement("password", tag.getPassword());
        }
    }

    private static void writeRemoveObject(Element parentElement, RemoveObject tag) {
        Element element = parentElement.addElement("removeobject");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
    }

    private static void writeRemoveObject2(Element parentElement, RemoveObject2 tag) {
        Element element = parentElement.addElement("removeobject2");
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
    }

    private static void writeScale9Grid(Element parentElement, Scale9Grid tag) {
        Element element = parentElement.addElement("scale9grid");
        element.addAttribute("charid", Integer.toString(tag.getCharacterId()));
        RecordXMLWriter.writeRect(element, "grid", tag.getGrid());
    }

    private static void writeScriptLimits(Element parentElement, ScriptLimits tag) {
        Element element = parentElement.addElement("scriptlimits");
        element.addAttribute("maxrecursiondepth", Integer.toString(tag.getMaxRecursionDepth()));
        element.addAttribute("scripttimeout", Integer.toString(tag.getScriptTimeoutSeconds()));
    }

    private static void writeSetTabIndex(Element parentElement, SetTabIndex tag) {
        Element element = parentElement.addElement("settabindex");
        element.addAttribute("depth", Integer.toString(tag.getDepth()));
        element.addAttribute("tabindex", Integer.toString(tag.getTabIndex()));
    }

    private static void writeShowFrame(Element parentElement, ShowFrame tag) {
        parentElement.addElement("showframe");
    }

    private static void writeSoundStreamBlock(Element parentElement, SoundStreamBlock tag) {
        Element element = parentElement.addElement("soundstreamblock");
        element.addElement("streamsounddata").addText(Base64.encode(tag.getStreamSoundData()));
    }

    private static void writeSoundStreamHead(Element parentElement, SoundStreamHead tag) {
        Element element = parentElement.addElement("soundstreamhead");
        element.addAttribute("streamformat", getSoundFormatString(tag.getStreamFormat()));
        element.addAttribute("streamrate", getSoundRateString(tag.getStreamRate()));
        if (tag.isStreamStereo()) {
            element.addAttribute("streamstereo", "true");
        }
        element.addAttribute("streamsamplecount", Integer.toString(tag.getStreamSampleCount()));
        element.addAttribute("playbackrate", getSoundRateString(tag.getPlaybackRate()));
        if (tag.isPlaybackStereo()) {
            element.addAttribute("playbackstereo", "true");
        }
        if (tag.getStreamFormat() == SoundStreamHead.FORMAT_MP3) {
            element.addAttribute("latencyseek", Short.toString(tag.getLatencySeek()));
        }
    }

    private static void writeSoundStreamHead2(Element parentElement, SoundStreamHead2 tag) {
        Element element = parentElement.addElement("soundstreamhead2");
        element.addAttribute("streamformat", getSoundFormatString(tag.getStreamFormat()));
        element.addAttribute("streamrate", getSoundRateString(tag.getStreamRate()));
        if (tag.isStream16BitSample()) {
            element.addAttribute("streamsample16bit", "true");
        }
        if (tag.isStreamStereo()) {
            element.addAttribute("streamstereo", "true");
        }
        element.addAttribute("streamsamplecount", Integer.toString(tag.getStreamSampleCount()));
        element.addAttribute("playbackrate", getSoundRateString(tag.getPlaybackRate()));
        if (tag.isPlayback16BitSample()) {
            element.addAttribute("playbacksample16bit", "true");
        }
        if (tag.isPlaybackStereo()) {
            element.addAttribute("playbackstereo", "true");
        }
        if (tag.getStreamFormat() == SoundStreamHead.FORMAT_MP3) {
            element.addAttribute("latencyseek", Short.toString(tag.getLatencySeek()));
        }
    }

    private static void writeStartSound(Element parentElement, StartSound tag) {
        Element element = parentElement.addElement("startsound");
        element.addAttribute("soundid", Integer.toString(tag.getSoundId()));
        RecordXMLWriter.writeSoundInfo(element, tag.getSoundInfo());
    }

    private static void writeUnknownTag(Element parentElement, UnknownTag tag) {
        Element element = parentElement.addElement("unknowntag");
        element.addAttribute("code", Integer.toString(tag.getCode()));
        element.addText(Base64.encode(tag.getData()));
    }

    private static void writeVideoFrame(Element parentElement, VideoFrame tag) {
        Element element = parentElement.addElement("videoframe");
        element.addAttribute("streamid", Integer.toString(tag.getStreamId()));
        element.addAttribute("framenum", Integer.toString(tag.getFrameNum()));
        element.addElement("videodata").addText(Base64.encode(tag.getVideoData()));
    }
}