You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@harmony.apache.org by ml...@apache.org on 2006/07/31 16:08:55 UTC

svn commit: r427121 [6/29] - in /incubator/harmony/enhanced/classlib/trunk/modules/swing: make/ src/main/java/common/javax/swing/ src/main/java/common/javax/swing/text/ src/main/java/common/javax/swing/text/html/ src/main/java/common/javax/swing/text/h...

Added: incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/HTMLDocument.java
URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/HTMLDocument.java?rev=427121&view=auto
==============================================================================
--- incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/HTMLDocument.java (added)
+++ incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/HTMLDocument.java Mon Jul 31 07:08:47 2006
@@ -0,0 +1,1444 @@
+/*
+ *  Copyright 2005 - 2006 The Apache Software Software Foundation or its licensors, as applicable.
+ *
+ *  Licensed 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.
+ */
+/**
+* @author Alexander T. Simbirtsev
+* @version $Revision$
+*/
+package javax.swing.text.html;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.swing.ButtonGroup;
+import javax.swing.event.DocumentEvent.EventType;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.ElementIterator;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.html.HTML.Tag;
+import javax.swing.text.html.HTMLEditorKit.Parser;
+import javax.swing.text.html.parser.ParserDelegator;
+
+import org.apache.harmony.x.swing.text.html.form.Form;
+import org.apache.harmony.x.swing.text.html.form.FormAttributes;
+import org.apache.harmony.x.swing.text.html.form.FormButtonModel;
+import org.apache.harmony.x.swing.text.html.form.FormElement;
+import org.apache.harmony.x.swing.text.html.form.FormFieldsetModel;
+import org.apache.harmony.x.swing.text.html.form.FormOption;
+import org.apache.harmony.x.swing.text.html.form.FormOptionGroup;
+import org.apache.harmony.x.swing.text.html.form.FormSelectComboBoxModel;
+import org.apache.harmony.x.swing.text.html.form.FormSelectListModel;
+import org.apache.harmony.x.swing.text.html.form.FormSelectModel;
+import org.apache.harmony.x.swing.text.html.form.FormTextModel;
+import org.apache.harmony.x.swing.text.html.form.FormToggleButtonModel;
+
+public class HTMLDocument extends DefaultStyledDocument {
+
+    public class BlockElement extends BranchElement {
+        public BlockElement(final Element parent, final AttributeSet attr) {
+            super(parent, attr);
+        }
+
+        public String getName() {
+            final Object tag = getAttribute(StyleConstants.NameAttribute);
+            return tag != null ? tag.toString() : super.getName();
+        }
+
+        public AttributeSet getResolveParent() {
+            return null;
+        }
+    }
+
+    public class RunElement extends LeafElement {
+        public RunElement(final Element parent, final AttributeSet a,
+                          final int start, final int end) {
+            super(parent, a, start, end);
+        }
+
+        public String getName() {
+            final Object tag = getAttribute(StyleConstants.NameAttribute);
+            return tag != null ? tag.toString() : super.getName();
+        }
+
+        public AttributeSet getResolveParent() {
+            return null;
+        }
+    }
+
+    public class HTMLReader extends HTMLEditorKit.ParserCallback {
+        public class TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+            }
+
+            public void end(final Tag tag) {
+            }
+        }
+
+        public class BlockAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                checkInsertTag(tag);
+                skipAddingBlockSpec = false;
+                blockOpen(tag, attr);
+            }
+
+            public void end(final Tag tag) {
+                blockClose(tag);
+            }
+        }
+
+        public class CharacterAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                checkInsertTag(tag);
+                pushCharacterStyle();
+                charAttr.addAttribute(tag, attr.copyAttributes());
+            }
+
+            public void end(final Tag tag) {
+                popCharacterStyle();
+            }
+        }
+
+        public class FormAction extends SpecialAction {
+            private static final String NO_NAME_ATTRIBUTE = "___no_name___";
+            
+            private Form currentForm;
+            private HashMap radioGroupped = new HashMap();
+            
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                if (Tag.OPTION.equals(tag) || Tag.OPTGROUP.equals(tag)) {
+                    checkInsertTag(tag);
+                    handleOption(tag, attr);
+                    return;
+                } 
+                super.start(tag, attr);
+                
+                final ElementSpec spec = getLastSpec();
+                assert spec != null : "we've just created a spec in super.start()";
+                FormElement model = null;
+                final MutableAttributeSet specAttr = (MutableAttributeSet)spec.getAttributes();
+                if (Tag.INPUT.equals(tag)) {
+                    model = handleInput(attr, specAttr);
+                } else if (Tag.TEXTAREA.equals(tag)) {
+                    model = new FormTextModel(getCurrentForm(), attr);
+                    openedBlocks.add(Tag.TEXTAREA);
+                } else if (Tag.BUTTON.equals(tag)) {
+                    model = new FormButtonModel(getCurrentForm(), attr);
+                    openedBlocks.add(Tag.BUTTON);
+                } else if (Tag.LEGEND.equals(tag)) {
+                    openedBlocks.add(Tag.LEGEND);
+                    if (openedBlocks.contains(Tag.FIELDSET)) {
+                        handleLegend(null, specAttr);
+                    }
+                } else if (Tag.FIELDSET.equals(tag)) {
+                    model = new FormFieldsetModel(getCurrentForm(), attr);
+                    openedBlocks.add(Tag.FIELDSET);
+                } else if (Tag.SELECT.equals(tag)) {
+                    if (FormAttributes.isListSelect(specAttr)) {
+                        selectModel = new FormSelectListModel(getCurrentForm(), attr);
+                    } else {
+                        selectModel = new FormSelectComboBoxModel(getCurrentForm(), attr);
+                    }
+                    model = selectModel;
+                    openedBlocks.add(Tag.SELECT);
+                }
+                if (model != null) {
+                    specAttr.addAttribute(StyleConstants.ModelAttribute, model);
+                    assert currentForm != null : "creating model with getCurrentForm() in constructor assures this";
+                    currentForm.addElement(model);
+                }
+            }
+
+            public void end(final Tag tag) {
+                openedBlocks.remove(tag);
+                if (Tag.SELECT.equals(tag)) {
+                    selectModel = null;
+                } else if (Tag.OPTGROUP.equals(tag)) {
+                    if (selectModel != null) {
+                        selectModel.getRootOptionGroup().popGroup();
+                    }
+                }
+            }
+            
+            void openForm(final AttributeSet attr) {
+                currentForm = new Form(attr);
+            }
+            
+            void closeForm() {
+                currentForm = null;
+                selectModel = null;
+                radioGroupped.clear();
+            }
+            
+            private FormElement handleInput(final AttributeSet attr, final MutableAttributeSet specAttr) {
+                FormElement result = null;
+                String inputType = (String)attr.getAttribute(HTML.Attribute.TYPE);
+                if (inputType == null) {
+                    inputType = FormAttributes.INPUT_TYPE_TEXT;
+                    specAttr.addAttribute(HTML.Attribute.TYPE, inputType);
+                }
+                int inputTypeIndex = FormAttributes.getTypeAttributeIndex((String)inputType);
+                
+                switch (inputTypeIndex) {
+                case FormAttributes.INPUT_TYPE_TEXT_INDEX:
+                case FormAttributes.INPUT_TYPE_PASSWORD_INDEX:
+                    result = new FormTextModel(getCurrentForm(), attr, FormTextModel.ENABLE_MAX_LENGTH_BOUND);
+                    break;
+                case FormAttributes.INPUT_TYPE_FILE_INDEX:
+                    result = new FormTextModel(getCurrentForm(), attr);
+                    break;
+                case FormAttributes.INPUT_TYPE_SUBMIT_INDEX:
+                case FormAttributes.INPUT_TYPE_RESET_INDEX:
+                case FormAttributes.INPUT_TYPE_BUTTON_INDEX:
+                case FormAttributes.INPUT_TYPE_IMAGE_INDEX:
+                    result = new FormButtonModel(getCurrentForm(), attr);
+                    break;
+                case FormAttributes.INPUT_TYPE_RADIO_INDEX:
+                    FormToggleButtonModel buttonModel = new FormToggleButtonModel(getCurrentForm(), attr);
+                    manageRadioGroup(attr, buttonModel);
+                    result = buttonModel;
+                    break;
+                case FormAttributes.INPUT_TYPE_CHECKBOX_INDEX:
+                    result = new FormToggleButtonModel(getCurrentForm(), attr);
+                    break;
+                default:
+                    break;
+                }
+                return result;
+            }
+
+            private void manageRadioGroup(final AttributeSet attr,
+                                          final FormToggleButtonModel buttonModel) {
+                String name = (String)attr.getAttribute(HTML.Attribute.NAME);
+                if (name == null) {
+                    name = NO_NAME_ATTRIBUTE;
+                }
+                Object groupped = radioGroupped.get(name);
+                if (groupped instanceof FormToggleButtonModel) {
+                    FormToggleButtonModel grouppedModel = (FormToggleButtonModel)groupped;
+                    ButtonGroup buttonGroup;
+                    if (grouppedModel.getGroup() != null) {
+                        buttonGroup = grouppedModel.getGroup();
+                    } else {
+                        buttonGroup = new ButtonGroup();
+                        grouppedModel.setGroup(buttonGroup);
+                    }
+                    buttonModel.setGroup(buttonGroup);
+                } else {
+                    radioGroupped.put(name, buttonModel);
+                }
+            }
+
+            private void handleOption(final Tag tag, final AttributeSet attr) {
+                FormOption option = null;
+                FormOptionGroup currentGroup = (selectModel != null) ? selectModel
+                        .getRootOptionGroup().getCurrentGroup() : null;
+                        
+                if (Tag.OPTION.equals(tag)) {
+                    option = new FormOption(currentGroup, attr);
+                    openedBlocks.add(Tag.OPTION);
+                } else if (Tag.OPTGROUP.equals(tag)) {
+                    currentGroup = new FormOptionGroup(currentGroup, attr);
+                    option = currentGroup;
+                }
+                
+                if (option != null && selectModel != null) {
+                    selectModel.addOption(option);
+                    selectModel.getRootOptionGroup().pushGroup(currentGroup);
+                }
+            }
+            
+            private ElementSpec getLastSpec() {
+                return !parseBuffer.isEmpty() ? (ElementSpec)parseBuffer.get(parseBuffer.size() - 1) : null;
+            }
+
+            private Form getCurrentForm() {
+                return (currentForm != null) ? currentForm : (currentForm = new Form(SimpleAttributeSet.EMPTY));
+            }
+        }
+
+        public class HiddenAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addSpecialElement(tag, attr);
+            }
+
+            public void end(final Tag tag) {
+                addSpecialElement(tag, createMutableSet(HTML.Attribute.ENDTAG, Boolean.TRUE));
+            }
+        }
+
+        public class IsindexAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                checkInsertTag(tag);
+                blockOpen(Tag.IMPLIED, new SimpleAttributeSet());
+                addSpecialElement(tag, attr);
+                blockClose(Tag.IMPLIED);
+            }
+
+        }
+
+        public class ParagraphAction extends BlockAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                openedBlocks.add(PARAGRAPH_TAG);
+            }
+        
+            public void end(final Tag tag) {
+                super.end(tag);
+                openedBlocks.remove(PARAGRAPH_TAG);
+            }
+        }
+
+        public class PreAction extends BlockAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                MutableAttributeSet blockAttr = new SimpleAttributeSet(attr); 
+                SimpleAttributeSet defaultAttr = getDefaultCSSAttributes(tag);
+                if (defaultAttr != null) {
+                    blockAttr.addAttributes(defaultAttr);
+                }
+                blockOpen(Tag.IMPLIED, blockAttr);
+                impliedBlockOpen = true;
+                needImpliedNewLine = true;
+            }
+        
+            public void end(final Tag tag) {
+                blockClose(Tag.IMPLIED);
+                super.end(tag);
+            }
+        }
+
+        public class SpecialAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                addSpecialElement(tag, attr);
+            }
+        }
+
+        class AdvancedCharacterAction extends CharacterAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                final AttributeSet attrs = getDefaultCSSAttributes(tag);
+                if (attrs != null) {
+                    charAttr.addAttributes(attrs);
+                }
+            }
+        }
+
+        class LabelAction extends BlockAction {
+            // TODO
+        }
+        
+        class AnchorAction extends CharacterAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                openedBlocks.add(Tag.A);
+                anchorTextEncountered = false;
+            }
+            
+            public void end(final Tag tag) {
+                if (openedBlocks.contains(Tag.A) && !anchorTextEncountered) {
+                    addContent(new char[] {' '}, 0, 1);
+                    anchorTextEncountered = true;
+                }
+                super.end(tag);
+                openedBlocks.remove(Tag.A);
+            }
+        }
+        
+        class FontAction extends CharacterAction {
+            final HTML.Attribute[] specialHTMLAttributes = new HTML.Attribute[] {
+                                                             HTML.Attribute.FACE,
+                                                             HTML.Attribute.COLOR,
+                                                             HTML.Attribute.SIZE};
+
+            final CSS.Attribute[] specialCSSAttributes = new CSS.Attribute[] {
+                                                             CSS.Attribute.FONT_FAMILY,
+                                                             CSS.Attribute.COLOR,
+                                                             CSS.Attribute.FONT_SIZE};
+            
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                
+                final StyleSheet styleSheet = getStyleSheet();
+                for (int i = 0; i < specialHTMLAttributes.length; i++) {
+                    final String value = (String)attr.getAttribute(specialHTMLAttributes[i]);
+                    if (value != null) {
+                        styleSheet.addCSSAttributeFromHTML(charAttr, specialCSSAttributes[i], value);
+                    }
+                }
+            }
+        }
+        
+        class FormTagAction extends BlockAction {
+            private Tag[] formActionTags = new Tag[] {Tag.TEXTAREA, Tag.SELECT,
+                                                 Tag.INPUT, Tag.OPTION,
+                                                 Tag.OPTGROUP, Tag.BUTTON,
+                                                 Tag.LEGEND, Tag.FIELDSET};
+            
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                super.start(tag, attr);
+                FormAction formAction = getFormAction();
+                if (formAction != null) {
+                    formAction.openForm(attr);
+                }
+            }
+
+            public void end(final Tag tag) {
+                super.end(tag);
+                FormAction formAction = getFormAction();
+                if (formAction != null) {
+                    formAction.closeForm();
+                }
+            }
+
+            private FormAction getFormAction() {
+                for (int i = 0; i < formActionTags.length; i++) {
+                    final Tag tag = formActionTags[i];
+                    TagAction action = getAction(tag);
+                    if (action instanceof FormAction) {
+                        return (FormAction)action;
+                    }
+                }
+                return null;
+            }
+        }
+        
+        class BaseAction extends TagAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                checkInsertTag(tag);
+                if (attr == null) {
+                    return;
+                }
+                final String href = (String)attr.getAttribute(HTML.Attribute.HREF);
+                if (href == null) {
+                    return;
+                }
+
+                final URL url = HTML.resolveURL(href, getBase());
+                setBase(url);
+                putProperty(INITIAL_BASE_PROPERTY, url);
+            }
+        }
+
+        class HeadAction extends BlockAction {
+            public void end(final Tag tag) {
+                super.end(tag);
+                if (styleRule != null) {
+                    getStyleSheet().addRule(styleRule);
+                    styleRule = null;
+                }
+            }
+        }
+
+        class TitleAction extends BlockAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                checkInsertTag(tag);
+                addSpecialElement(tag, attr);
+                openedBlocks.add(Tag.TITLE);
+            }
+
+            public void end(final Tag tag) {
+                addSpecialElement(tag, createMutableSet(HTML.Attribute.ENDTAG, Boolean.TRUE));
+                openedBlocks.remove(Tag.TITLE);
+            }
+        }
+
+        class AppletAction extends HiddenAction {
+            // TODO: implement
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                super.start(tag, attr);
+            }
+        }
+
+        class AreaAction extends HiddenAction {
+            // TODO: implement
+        }
+
+        class MapAction extends HiddenAction {
+            // TODO: implement
+        }
+
+        class ScriptAction extends HiddenAction {
+            // TODO: implement
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                super.start(tag, attr);
+            }
+        }
+
+        class LinkAction extends HiddenAction {
+            public void start(final Tag tag, final MutableAttributeSet attr) {
+                addJoinPreviousSpec = true;
+                super.start(tag, attr);
+                if (attr.containsAttribute(HTML.Attribute.TYPE, "text/css")) {
+                    loadCSS(attr);
+                }
+            }
+            
+            public void end(final Tag tag) {
+            }
+            
+            private void loadCSS(final AttributeSet attr) {
+                String href = (String)attr.getAttribute(HTML.Attribute.HREF);
+                final URL url = HTML.resolveURL(href, getBase());
+                try {
+                    getStyleSheet().loadRules(new BufferedReader(new InputStreamReader(url.openStream())), url);
+                } catch (IOException e) {
+                }
+            }
+        }
+
+        class MetaAction extends SpecialAction {
+            public void end(final Tag tag) {
+            }
+        }
+
+        class StyleAction extends TagAction {
+            public void start(Tag tag, MutableAttributeSet attr) {
+                checkInsertTag(tag);
+                openedBlocks.add(Tag.STYLE);
+            }
+            
+            public void end(Tag tag) {
+                openedBlocks.remove(Tag.STYLE);
+            }
+        }
+
+        protected MutableAttributeSet charAttr = new SimpleAttributeSet();
+        protected Vector parseBuffer = new Vector();
+
+        private static final String PARAGRAPH_TAG = "_paragraph_tag_";
+        private static final int IMPLIED_HTML_DOCUMENT_START_SPECS_NUMBER = 8;
+        private static final int TOKEN_THRESHOLD_MULTIPLIER = 5;
+
+        private final HashMap tagActionMap = new HashMap();
+        private final TagAction emptyAction = new TagAction(); 
+        private final Stack attrStack = new Stack();
+        private final Set openedBlocks = new HashSet();
+        private boolean impliedBlockOpen;
+        private int numBlocksOpen;
+        private boolean anchorTextEncountered;
+
+        private boolean needImpliedNewLine;
+        private String styleRule;
+        private FormSelectModel selectModel;
+        private int tokenThreshold; 
+        
+        private int offset;
+        private int popDepth;
+        private int pushDepth;
+        private Tag insertTag;
+        private boolean insertTagFound;
+        private boolean implicitSpecsRemove;
+        private boolean skipAddingBlockSpec;
+        private boolean addJoinPreviousSpec;
+        private int specsCount;
+
+        public HTMLReader(final int offset) {
+            this(offset, 0, 0, null);
+        }
+
+        public HTMLReader(final int offset, final int popDepth,
+                          final int pushDepth, final Tag insertTag) {
+            this.offset = offset;
+            this.popDepth = popDepth;
+            this.pushDepth = pushDepth;
+            this.insertTag = insertTag;
+            insertTagFound = (insertTag == null);
+            tokenThreshold = getTokenThreshold();
+            fillTagActionMap();
+        }
+
+        public void handleComment(final char[] data, final int pos) {
+            final String comment = new String(data);
+            if (openedBlocks.contains(Tag.P)) {
+                addSpecialElement(Tag.COMMENT, createMutableSet(HTML.Attribute.COMMENT, comment));
+            } else {
+                Vector comments = (Vector)getProperty(AdditionalComments);
+                if (comments == null) {
+                    comments = new Vector();
+                    putProperty(AdditionalComments, comments);
+                }
+                comments.add(comment);
+            }
+        }
+
+        public void handleEndOfLineString(final String eol) {
+            putProperty(DefaultEditorKit.EndOfLineStringProperty, eol);
+        }
+
+        public void handleSimpleTag(final Tag tag,
+                                    final MutableAttributeSet attr,
+                                    final int pos) {
+            final TagAction action = getAction(tag);
+            MutableAttributeSet tagAttr = handleStyleAttribute(attr);
+            if (action != emptyAction || !getPreservesUnknownTags()) {
+                action.start(tag, tagAttr);
+                action.end(tag);
+            } else {
+                addSpecialElement(tag, tagAttr);
+            }
+        }
+
+        public void handleStartTag(final Tag tag,
+                                   final MutableAttributeSet attr, final int pos) {
+            final TagAction action = getAction(tag);
+            action.start(tag, handleStyleAttribute(attr));
+        }
+
+        public void handleEndTag(final Tag tag, final int pos) {
+            final TagAction action = getAction(tag);
+            action.end(tag);
+        }
+
+        public void handleText(final char[] data, final int pos) {
+            if (openedBlocks.contains(Tag.A)) {
+                anchorTextEncountered = true;
+            }
+            if (openedBlocks.contains(Tag.TITLE)) {
+                putProperty(TitleProperty, new String(data));
+                return;
+            } 
+            if (openedBlocks.contains(Tag.STYLE) && openedBlocks.contains(Tag.HEAD)) {
+                final String newStyle = new String(data);
+                if (styleRule == null) {
+                    styleRule = newStyle;
+                } else {
+                    styleRule += newStyle;
+                }
+                return;
+            } 
+            if (openedBlocks.contains(Tag.TEXTAREA)) {
+                textAreaContent(data);
+                return;
+            } 
+            if (openedBlocks.contains(Tag.PRE)) {
+                preContent(data);
+                return;
+            } 
+            if (openedBlocks.contains(Tag.OPTION) && openedBlocks.contains(Tag.SELECT)) {
+                final Option option = selectModel.getLastOption();
+                if (option != null && !(option instanceof FormOptionGroup)) {
+                    option.setLabel(new String(data));
+                }
+            } 
+            if (openedBlocks.contains(Tag.LEGEND) && openedBlocks.contains(Tag.FIELDSET)) {
+                if (handleLegend(new String(data), null)) {
+                    return;
+                }
+            } 
+            if (numBlocksOpen > 0) {
+                addContent(data, 0, data.length);
+            }
+        }
+
+        public void flush() throws BadLocationException {
+            flushImpl(true);
+        }
+
+        protected void registerTag(final Tag tag,
+                                   final TagAction action) {
+            tagActionMap.put(tag, action);
+        }
+
+        protected void pushCharacterStyle() {
+            if (charAttr == null) {
+                throw new NullPointerException();
+            }
+            attrStack.push(charAttr.copyAttributes());
+        }
+
+        protected void popCharacterStyle() {
+            if (!attrStack.empty()) {
+                charAttr = (MutableAttributeSet)attrStack.pop();
+            }
+        }
+
+        protected void preContent(final char[] data) {
+            addContent(data, 0, data.length);
+        }
+
+        protected void addContent(final char[] data, final int offset,
+                                  final int length) {
+            addContent(data, offset, length, true);
+        }
+
+        protected void addContent(final char[] data, final int offset,
+                                  final int length,
+                                  final boolean createImpliedPIfNecessary) {
+            addContentSpec(Tag.CONTENT, data, offset, length, charAttr, createImpliedPIfNecessary);
+            
+            if (parseBuffer.size() > tokenThreshold) {
+                try {
+                    flushImpl(false);
+                } catch (BadLocationException e) {
+                }
+                tokenThreshold *= TOKEN_THRESHOLD_MULTIPLIER;
+            }
+        }
+
+        protected void addSpecialElement(final Tag tag,
+                                         final MutableAttributeSet attr) {
+            final boolean needImpliedBlock = !Tag.FRAME.equals(tag);
+            if (!needImpliedBlock) {
+                needImpliedNewLine = false;
+            }
+            addContentSpec(tag, new char[] {' '}, 0, 1, attr, needImpliedBlock);
+        }
+        
+        protected void textAreaContent(final char[] data) {
+            final ElementSpec textareaSpec = findLastSpec(Tag.TEXTAREA);
+            if (textareaSpec == null) {
+                return;
+            }
+            FormTextModel doc = (FormTextModel)getModel(textareaSpec);
+            if (doc != null) {
+                doc.setInitialContent(new String(data));
+            }
+        }
+
+        protected void blockOpen(final Tag tag,
+                                 final MutableAttributeSet attr) {
+            if (impliedBlockOpen && !Tag.IMPLIED.equals(tag)) {
+                blockClose(Tag.IMPLIED);
+                impliedBlockOpen = false;
+            }
+            if (!skipAddingBlockSpec) {
+                ElementSpec blockSpec = new ElementSpec(deriveSpecAttributes(tag, attr), ElementSpec.StartTagType);
+                addSpec(blockSpec);
+            } 
+            skipAddingBlockSpec = false;
+            needImpliedNewLine = true;
+            openedBlocks.add(tag);
+            numBlocksOpen++;
+        }
+
+        protected void blockClose(final Tag tag) {
+            if (needImpliedNewLine) {
+                addImpliedNewLine();
+                if (impliedBlockOpen && !Tag.IMPLIED.equals(tag)) {
+                    blockClose(Tag.IMPLIED);
+                    impliedBlockOpen = false;
+                }
+            }
+            numBlocksOpen--;
+            openedBlocks.remove(tag);
+            addSpec(new ElementSpec(null, ElementSpec.EndTagType));
+        }
+
+        private Object getModel(final ElementSpec spec) {
+            return spec.getAttributes().getAttribute(StyleConstants.ModelAttribute);
+        }
+
+        private void addImpliedNewLine() {
+            pushCharacterStyle();
+            charAttr.addAttribute(HTML.Attribute.IMPLIED_NEW_LINE, Boolean.TRUE);
+            addContent(new char[] {'\n'}, 0, 1, true);
+            popCharacterStyle();
+            needImpliedNewLine = false;
+        }
+
+        private MutableAttributeSet handleStyleAttribute(final MutableAttributeSet attr) {
+            final String style = (String)attr.getAttribute(HTML.Attribute.STYLE);
+            if (style != null) {
+                attr.addAttributes(getStyleSheet().getDeclaration(style));
+                attr.removeAttribute(HTML.Attribute.STYLE);
+            }
+            return attr;
+        }
+
+        private void createImpliedBlock() {
+            if (paragraphOpened()) {
+                return;
+            }
+            blockOpen(Tag.IMPLIED, new SimpleAttributeSet());
+            impliedBlockOpen = true;
+        }
+
+        private MutableAttributeSet createMutableSet(final Object key, final Object value) {
+            MutableAttributeSet specAttr = new SimpleAttributeSet();
+            specAttr.addAttribute(key, value);
+            return specAttr;
+        }
+
+        private boolean paragraphOpened() {
+            return openedBlocks.contains(PARAGRAPH_TAG) || openedBlocks.contains(Tag.IMPLIED) && impliedBlockOpen;
+        }
+
+        private boolean handleLegend(final String legend, final MutableAttributeSet legendAttr) {
+            final ElementSpec fieldSetSpec = findLastSpec(Tag.FIELDSET);
+            FormFieldsetModel fieldSet = (FormFieldsetModel)getModel(fieldSetSpec);
+            if (fieldSet == null || fieldSet.getLegend() != null) {
+                return false;
+            }                
+            if (legend != null) {
+                fieldSet.setLegend(legend);
+            }
+            if (legendAttr != null) {
+                fieldSet.setLegendAttributes(legendAttr);
+            }
+            return true;
+        }
+
+        private void setRemoveImplicitSpecs(final boolean implicitSpecsRemove) {
+            this.implicitSpecsRemove = implicitSpecsRemove;
+        }
+
+        private void flushImpl(final boolean isFinal) throws BadLocationException {
+            if (parseBuffer.isEmpty()) {
+                return;
+            }
+            if (isCreate()) {
+                ElementSpec[] specs = vectorToArray(parseBuffer);
+                create(specs);
+                removeDefaultBody();
+            } else {
+                if (needAddingPopPushSpecs()) {
+                    addPopPushSpecs(parseBuffer);
+                }
+                ElementSpec[] specs = isFinal ? trimEndSpecs(parseBuffer)
+                        : vectorToArray(parseBuffer);
+                insert(offset, specs);
+            }
+            
+            parseBuffer.clear();
+        }
+
+        private void removeDefaultBody() {
+            Element impliedLF = getCharacterElement(getLength() - 1);
+            try {
+                remove(getLength() - 1, 1);
+            } catch (BadLocationException e) {
+            }
+            BranchElement root = (BranchElement)getDefaultRootElement();
+            final int oddBodyIndex = root.getElementCount() - 1;
+            Element oddBody = root.getElement(oddBodyIndex);
+            final Element[] emptyArray = new Element[0];
+            root.replace(oddBodyIndex, 1, emptyArray);
+            
+            Element lf = getCharacterElement(getLength());
+            writeLock();
+            try {
+                ((MutableAttributeSet)lf).removeAttributes(lf.getAttributes().getAttributeNames());
+                ((MutableAttributeSet)lf).addAttributes(impliedLF.getAttributes());
+            } finally {
+                writeUnlock();
+            }
+            
+            final DefaultDocumentEvent removeEvent = new DefaultDocumentEvent(oddBody.getStartOffset(), oddBody.getEndOffset() - oddBody.getStartOffset(), EventType.REMOVE);
+            removeEvent.addEdit(new ElementEdit(root, oddBodyIndex, new Element[] {oddBody}, emptyArray));
+            fireRemoveUpdate(removeEvent);
+        }
+
+        private boolean isCreate() {
+            return offset == 0 && insertTag == null && getLength() == 0 && !implicitSpecsRemove;
+        }
+
+        private ElementSpec[] trimEndSpecs(final Vector buffer) {
+            if ((implicitSpecsRemove || insertTag != null && insertTagFound)) {
+                for (int i = 0; i < 4; i++) {
+                    buffer.remove(buffer.size() - 1);
+                }
+            }
+            return vectorToArray(buffer);
+        }
+
+        private ElementSpec[] vectorToArray(final Vector buffer) {
+            return (ElementSpec[])buffer.toArray(new ElementSpec[buffer.size()]);
+        }
+
+        private boolean needAddingPopPushSpecs() {
+            return (insertTag != null && insertTagFound || insertTag == null) && (popDepth != 0 || pushDepth != 0);
+        }
+
+        private void addContentSpec(final Tag tag, final char[] data,
+                                    final int offset, final int length,
+                                    final MutableAttributeSet attr,
+                                    final boolean createImpliedBlock) {
+            if (createImpliedBlock) {
+                createImpliedBlock();
+            }
+            checkInsertTag(tag);
+            addSpec(new ElementSpec(deriveSpecAttributes(tag, attr),
+                                    ElementSpec.ContentType, data, offset,
+                                    length));
+        }
+
+        private SimpleAttributeSet deriveSpecAttributes(final Tag tag, final MutableAttributeSet attr) {
+            attr.removeAttribute(IMPLIED);
+            attr.addAttribute(StyleConstants.NameAttribute, tag);
+            return new SimpleAttributeSet(attr);
+        }
+
+        private void addSpec(final ElementSpec spec) {
+            if (insertTagFound && (!implicitSpecsRemove || 
+                    specsCount >= IMPLIED_HTML_DOCUMENT_START_SPECS_NUMBER)) {
+                parseBuffer.add(spec);
+            }
+            specsCount++;
+        }
+
+        private void fillTagActionMap() {
+            HiddenAction hiddenAction = new HiddenAction();
+            CharacterAction characterAction = new CharacterAction();
+            AdvancedCharacterAction advancedCharacterAction = new AdvancedCharacterAction();
+            BlockAction blockAction = new BlockAction();
+            ParagraphAction paragraphAction = new ParagraphAction();
+            SpecialAction specialAction = new SpecialAction();
+            FormAction formAction = new FormAction();
+            
+            tagActionMap.put(Tag.A,  new AnchorAction());
+            tagActionMap.put(Tag.ABBR, characterAction);
+            tagActionMap.put(Tag.ACRONYM, characterAction);
+            tagActionMap.put(Tag.ADDRESS, characterAction);
+            tagActionMap.put(Tag.APPLET, new AppletAction());
+            tagActionMap.put(Tag.AREA, new AreaAction());
+            tagActionMap.put(Tag.B, advancedCharacterAction);
+            tagActionMap.put(Tag.BASE, new BaseAction());
+            tagActionMap.put(Tag.BASEFONT, characterAction);
+            tagActionMap.put(Tag.BIG, characterAction);
+            tagActionMap.put(Tag.BDO, characterAction);
+            tagActionMap.put(Tag.BLOCKQUOTE, blockAction);
+            tagActionMap.put(Tag.BODY, blockAction);
+            tagActionMap.put(Tag.BR, specialAction);
+            tagActionMap.put(Tag.BUTTON, formAction);
+            tagActionMap.put(Tag.CAPTION, blockAction);
+            tagActionMap.put(Tag.CENTER, blockAction);
+            tagActionMap.put(Tag.CITE, characterAction);
+            tagActionMap.put(Tag.CODE, characterAction);
+            tagActionMap.put(Tag.COL, hiddenAction);
+            tagActionMap.put(Tag.COLGROUP, hiddenAction);
+            tagActionMap.put(Tag.DD, blockAction);
+            tagActionMap.put(Tag.DFN, characterAction);
+            tagActionMap.put(Tag.DEL, characterAction);
+            tagActionMap.put(Tag.DIR, blockAction);
+            tagActionMap.put(Tag.DIV, blockAction);
+            tagActionMap.put(Tag.DL, blockAction);
+            tagActionMap.put(Tag.DT, paragraphAction);
+            tagActionMap.put(Tag.EM, characterAction);
+            tagActionMap.put(Tag.FIELDSET, formAction);
+            tagActionMap.put(Tag.FONT, new FontAction());
+            tagActionMap.put(Tag.FORM, new FormTagAction());
+            tagActionMap.put(Tag.FRAME, specialAction);
+            tagActionMap.put(Tag.FRAMESET, blockAction);
+            tagActionMap.put(Tag.H1, paragraphAction);
+            tagActionMap.put(Tag.H2, paragraphAction);
+            tagActionMap.put(Tag.H3, paragraphAction);
+            tagActionMap.put(Tag.H4, paragraphAction);
+            tagActionMap.put(Tag.H5, paragraphAction);
+            tagActionMap.put(Tag.H6, paragraphAction);
+            tagActionMap.put(Tag.HEAD, new HeadAction());
+            tagActionMap.put(Tag.HR, specialAction);
+            tagActionMap.put(Tag.HTML, blockAction);
+            tagActionMap.put(Tag.I, advancedCharacterAction);
+            tagActionMap.put(Tag.IFRAME, hiddenAction);
+            tagActionMap.put(Tag.IMG, specialAction);
+            tagActionMap.put(Tag.INPUT, formAction);
+            tagActionMap.put(Tag.INS, characterAction);
+            tagActionMap.put(Tag.ISINDEX, new IsindexAction());
+            tagActionMap.put(Tag.KBD, characterAction);
+            tagActionMap.put(Tag.LABEL, new LabelAction());
+            tagActionMap.put(Tag.LEGEND, formAction);
+            tagActionMap.put(Tag.LI, blockAction);
+            tagActionMap.put(Tag.LINK, new LinkAction());
+            tagActionMap.put(Tag.MAP, new MapAction());
+            tagActionMap.put(Tag.MENU, blockAction);
+            tagActionMap.put(Tag.META, new MetaAction());
+            tagActionMap.put(Tag.NOFRAMES, blockAction);
+            tagActionMap.put(Tag.NOSCRIPT, blockAction);
+            tagActionMap.put(Tag.OBJECT, specialAction);
+            tagActionMap.put(Tag.OL, blockAction);
+            tagActionMap.put(Tag.OPTION, formAction);
+            tagActionMap.put(Tag.OPTGROUP, formAction);
+            tagActionMap.put(Tag.P, paragraphAction);
+            tagActionMap.put(Tag.PARAM, hiddenAction);
+            tagActionMap.put(Tag.PRE, new PreAction());
+            tagActionMap.put(Tag.Q, characterAction);
+            tagActionMap.put(Tag.SAMP, characterAction);
+            tagActionMap.put(Tag.SCRIPT, new ScriptAction());
+            tagActionMap.put(Tag.SELECT, formAction);
+            tagActionMap.put(Tag.SMALL, characterAction);
+            tagActionMap.put(Tag.STRIKE, advancedCharacterAction);
+            tagActionMap.put(Tag.S, characterAction);
+            tagActionMap.put(Tag.SPAN, characterAction);
+            tagActionMap.put(Tag.STRONG, characterAction);
+            tagActionMap.put(Tag.STYLE, new StyleAction());
+            tagActionMap.put(Tag.SUB, advancedCharacterAction);
+            tagActionMap.put(Tag.SUP, advancedCharacterAction);
+            tagActionMap.put(Tag.TABLE, blockAction);
+            tagActionMap.put(Tag.TBODY, blockAction);
+            tagActionMap.put(Tag.TD, blockAction);
+            tagActionMap.put(Tag.TEXTAREA, formAction);
+            tagActionMap.put(Tag.TFOOT, blockAction);
+            tagActionMap.put(Tag.THEAD, blockAction);
+            tagActionMap.put(Tag.TH, blockAction);
+            tagActionMap.put(Tag.TITLE, new TitleAction());
+            tagActionMap.put(Tag.TR, blockAction);
+            tagActionMap.put(Tag.TT, characterAction);
+            tagActionMap.put(Tag.U, advancedCharacterAction);
+            tagActionMap.put(Tag.UL, blockAction);
+            tagActionMap.put(Tag.VAR, characterAction);
+        }
+
+        private TagAction getAction(final Tag tag) {
+            TagAction action = (TagAction)tagActionMap.get(tag);
+            if (action == null) {
+                action = emptyAction;
+            }
+            return action;
+        }
+
+        private ElementSpec findLastSpec(final Tag tag) {
+            for (int i = parseBuffer.size() - 1; i >= 0; i--) {
+                ElementSpec spec = (ElementSpec)parseBuffer.get(i);
+                final AttributeSet specAttr = spec.getAttributes();
+                if (specAttr != null && specAttr.containsAttribute(StyleConstants.NameAttribute, tag)) {
+                    return spec;
+                }
+            }
+            return null;
+        }
+        
+        private boolean checkInsertTag(final Tag tag) {
+            if (!insertTagFound && insertTag.equals(tag)) {
+                insertTagFound = true;
+                skipAddingBlockSpec = true;  
+                addPopPushSpecs(parseBuffer);
+            }
+            
+            return insertTagFound;
+        }
+
+        private void addPopPushSpecs(final Vector buffer) {
+            addJoinPreviousSpec &= (pushDepth > 0 || popDepth > 0);
+//            boolean addNewLineTag = (pushDepth == 0 && popDepth > 0);
+//            if (addNewLineTag && !implicitSpecsRemove && offset == 0) {
+//                parseBuffer.add(0, new ElementSpec(null, ElementSpec.ContentType, new char[] {'\n'}, 0, 1));
+//            }
+            for (int i = 0; i < pushDepth; i++) {
+                final ElementSpec pushSpec = new ElementSpec(null, ElementSpec.StartTagType);
+                pushSpec.setDirection(ElementSpec.JoinNextDirection);
+                buffer.add(0, pushSpec);
+            }
+            pushDepth = 0;
+            for (int i = 0; i < popDepth; i++) {
+                final ElementSpec popSpec = new ElementSpec(null, ElementSpec.EndTagType);
+                buffer.add(0, popSpec);
+            }
+            popDepth = 0;
+            if (addJoinPreviousSpec && !implicitSpecsRemove && offset == 0) {
+                ElementSpec joinPreviousSpec = new ElementSpec(null, ElementSpec.ContentType, new char[] {'\n'}, 0, 1);
+                joinPreviousSpec.setDirection(ElementSpec.JoinPreviousDirection);
+                parseBuffer.add(0, joinPreviousSpec);
+                addJoinPreviousSpec = false;
+            }
+        }
+    }
+
+    public abstract static class Iterator {
+        public abstract AttributeSet getAttributes();
+        public abstract int getEndOffset();
+        public abstract int getStartOffset();
+        public abstract Tag getTag();
+        public abstract boolean isValid();
+        public abstract void next();
+    }
+
+    public static final String AdditionalComments = "AdditionalComments";
+    static final String INITIAL_BASE_PROPERTY = "_initialBase_";
+    
+    private static final String TARGET_TOP = "_top";
+    private static final String TARGET_SELF = "_self";
+    private static final String TARGET_PARENT = "_parent";
+
+    private static final SimpleAttributeSet DEFAULT_CHARACHTER_ATTRIBUTES = new SimpleAttributeSet();  
+
+    private URL base;
+    private Parser parser;
+    private boolean preservesUnknownTags = true;
+    private int threshold = Integer.MAX_VALUE;
+    
+    static {
+        initDefaultCharacterAttributes();
+    }
+
+    public HTMLDocument(final Content c, final StyleSheet styles) {
+        super(c, styles);
+    }
+
+    public HTMLDocument(final StyleSheet styles) {
+        super(styles);
+    }
+
+    public HTMLDocument() {
+        super(new StyleSheet());
+    }
+
+    public void setBase(final URL base) {
+        this.base = base;
+        getStyleSheet().setBase(base);
+    }
+
+    public URL getBase() {
+        return base;
+    }
+
+    public void setParser(final HTMLEditorKit.Parser parser) {
+        this.parser = parser;
+    }
+
+    public HTMLEditorKit.Parser getParser() {
+        return parser;
+    }
+
+    public void setPreservesUnknownTags(final boolean preservesTags) {
+        preservesUnknownTags = preservesTags;
+    }
+
+    public boolean getPreservesUnknownTags() {
+        return preservesUnknownTags;
+    }
+
+    public void setTokenThreshold(final int threshold) {
+        this.threshold = threshold;
+    }
+
+    public int getTokenThreshold() {
+        return threshold;
+    }
+
+    public Element getElement(final Element e, final Object attribute,
+                              final Object value) {
+        final ElementIterator it = new ElementIterator(e);
+        while (it.next() != null) {
+            final Element current = it.current();
+            if (current.getAttributes().containsAttribute(attribute, value)) {
+                return current;
+            }
+        }
+        return null;
+    }
+
+    public Element getElement(final String id) {
+        return getElement(getDefaultRootElement(), HTML.Attribute.ID, id);
+    }
+
+    public Iterator getIterator(final Tag tag) {
+        return new TagIterator(tag, this);
+    }
+
+    public HTMLEditorKit.ParserCallback getReader(final int pos,
+                                                  final int popDepth,
+                                                  final int pushDepth,
+                                                  final Tag insertTag) {
+        return new HTMLReader(pos, popDepth, pushDepth, insertTag);
+    }
+
+    public HTMLEditorKit.ParserCallback getReader(final int pos) {
+        return new HTMLReader(pos);
+    }
+
+    public StyleSheet getStyleSheet() {
+        return (StyleSheet)getAttributeContext();
+    }
+
+    public void insertBeforeEnd(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        checkLeaf(elem);
+        int offset = isParagraph(elem) ? elem.getEndOffset() - 1 : elem.getEndOffset();
+        insertHTMLText(elem, offset, htmlText);
+    }
+    
+    private boolean isParagraph(final Element elem) {
+        final AttributeSet attr = elem.getAttributes();
+        return attr != null && Tag.P.equals(attr.getAttribute(StyleConstants.NameAttribute));
+    }
+
+    public void insertAfterEnd(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        if (elem == null) {
+            return;
+        }
+        int offset = elem.getEndOffset() > getLength() ? getLength() : elem.getEndOffset();
+        insertHTMLText(elem.getParentElement(), offset, htmlText);
+    }
+
+    public void insertBeforeStart(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        if (elem == null) {
+            return;
+        }
+        insertHTMLText(elem.getParentElement(), elem.getStartOffset(), htmlText);
+    }
+    
+    public void insertAfterStart(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        checkLeaf(elem);
+        insertHTMLText(elem, elem.getStartOffset(), htmlText);
+    }
+
+    public void setInnerHTML(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        if (elem == null) {
+            return;
+        }
+        checkLeaf(elem);
+
+        int numRemovingElements = elem.getElementCount();
+        insertHTMLText(elem, elem.getStartOffset(), htmlText);
+        removeElements(elem, elem.getElementCount() - numRemovingElements, numRemovingElements);
+        throw new UnsupportedOperationException("Not implemented");
+    }
+    
+    public void setOuterHTML(final Element elem, final String htmlText)
+            throws BadLocationException, IOException {
+        checkParser();
+        if (elem == null) {
+            return;
+        }
+        
+        int length = elem.getEndOffset() - elem.getStartOffset() - 1;
+        final BranchElement parent = (BranchElement)elem.getParentElement();
+        final int indexBefore = getElementIndex(parent, elem);
+        final int numElementsBefore = parent.getElementCount();
+        insertHTMLText(elem.getParentElement(), elem.getStartOffset(), htmlText);
+        removeElements(parent, indexBefore + (parent.getElementCount() - numElementsBefore), 1);
+        throw new UnsupportedOperationException("Not implemented");
+    }
+    
+    public void processHTMLFrameHyperlinkEvent(final HTMLFrameHyperlinkEvent event) {
+        final String target = event.getTarget();
+
+        if (TARGET_TOP.equals(target)){
+            return;
+        }
+
+        final Element source = event.getSourceElement();
+        final String src = event.getURL().toString();
+        if (source != null && TARGET_SELF.equals(target)) {
+            processTarget(source, src);
+        } else if (source != null && TARGET_PARENT.equals(target)) {
+            final Element parent = source.getParentElement();
+            if (getParser() == null) {
+                setParser(new ParserDelegator());
+            }
+            try {
+                setOuterHTML(parent, "<frame src=\"" + src + "\">");
+            } catch (BadLocationException e) {
+            } catch (IOException e) {
+            }
+        } else {
+            final ElementIterator frameIterator = new ElementIterator(getDefaultRootElement());
+            if (frameIterator != null) {
+                while (frameIterator.next() != null) {
+                    final Element element = frameIterator.current();
+                    if (Tag.FRAME.equals(element.getName())
+                        && element.getAttributes().containsAttribute(HTML.Attribute.NAME, target)) {
+                        
+                        processTarget(element, src);
+                    }
+                }
+            }
+        }
+    }
+    
+    protected AbstractElement createDefaultRoot() {
+        final BlockElement root = new BlockElement(null, null);
+        writeLock();
+        try {
+            root.addAttribute(StyleConstants.NameAttribute, Tag.HTML);
+            AttributeSet attr = getAttributeContext().getEmptySet();
+            final BranchElement body = (BranchElement)createBranchElement(root,
+                                                                          null);
+            body.addAttribute(StyleConstants.NameAttribute, Tag.BODY);
+            
+            final BranchElement p = (BranchElement)createBranchElement(body, null);
+            p.addAttribute(StyleConstants.NameAttribute, Tag.P);
+            p.addAttribute(CSS.Attribute.MARGIN_TOP, 
+                           CSS.Attribute.MARGIN_TOP.getDefaultValue());
+            
+            final LeafElement content =
+                (LeafElement)createLeafElement(p, null, getStartPosition().getOffset(),
+                                              getEndPosition().getOffset());
+            content.addAttribute(StyleConstants.NameAttribute, Tag.CONTENT);
+            p.replace(0, 0, new Element[] {content});
+            body.replace(0, 0, new Element[] {p});
+            root.replace(0, 0, new Element[] {body});
+        } finally {
+            writeUnlock();
+        }
+        return root;
+    }
+
+    protected Element createLeafElement(final Element parent,
+                                        final AttributeSet a,
+                                        final int start, final int end) {
+        return new RunElement(parent, a, start, end);
+    }
+
+    protected Element createBranchElement(final Element parent,
+                                          final AttributeSet attr) {
+        return new BlockElement(parent, attr);
+    }
+    
+    protected void insertUpdate(final DefaultDocumentEvent event, final AttributeSet attrs) {
+        AttributeSet contentAttr = attrs;
+        if (contentAttr == null) {
+            contentAttr = new SimpleAttributeSet();
+            ((SimpleAttributeSet)contentAttr).addAttribute(StyleConstants.NameAttribute, Tag.CONTENT);
+        }
+        super.insertUpdate(event, contentAttr);
+    }
+
+    private void processTarget(final Element target, final String src) {
+        final MutableAttributeSet targetAttr = (MutableAttributeSet)target.getAttributes();
+        writeLock();
+        try {
+            targetAttr.addAttribute(HTML.Attribute.SRC, src);
+        } finally {
+            writeUnlock();
+        }
+        fireChangedUpdate(new DefaultDocumentEvent(target.getStartOffset(), target.getEndOffset() - target.getStartOffset(), EventType.CHANGE));
+    }
+
+    private void removeElements(final Element parent, final int startRemoveIndex, final int numRemoved) {
+        final Element[] emptyArray = new Element[0];
+        final Element[] removedArray = new Element[numRemoved];
+        final BranchElement branch = (BranchElement)parent;
+        final int numElements = branch.getElementCount();
+        for (int i = 0; i < numRemoved; i++) {
+            removedArray[i] = branch.getElement(startRemoveIndex + i);
+        }
+        branch.replace(startRemoveIndex, numRemoved, emptyArray);
+        final int eventLength = removedArray[numRemoved - 1].getEndOffset() - removedArray[0].getStartOffset();
+        DefaultDocumentEvent removeEvent = new DefaultDocumentEvent(removedArray[0].getStartOffset(), 
+                                                                    eventLength, 
+                                                                    EventType.REMOVE);
+        removeEvent.addEdit(new ElementEdit(parent, startRemoveIndex, removedArray, emptyArray));
+        fireRemoveUpdate(removeEvent);
+    }
+
+    private void insertHTMLText(final Element elem, final int offset,
+                                final String htmlText) throws IOException {
+        Element preceedBranch = getInsertionStartElement(offset - 1);
+        int pop = -1;
+        int push = -1;
+        do {
+            pop++;
+            preceedBranch = preceedBranch.getParentElement();
+            push = getAncestorDepth(preceedBranch, elem);
+        } while (preceedBranch != null && push < 0);
+        if (push == -1) {
+            return;
+        }
+        
+        final HTMLReader reader = new HTMLReader(offset, pop, push, null);
+        reader.setRemoveImplicitSpecs(true);
+        parser.parse(new StringReader(htmlText), reader, true);
+        try {
+            reader.flush();
+        } catch (BadLocationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void checkParser() {
+        if (parser == null) {
+            throw new IllegalStateException("Parser should be set");
+        }
+    }
+
+    private void checkLeaf(final Element elem) {
+        if (elem.isLeaf()) {
+            throw new IllegalArgumentException("Can't insert HTML text after start of a leaf element");
+        }
+    }
+    
+    private int getElementIndex(final BranchElement branch, final Element child) {
+        final int numChildren = branch.getElementCount();
+        for (int i = 0; i < numChildren; i++) {
+            if (branch.getElement(i) == child) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    private Element getInsertionStartElement(final int offset) {
+        Element result = getDefaultRootElement();
+        do {
+            result = result.getElement(result.getElementIndex(offset));
+        } while (!result.isLeaf());
+        
+        return result;
+    }
+
+    private int getAncestorDepth(final Element elem, final Element child) {
+        int depth = 0;
+        Element parent = child;
+        while (parent != null) {
+            if (elem == parent) {
+                return depth;
+            }
+            depth++;
+            parent = parent.getParentElement();
+        }
+        return -1;
+    }
+    
+    private static void initDefaultCharacterAttributes() {
+        addDefaultCSSAttribute(Tag.B, CSS.Attribute.FONT_WEIGHT, "bold");
+        addDefaultCSSAttribute(Tag.I, CSS.Attribute.FONT_STYLE, "italic");
+        addDefaultCSSAttribute(Tag.STRIKE, CSS.Attribute.TEXT_DECORATION, "line-through");
+        addDefaultCSSAttribute(Tag.SUB, CSS.Attribute.VERTICAL_ALIGN, "sub");
+        addDefaultCSSAttribute(Tag.SUP, CSS.Attribute.VERTICAL_ALIGN, "super");
+        addDefaultCSSAttribute(Tag.U, CSS.Attribute.TEXT_DECORATION, "underline");
+        addDefaultCSSAttribute(Tag.PRE, CSS.Attribute.WHITE_SPACE, "pre");
+    }
+
+    private static void addDefaultCSSAttribute(final Tag tag, final CSS.Attribute attr, final String value) {
+        SimpleAttributeSet attrSet = new SimpleAttributeSet();
+        attrSet.addAttribute(attr, attr.getConverter().toCSS(value));
+        DEFAULT_CHARACHTER_ATTRIBUTES.addAttribute(tag, attrSet);
+    }
+
+    private static SimpleAttributeSet getDefaultCSSAttributes(final Tag tag) {
+        return (SimpleAttributeSet)DEFAULT_CHARACHTER_ATTRIBUTES.getAttribute(tag);
+    }
+}