001    /* HTMLEditorKit.java --
002       Copyright (C) 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text.html;
040    
041    
042    import java.awt.event.ActionEvent;
043    import java.awt.event.MouseAdapter;
044    import java.awt.event.MouseEvent;
045    import java.awt.event.MouseMotionListener;
046    import java.awt.Cursor;
047    import java.awt.Point;
048    
049    import java.io.IOException;
050    import java.io.InputStream;
051    import java.io.InputStreamReader;
052    import java.io.Reader;
053    import java.io.Serializable;
054    import java.io.StringReader;
055    import java.io.Writer;
056    import java.net.MalformedURLException;
057    import java.net.URL;
058    
059    import javax.accessibility.Accessible;
060    import javax.accessibility.AccessibleContext;
061    
062    import javax.swing.Action;
063    import javax.swing.JEditorPane;
064    import javax.swing.SwingUtilities;
065    import javax.swing.event.HyperlinkEvent;
066    import javax.swing.text.AttributeSet;
067    import javax.swing.text.BadLocationException;
068    import javax.swing.text.Document;
069    import javax.swing.text.EditorKit;
070    import javax.swing.text.Element;
071    import javax.swing.text.MutableAttributeSet;
072    import javax.swing.text.StyleConstants;
073    import javax.swing.text.StyledDocument;
074    import javax.swing.text.StyledEditorKit;
075    import javax.swing.text.TextAction;
076    import javax.swing.text.View;
077    import javax.swing.text.ViewFactory;
078    import javax.swing.text.html.parser.ParserDelegator;
079    
080    /* Move these imports here after javax.swing.text.html to make it compile
081       with jikes.  */
082    import gnu.javax.swing.text.html.parser.GnuParserDelegator;
083    import gnu.javax.swing.text.html.parser.HTML_401F;
084    
085    /**
086     * @author Lillian Angel (langel at redhat dot com)
087     */
088    public class HTMLEditorKit
089      extends StyledEditorKit
090      implements Serializable, Cloneable, Accessible
091    {
092    
093      /**
094       * Fires the hyperlink events on the associated component
095       * when needed.
096       */
097      public static class LinkController
098        extends MouseAdapter
099        implements MouseMotionListener, Serializable
100        {
101    
102          /**
103           * The element of the last anchor tag.
104           */
105          private Element lastAnchorElement;
106    
107          /**
108           * Constructor
109           */
110          public LinkController()
111          {
112            super();
113          }
114    
115          /**
116           * Dispatched when the mouse is clicked. If the component
117           * is read-only, then the clicked event is used to drive an
118           * attempt to follow the reference specified by a link
119           *
120           * @param e - the mouse event
121           */
122          public void mouseClicked(MouseEvent e)
123          {
124            JEditorPane editor = (JEditorPane) e.getSource();
125            if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e))
126              {
127                Point loc = e.getPoint();
128                int pos = editor.viewToModel(loc);
129                if (pos >= 0)
130                  activateLink(pos, editor, e.getX(), e.getY());
131              }
132          }
133    
134          /**
135           * Dispatched when the mouse is dragged on a component.
136           *
137           * @param e - the mouse event.
138           */
139          public void mouseDragged(MouseEvent e)
140          {
141            // Nothing to do here.
142          }
143    
144          /**
145           * Dispatched when the mouse cursor has moved into the component.
146           *
147           * @param e - the mouse event.
148           */
149          public void mouseMoved(MouseEvent e)
150          {
151            JEditorPane editor = (JEditorPane) e.getSource();
152            HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
153            if (! editor.isEditable())
154              {
155                Document doc = editor.getDocument();
156                if (doc instanceof HTMLDocument)
157                  {
158                    Cursor newCursor = kit.getDefaultCursor();
159                    HTMLDocument htmlDoc = (HTMLDocument) doc;
160                    Point loc = e.getPoint();
161                    int pos = editor.viewToModel(loc);
162                    Element el = htmlDoc.getCharacterElement(pos);
163                    if (pos < el.getStartOffset() || pos >= el.getEndOffset())
164                      el = null;
165                    if (el != null)
166                      {
167                        AttributeSet aAtts = (AttributeSet)
168                                       el.getAttributes().getAttribute(HTML.Tag.A);
169                        if (aAtts != null)
170                          {
171                            if (el != lastAnchorElement)
172                              {
173                                if (lastAnchorElement != null)
174                                  htmlDoc.updateSpecialClass(lastAnchorElement,
175                                                      HTML.Attribute.DYNAMIC_CLASS,
176                                                      null);
177                                lastAnchorElement = el;
178                                htmlDoc.updateSpecialClass(el,
179                                                      HTML.Attribute.DYNAMIC_CLASS,
180                                                      "hover");
181                              }
182                            newCursor = kit.getLinkCursor();
183                          }
184                        else
185                          {
186                            if (lastAnchorElement != null)
187                              htmlDoc.updateSpecialClass(lastAnchorElement,
188                                                  HTML.Attribute.DYNAMIC_CLASS,
189                                                  null);
190                            lastAnchorElement = null;
191                          }
192                      }
193                    else
194                      {
195                        if (lastAnchorElement != null)
196                          htmlDoc.updateSpecialClass(lastAnchorElement,
197                                              HTML.Attribute.DYNAMIC_CLASS,
198                                              null);
199                        lastAnchorElement = null;
200                      }
201                    if (editor.getCursor() != newCursor)
202                      {
203                        editor.setCursor(newCursor);
204                      }
205                  }
206              }
207          }
208    
209          /**
210           * If the given position represents a link, then linkActivated is called
211           * on the JEditorPane.
212           *
213           * @param pos the position
214           * @param editor the editor pane
215           */
216          protected void activateLink(int pos, JEditorPane editor)
217          {
218            activateLink(pos, editor);
219          }
220    
221          private void activateLink(int pos, JEditorPane editor, int x, int y)
222          {
223            // TODO: This is here for future extension for mapped links support.
224            // For the time beeing we implement simple hyperlinks.
225            Document doc = editor.getDocument();
226            if (doc instanceof HTMLDocument)
227              {
228                HTMLDocument htmlDoc = (HTMLDocument) doc;
229                Element el = htmlDoc.getCharacterElement(pos);
230                AttributeSet atts = el.getAttributes();
231                AttributeSet anchorAtts =
232                  (AttributeSet) atts.getAttribute(HTML.Tag.A);
233                String href = null;
234                if (anchorAtts != null)
235                  {
236                    href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF);
237                    htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS,
238                                               "visited");
239                  }
240                else
241                  {
242                    // TODO: Implement link maps here.
243                  }
244                HyperlinkEvent event = null;
245                if (href != null)
246                  event = createHyperlinkEvent(editor, htmlDoc, href,
247                                               anchorAtts, el);
248                if (event != null)
249                  editor.fireHyperlinkUpdate(event);
250              }
251    
252          }
253    
254          /**
255           * Creates a HyperlinkEvent for the specified href and anchor if
256           * possible. If for some reason this won't work, return null.
257           *
258           * @param editor the editor
259           * @param doc the document
260           * @param href the href link
261           * @param anchor the anchor
262           * @param el the element
263           *
264           * @return the hyperlink event, or <code>null</code> if we couldn't
265           *         create one
266           */
267          private HyperlinkEvent createHyperlinkEvent(JEditorPane editor,
268                                                      HTMLDocument doc,
269                                                      String href,
270                                                      AttributeSet anchor,
271                                                      Element el)
272          {
273            URL url;
274            try
275              {
276                URL base = doc.getBase();
277                url = new URL(base, href);
278    
279              }
280            catch (MalformedURLException ex)
281              {
282                url = null;
283              }
284            HyperlinkEvent ev;
285            if (doc.isFrameDocument())
286              {
287                String target = null;
288                if (anchor != null)
289                  target = (String) anchor.getAttribute(HTML.Attribute.TARGET);
290                if (target == null || target.equals(""))
291                  target = doc.getBaseTarget();
292                if (target == null || target.equals(""))
293                  target = "_self";
294                ev = new HTMLFrameHyperlinkEvent(editor,
295                                                HyperlinkEvent.EventType.ACTIVATED,
296                                                url, href, el, target);
297              }
298            else
299              {
300                ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED,
301                                        url, href, el);
302              }
303            return ev;
304          }
305        }
306    
307      /**
308       * This class is used to insert a string of HTML into an existing
309       * document. At least 2 HTML.Tags need to be supplied. The first Tag (parentTag)
310       * identifies the parent in the document to add the elements to. The second, (addTag),
311       * identifies that the first tag should be added to the document as seen in the string.
312       * The parser will generate all appropriate (opening/closing tags_ even if they are not
313       * in the HTML string passed in.
314       */
315      public static class InsertHTMLTextAction
316        extends HTMLTextAction
317        {
318    
319          /**
320           * Tag in HTML to start adding tags from.
321           */
322          protected HTML.Tag addTag;
323    
324          /**
325           * Alternate tag in HTML to start adding tags from if parentTag is
326           * not found and alternateParentTag is not found.
327           */
328          protected HTML.Tag alternateAddTag;
329    
330          /**
331           * Alternate tag to check if parentTag is not found.
332           */
333          protected HTML.Tag alternateParentTag;
334    
335          /**
336           * HTML to insert.
337           */
338          protected String html;
339    
340          /**
341           * Tag to check for in the document.
342           */
343          protected HTML.Tag parentTag;
344    
345          /**
346           * Initializes all fields.
347           *
348           * @param name - the name of the document.
349           * @param html - the html to insert
350           * @param parentTag - the parent tag to check for
351           * @param addTag - the tag to start adding from
352           */
353          public InsertHTMLTextAction(String name, String html,
354                                      HTML.Tag parentTag, HTML.Tag addTag)
355          {
356            this(name, html, parentTag, addTag, null, null);
357          }
358    
359          /**
360           * Initializes all fields and calls super
361           *
362           * @param name - the name of the document.
363           * @param html - the html to insert
364           * @param parentTag - the parent tag to check for
365           * @param addTag - the tag to start adding from
366           * @param alternateParentTag - the alternate parent tag
367           * @param alternateAddTag - the alternate add tag
368           */
369          public InsertHTMLTextAction(String name, String html, HTML.Tag parentTag,
370                                      HTML.Tag addTag, HTML.Tag alternateParentTag,
371                                      HTML.Tag alternateAddTag)
372          {
373            super(name);
374            // Fields are for easy access when the action is applied to an actual
375            // document.
376            this.html = html;
377            this.parentTag = parentTag;
378            this.addTag = addTag;
379            this.alternateParentTag = alternateParentTag;
380            this.alternateAddTag = alternateAddTag;
381          }
382    
383          /**
384           * HTMLEditorKit.insertHTML is called. If an exception is
385           * thrown, it is wrapped in a RuntimeException and thrown.
386           *
387           * @param editor - the editor to use to get the editorkit
388           * @param doc -
389           *          the Document to insert the HTML into.
390           * @param offset -
391           *          where to begin inserting the HTML.
392           * @param html -
393           *          the String to insert
394           * @param popDepth -
395           *          the number of ElementSpec.EndTagTypes to generate before
396           *          inserting
397           * @param pushDepth -
398           *          the number of ElementSpec.StartTagTypes with a direction of
399           *          ElementSpec.JoinNextDirection that should be generated before
400           * @param addTag -
401           *          the first tag to start inserting into document
402           */
403          protected void insertHTML(JEditorPane editor, HTMLDocument doc, int offset,
404                                  String html, int popDepth, int pushDepth,
405                                  HTML.Tag addTag)
406          {
407            try
408              {
409                super.getHTMLEditorKit(editor).insertHTML(doc, offset, html,
410                                                          popDepth, pushDepth, addTag);
411              }
412            catch (IOException e)
413              {
414                throw (RuntimeException) new RuntimeException("Parser is null.").initCause(e);
415              }
416            catch (BadLocationException ex)
417              {
418                throw (RuntimeException) new RuntimeException("BadLocationException: "
419                                                  + offset).initCause(ex);
420              }
421          }
422    
423          /**
424           * Invoked when inserting at a boundary. Determines the number of pops,
425           * and then the number of pushes that need to be performed. The it calls
426           * insertHTML.
427           *
428           * @param editor -
429           *          the editor to use to get the editorkit
430           * @param doc -
431           *          the Document to insert the HTML into.
432           * @param offset -
433           *          where to begin inserting the HTML.
434           * @param insertElement -
435           *          the element to insert
436           * @param html -
437           *          the html to insert
438           * @param parentTag -
439           *          the parent tag
440           * @param addTag -
441           *          the first tag
442           */
443          protected void insertAtBoundary(JEditorPane editor,
444                                          HTMLDocument doc, int offset,
445                                          Element insertElement,
446                                          String html, HTML.Tag parentTag,
447                                          HTML.Tag addTag)
448          {
449            insertAtBoundry(editor, doc, offset, insertElement,
450                            html, parentTag, addTag);
451          }
452    
453          /**
454           * Invoked when inserting at a boundary. Determines the number of pops,
455           * and then the number of pushes that need to be performed. The it calls
456           * insertHTML.
457           *
458           * @param editor - the editor to use to get the editorkit
459           * @param doc -
460           *          the Document to insert the HTML into.
461           * @param offset -
462           *          where to begin inserting the HTML.
463           * @param insertElement - the element to insert
464           * @param html - the html to insert
465           * @param parentTag - the parent tag
466           * @param addTag - the first tag
467           *
468           * @deprecated as of v1.3, use insertAtBoundary
469           */
470          protected void insertAtBoundry(JEditorPane editor,
471                                         HTMLDocument doc,
472                                         int offset, Element insertElement,
473                                         String html, HTML.Tag parentTag,
474                                         HTML.Tag addTag)
475          {
476            Element parent = insertElement;
477            Element el;
478            // Find common parent element.
479            if (offset > 0 || insertElement == null)
480              {
481                el = doc.getDefaultRootElement();
482                while (el != null && el.getStartOffset() != offset
483                       && ! el.isLeaf())
484                  el = el.getElement(el.getElementIndex(offset));
485                parent = el != null ? el.getParentElement() : null;
486              }
487            if (parent != null)
488              {
489                int pops = 0;
490                int pushes = 0;
491                if (offset == 0 && insertElement != null)
492                  {
493                    el = parent;
494                    while (el != null && ! el.isLeaf())
495                      {
496                        el = el.getElement(el.getElementIndex(offset));
497                        pops++;
498                      }
499                  }
500                else
501                  {
502                    el = parent;
503                    offset--;
504                    while (el != null && ! el.isLeaf())
505                      {
506                        el = el.getElement(el.getElementIndex(offset));
507                        pops++;
508                      }
509                    el = parent;
510                    offset++;
511                    while (el != null && el != insertElement)
512                      {
513                        el = el.getElement(el.getElementIndex(offset));
514                        pushes++;
515                      }
516                  }
517                pops = Math.max(0, pops - 1);
518                insertHTML(editor, doc, offset, html, pops, pushes, addTag);
519              }
520          }
521    
522          /**
523           * Inserts the HTML.
524           *
525           * @param ae - the action performed
526           */
527          public void actionPerformed(ActionEvent ae)
528          {
529            JEditorPane source = getEditor(ae);
530            if (source != null)
531              {
532                HTMLDocument d = getHTMLDocument(source);
533                int offset = source.getSelectionStart();
534                int length = d.getLength();
535                boolean inserted = true;
536                if (! tryInsert(source, d, offset, parentTag, addTag))
537                  {
538                    inserted = tryInsert(source, d, offset, alternateParentTag,
539                                         alternateAddTag);
540                  }
541                if (inserted)
542                  adjustSelection(source, d, offset, length);
543              }
544          }
545    
546          /**
547           * Tries to insert the html chunk to the specified <code>addTag</code>.
548           *
549           * @param pane the editor
550           * @param doc the document
551           * @param offset the offset at which to insert
552           * @param tag the tag at which to insert
553           * @param addTag the add tag
554           *
555           * @return <code>true</code> when the html has been inserted successfully,
556           *         <code>false</code> otherwise
557           */
558          private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset,
559                                    HTML.Tag tag, HTML.Tag addTag)
560          {
561            boolean inserted = false;
562            Element el = findElementMatchingTag(doc, offset, tag);
563            if (el != null && el.getStartOffset() == offset)
564              {
565                insertAtBoundary(pane, doc, offset, el, html, tag, addTag);
566                inserted = true;
567              }
568            else if (offset > 0)
569              {
570                int depth = elementCountToTag(doc, offset - 1, tag);
571                if (depth != -1)
572                  {
573                    insertHTML(pane, doc, offset, html, depth, 0, addTag);
574                    inserted = true;
575                  }
576              }
577            return inserted;
578          }
579    
580          /**
581           * Adjusts the selection after an insertion has been performed.
582           *
583           * @param pane the editor pane
584           * @param doc the document
585           * @param offset the insert offset
586           * @param oldLen the old document length
587           */
588          private void adjustSelection(JEditorPane pane, HTMLDocument doc,
589                                       int offset, int oldLen)
590          {
591            int newLen = doc.getLength();
592            if (newLen != oldLen && offset < newLen)
593              {
594                if (offset > 0)
595                  {
596                    String text;
597                    try
598                      {
599                        text = doc.getText(offset - 1, 1);
600                      }
601                    catch (BadLocationException ex)
602                      {
603                        text = null;
604                      }
605                    if (text != null && text.length() > 0
606                        && text.charAt(0) == '\n')
607                      {
608                        pane.select(offset, offset);
609                      }
610                    else
611                      {
612                        pane.select(offset + 1, offset + 1);
613                      }
614                  }
615                else
616                  {
617                    pane.select(1, 1);
618                  }
619              }
620          }
621      }
622    
623      /**
624       * Abstract Action class that helps inserting HTML into an existing document.
625       */
626      public abstract static class HTMLTextAction
627        extends StyledEditorKit.StyledTextAction
628        {
629    
630          /**
631           * Constructor
632           */
633          public HTMLTextAction(String name)
634          {
635            super(name);
636          }
637    
638          /**
639           * Gets the HTMLDocument from the JEditorPane.
640           *
641           * @param e - the editor pane
642           * @return the html document.
643           */
644          protected HTMLDocument getHTMLDocument(JEditorPane e)
645          {
646            Document d = e.getDocument();
647            if (d instanceof HTMLDocument)
648              return (HTMLDocument) d;
649            throw new IllegalArgumentException("Document is not a HTMLDocument.");
650          }
651    
652          /**
653           * Gets the HTMLEditorKit
654           *
655           * @param e - the JEditorPane to get the HTMLEditorKit from.
656           * @return the HTMLEditorKit
657           */
658          protected HTMLEditorKit getHTMLEditorKit(JEditorPane e)
659          {
660            EditorKit d = e.getEditorKit();
661            if (d instanceof HTMLEditorKit)
662              return (HTMLEditorKit) d;
663            throw new IllegalArgumentException("EditorKit is not a HTMLEditorKit.");
664          }
665    
666          /**
667           * Returns an array of Elements that contain the offset.
668           * The first elements corresponds to the roots of the doc.
669           *
670           * @param doc - the document to get the Elements from.
671           * @param offset - the offset the Elements must contain
672           * @return an array of all the elements containing the offset.
673           */
674          protected Element[] getElementsAt(HTMLDocument doc,
675                                            int offset)
676          {
677            return getElementsAt(doc.getDefaultRootElement(), offset, 0);
678          }
679    
680          /**
681           * Helper function to get all elements using recursion.
682           */
683          private Element[] getElementsAt(Element root, int offset, int depth)
684          {
685            Element[] elements = null;
686            if (root != null)
687              {
688                if (root.isLeaf())
689                  {
690                    elements = new Element[depth + 1];
691                    elements[depth] = root;
692                    return elements;
693                  }
694                elements = getElementsAt(root.getElement(root.getElementIndex(offset)),
695                                         offset, depth + 1);
696                elements[depth] = root;
697              }
698            return elements;
699          }
700    
701          /**
702           * Returns the number of elements, starting at the deepest point, needed
703           * to get an element representing tag. -1 if no elements are found, 0 if
704           * the parent of the leaf at offset represents the tag.
705           *
706           * @param doc -
707           *          the document to search
708           * @param offset -
709           *          the offset to check
710           * @param tag -
711           *          the tag to look for
712           * @return - the number of elements needed to get an element representing
713           *         tag.
714           */
715          protected int elementCountToTag(HTMLDocument doc,
716                                          int offset, HTML.Tag tag)
717          {
718            Element root = doc.getDefaultRootElement();
719            int num = -1;
720            Element next = root.getElement(root.getElementIndex(offset));
721    
722            while (!next.isLeaf())
723              {
724                num++;
725                if (next.getAttributes().
726                    getAttribute(StyleConstants.NameAttribute).equals(tag))
727                  return num;
728                next = next.getElement(next.getElementIndex(offset));
729              }
730            return num;
731          }
732    
733          /**
734           * Gets the deepest element at offset with the
735           * matching tag.
736           *
737           * @param doc - the document to search
738           * @param offset - the offset to check for
739           * @param tag - the tag to match
740           * @return - the element that is found, null if not found.
741           */
742          protected Element findElementMatchingTag(HTMLDocument doc,
743                                                   int offset, HTML.Tag tag)
744          {
745            Element element = doc.getDefaultRootElement();
746            Element tagElement = null;
747    
748            while (element != null)
749              {
750                Object otag = element.getAttributes().getAttribute(
751                                         StyleConstants.NameAttribute);
752                if (otag instanceof HTML.Tag && otag.equals(tag))
753                  tagElement = element;
754                element = element.getElement(element.getElementIndex(offset));
755              }
756    
757            return tagElement;
758          }
759        }
760    
761      /**
762       * A {@link ViewFactory} that is able to create {@link View}s for
763       * the <code>Element</code>s that are supported.
764       */
765      public static class HTMLFactory
766        implements ViewFactory
767      {
768    
769        /**
770         * Constructor
771         */
772        public HTMLFactory()
773        {
774          // Do Nothing here.
775        }
776    
777        /**
778         * Creates a {@link View} for the specified <code>Element</code>.
779         *
780         * @param element the <code>Element</code> to create a <code>View</code>
781         *        for
782         * @return the <code>View</code> for the specified <code>Element</code>
783         *         or <code>null</code> if the type of <code>element</code> is
784         *         not supported
785         */
786        public View create(Element element)
787        {
788          View view = null;
789          Object attr =
790            element.getAttributes().getAttribute(StyleConstants.NameAttribute);
791          if (attr instanceof HTML.Tag)
792            {
793              HTML.Tag tag = (HTML.Tag) attr;
794    
795              if (tag == HTML.Tag.IMPLIED || tag == HTML.Tag.P
796                  || tag == HTML.Tag.H1 || tag == HTML.Tag.H2
797                  || tag == HTML.Tag.H3 || tag == HTML.Tag.H4
798                  || tag == HTML.Tag.H5 || tag == HTML.Tag.H6
799                  || tag == HTML.Tag.DT)
800                view = new ParagraphView(element);
801              else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL
802                       || tag == HTML.Tag.DD || tag == HTML.Tag.BODY
803                       || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER
804                       || tag == HTML.Tag.DIV
805                       || tag == HTML.Tag.BLOCKQUOTE
806                       || tag == HTML.Tag.PRE
807                       || tag == HTML.Tag.FORM
808                       // Misplaced TD and TH tags get mapped as vertical block.
809                       // Note that correctly placed tags get mapped in TableView.
810                       || tag == HTML.Tag.TD || tag == HTML.Tag.TH)
811                view = new BlockView(element, View.Y_AXIS);
812              else if (tag == HTML.Tag.TR)
813                // Misplaced TR tags get mapped as horizontal blocks.
814                // Note that correctly placed tags get mapped in TableView.
815                view = new BlockView(element, View.X_AXIS);
816              else if (tag == HTML.Tag.IMG)
817                view = new ImageView(element);
818    
819              else if (tag == HTML.Tag.CONTENT)
820                view = new InlineView(element);
821              else if (tag == HTML.Tag.HEAD)
822                view = new NullView(element);
823              else if (tag == HTML.Tag.TABLE)
824                view = new javax.swing.text.html.TableView(element);
825              else if (tag == HTML.Tag.HR)
826                view = new HRuleView(element);
827              else if (tag == HTML.Tag.BR)
828                view = new BRView(element);
829              else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT
830                       || tag == HTML.Tag.TEXTAREA)
831                view = new FormView(element);
832    
833              else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR
834                       || tag == HTML.Tag.UL || tag == HTML.Tag.OL)
835                view = new ListView(element);
836              else if (tag == HTML.Tag.FRAMESET)
837                view = new FrameSetView(element);
838              else if (tag == HTML.Tag.FRAME)
839                view = new FrameView(element);
840              else if (tag == HTML.Tag.OBJECT)
841                view = new ObjectView(element);
842            }
843          if (view == null)
844            {
845              view = new NullView(element);
846            }
847          return view;
848        }
849      }
850    
851      /**
852       * The abstract HTML parser declaration.
853       */
854      public abstract static class Parser
855      {
856        /**
857         * Parse the HTML text, calling various methods of the provided callback
858         * in response to the occurence of the corresponding HTML constructions.
859         * @param reader The reader to read the source HTML from.
860         * @param callback The callback to receive information about the parsed
861         * HTML structures
862         * @param ignoreCharSet If true, the parser ignores all charset information
863         * that may be present in HTML documents.
864         * @throws IOException, normally if the reader throws one.
865         */
866        public abstract void parse(Reader reader, ParserCallback callback,
867                                   boolean ignoreCharSet) throws IOException;
868      }
869    
870      /**
871       * The "hook" that receives all information about the HTML document
872       * structure while parsing it. The methods are invoked by parser
873       * and should be normally overridden.
874       */
875      public static class ParserCallback
876      {
877        /**
878         * If the tag does not occurs in the html stream directly, but
879         * is supposed by parser, the tag attribute set contains this additional
880         * attribute, having value Boolean.True.
881         */
882        public static final Object IMPLIED = "_implied_";
883    
884        /**
885         * Constructor
886         */
887        public ParserCallback()
888        {
889          // Nothing to do here.
890        }
891    
892        /**
893         * The parser calls this method after it finishes parsing the document.
894         */
895        public void flush() throws BadLocationException
896        {
897          // Nothing to do here.
898        }
899    
900        /**
901         * Handle HTML comment, present in the given position.
902         * @param comment the comment
903         * @position the position of the comment in the text being parsed.
904         */
905        public void handleComment(char[] comment, int position)
906        {
907          // Nothing to do here.
908        }
909    
910        /**
911         * Notifies about the character sequences, used to separate lines in
912         * this document. The parser calls this method after it finishes
913         * parsing the document, but before flush().
914         * @param end_of_line The "end of line sequence", one of: \r or \n or \r\n.
915         */
916        public void handleEndOfLineString(String end_of_line)
917        {
918          // Nothing to do here.
919        }
920    
921        /**
922         * The method is called when the HTML closing tag ((like &lt;/table&gt;)
923         * is found or if the parser concludes that the one should be present
924         * in the current position.
925         * @param tag The tag being handled
926         * @param position the tag position in the text being parsed.
927         */
928        public void handleEndTag(HTML.Tag tag, int position)
929        {
930          // Nothing to do here.
931        }
932    
933        /**
934         * Handle the error.
935         * @param message The message, explaining the error.
936         * @param position The starting position of the fragment that has caused
937         * the error in the html document being parsed.
938         */
939        public void handleError(String message, int position)
940        {
941          // Nothing to do here.
942        }
943    
944        /**
945         * Handle the tag with no content, like &lt;br&gt;. The method is
946         * called for the elements that, in accordance with the current DTD,
947         * has an empty content.
948         * @param tag The tag being handled.
949         * @param position The tag position in the text being parsed.
950         */
951        public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes,
952                                    int position)
953        {
954          // Nothing to do here.
955        }
956    
957        /**
958         * The method is called when the HTML opening tag ((like &lt;table&gt;)
959         * is found or if the parser concludes that the one should be present
960         * in the current position.
961         * @param tag The tag being handled
962         * @param position The tag position in the text being parsed
963         */
964        public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes,
965                                   int position)
966        {
967          // Nothing to do here.
968        }
969    
970        /**
971         * Handle the text section.
972         * @param text A section text.
973         * @param position The text position in the HTML document text being parsed.
974         */
975        public void handleText(char[] text, int position)
976        {
977          // Nothing to do here.
978        }
979      }
980    
981      /**
982       * Use serialVersionUID (v1.4) for interoperability.
983       */
984      private static final long serialVersionUID = 8751997116710384592L;
985    
986      /**
987       * Default cascading stylesheed file ("default.css").
988       */
989      public static final String DEFAULT_CSS = "default.css";
990    
991      /**
992       * The <b>bold</b> action identifier.
993       */
994      public static final String BOLD_ACTION = "html-bold-action";
995    
996      /**
997       * The <i>italic</i> action identifier.
998       */
999      public static final String ITALIC_ACTION = "html-italic-action";
1000    
1001      /**
1002       * The <font color="#FF0000">color</font> action indentifier
1003       * (passing the color as an argument).
1004       */
1005      public static final String COLOR_ACTION = "html-color-action";
1006    
1007      /**
1008       * The <font size="+1">increase</font> font action identifier.
1009       */
1010      public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
1011    
1012      /**
1013       * The <font size="-1">decrease</font> font action identifier.
1014       */
1015      public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
1016    
1017      /**
1018       * Align images at the bottom.
1019       */
1020      public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
1021    
1022      /**
1023       * Align images at the middle.
1024       */
1025      public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
1026    
1027      /**
1028       * Align images at the top.
1029       */
1030      public static final String IMG_ALIGN_TOP = "html-image-align-top";
1031    
1032      /**
1033       * Align images at the border.
1034       */
1035      public static final String IMG_BORDER = "html-image-border";
1036    
1037      /**
1038       * The "logical style" action identifier, passing that style as parameter.
1039       */
1040      public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
1041    
1042      /**
1043       * The "ident paragraph left" action.
1044       */
1045      public static final String PARA_INDENT_LEFT = "html-para-indent-left";
1046    
1047      /**
1048       * The "ident paragraph right" action.
1049       */
1050      public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
1051    
1052      /**
1053       * Actions for HTML
1054       */
1055      private static final Action[] defaultActions =
1056      {
1057        new InsertHTMLTextAction("InsertTable",
1058                                 "<table border=1><tr><td></td></tr></table>",
1059                                 HTML.Tag.BODY, HTML.Tag.TABLE),
1060        new InsertHTMLTextAction("InsertTableRow",
1061                                 "<table border=1><tr><td></td></tr></table>",
1062                                 HTML.Tag.TABLE, HTML.Tag.TR,
1063                                 HTML.Tag.BODY, HTML.Tag.TABLE),
1064        new InsertHTMLTextAction("InsertTableCell",
1065                                 "<table border=1><tr><td></td></tr></table>",
1066                                 HTML.Tag.TR, HTML.Tag.TD,
1067                                 HTML.Tag.BODY, HTML.Tag.TABLE),
1068        new InsertHTMLTextAction("InsertUnorderedList",
1069                                 "<ul><li></li></ul>",
1070                                 HTML.Tag.BODY, HTML.Tag.UL),
1071        new InsertHTMLTextAction("InsertUnorderedListItem",
1072                                 "<ul><li></li></ul>",
1073                                 HTML.Tag.UL, HTML.Tag.LI,
1074                                 HTML.Tag.BODY, HTML.Tag.UL),
1075        new InsertHTMLTextAction("InsertOrderedList",
1076                                 "<ol><li></li></ol>",
1077                                 HTML.Tag.BODY, HTML.Tag.OL),
1078        new InsertHTMLTextAction("InsertOrderedListItem",
1079                                 "<ol><li></li></ol>",
1080                                 HTML.Tag.OL, HTML.Tag.LI,
1081                                 HTML.Tag.BODY, HTML.Tag.OL),
1082        new InsertHTMLTextAction("InsertPre",
1083                                 "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE)
1084        // TODO: The reference impl has an InsertHRAction too.
1085      };
1086    
1087      /**
1088       * The current style sheet.
1089       */
1090      private StyleSheet styleSheet;
1091    
1092      /**
1093       * The ViewFactory for HTMLFactory.
1094       */
1095      HTMLFactory viewFactory;
1096    
1097      /**
1098       * The Cursor for links.
1099       */
1100      Cursor linkCursor;
1101    
1102      /**
1103       * The default cursor.
1104       */
1105      Cursor defaultCursor;
1106    
1107      /**
1108       * The parser.
1109       */
1110      Parser parser;
1111    
1112      /**
1113       * The mouse listener used for links.
1114       */
1115      private LinkController linkController;
1116    
1117      /** The content type */
1118      String contentType = "text/html";
1119    
1120      /** The input attributes defined by default.css */
1121      MutableAttributeSet inputAttributes;
1122    
1123      /** The editor pane used. */
1124      JEditorPane editorPane;
1125    
1126      /**
1127       * Whether or not the editor kit handles form submissions.
1128       *
1129       * @see #isAutoFormSubmission()
1130       * @see #setAutoFormSubmission(boolean)
1131       */
1132      private boolean autoFormSubmission;
1133    
1134      /**
1135       * Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet.
1136       */
1137      public HTMLEditorKit()
1138      {
1139        linkController = new LinkController();
1140        autoFormSubmission = true;
1141      }
1142    
1143      /**
1144       * Gets a factory suitable for producing views of any
1145       * models that are produced by this kit.
1146       *
1147       * @return the view factory suitable for producing views.
1148       */
1149      public ViewFactory getViewFactory()
1150      {
1151        if (viewFactory == null)
1152          viewFactory = new HTMLFactory();
1153        return viewFactory;
1154      }
1155    
1156      /**
1157       * Create a text storage model for this type of editor.
1158       *
1159       * @return the model
1160       */
1161      public Document createDefaultDocument()
1162      {
1163        // Protect the shared stylesheet.
1164        StyleSheet styleSheet = getStyleSheet();
1165        StyleSheet ss = new StyleSheet();
1166        ss.addStyleSheet(styleSheet);
1167    
1168        HTMLDocument document = new HTMLDocument(ss);
1169        document.setParser(getParser());
1170        document.setAsynchronousLoadPriority(4);
1171        document.setTokenThreshold(100);
1172        return document;
1173      }
1174    
1175      /**
1176       * Get the parser that this editor kit uses for reading HTML streams. This
1177       * method can be overridden to use the alternative parser.
1178       *
1179       * @return the HTML parser (by default, {@link ParserDelegator}).
1180       */
1181      protected Parser getParser()
1182      {
1183        if (parser == null)
1184          {
1185            parser = new GnuParserDelegator(HTML_401F.getInstance());
1186          }
1187        return parser;
1188      }
1189    
1190      /**
1191       * Inserts HTML into an existing document.
1192       *
1193       * @param doc - the Document to insert the HTML into.
1194       * @param offset - where to begin inserting the HTML.
1195       * @param html - the String to insert
1196       * @param popDepth - the number of ElementSpec.EndTagTypes
1197       * to generate before inserting
1198       * @param pushDepth - the number of ElementSpec.StartTagTypes
1199       * with a direction of ElementSpec.JoinNextDirection that
1200       * should be generated before
1201       * @param insertTag - the first tag to start inserting into document
1202       * @throws IOException - on any I/O error
1203       * @throws BadLocationException - if pos represents an invalid location
1204       * within the document
1205       */
1206      public void insertHTML(HTMLDocument doc, int offset, String html,
1207                             int popDepth, int pushDepth, HTML.Tag insertTag)
1208          throws BadLocationException, IOException
1209      {
1210        Parser parser = getParser();
1211        if (offset < 0 || offset > doc.getLength())
1212          throw new BadLocationException("Bad location", offset);
1213        if (parser == null)
1214          throw new IOException("Parser is null.");
1215    
1216        ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag);
1217    
1218        // FIXME: What should ignoreCharSet be set to?
1219    
1220        // parser.parse inserts html into the buffer
1221        parser.parse(new StringReader(html), pc, false);
1222        pc.flush();
1223      }
1224    
1225      /**
1226       * Inserts content from the given stream. Inserting HTML into a non-empty
1227       * document must be inside the body Element, if you do not insert into
1228       * the body an exception will be thrown. When inserting into a non-empty
1229       * document all tags outside of the body (head, title) will be dropped.
1230       *
1231       * @param in - the stream to read from
1232       * @param doc - the destination for the insertion
1233       * @param pos - the location in the document to place the content
1234       * @throws IOException - on any I/O error
1235       * @throws BadLocationException - if pos represents an invalid location
1236       * within the document
1237       */
1238      public void read(Reader in, Document doc, int pos) throws IOException,
1239          BadLocationException
1240      {
1241        if (doc instanceof HTMLDocument)
1242          {
1243            Parser parser = getParser();
1244            if (pos < 0 || pos > doc.getLength())
1245              throw new BadLocationException("Bad location", pos);
1246            if (parser == null)
1247              throw new IOException("Parser is null.");
1248    
1249            HTMLDocument hd = ((HTMLDocument) doc);
1250            if (editorPane != null)
1251              hd.setBase(editorPane.getPage());
1252            ParserCallback pc = hd.getReader(pos);
1253    
1254            // FIXME: What should ignoreCharSet be set to?
1255    
1256            // parser.parse inserts html into the buffer
1257            parser.parse(in, pc, false);
1258            pc.flush();
1259          }
1260        else
1261          // read in DefaultEditorKit is called.
1262          // the string is inserted in the document as usual.
1263          super.read(in, doc, pos);
1264      }
1265    
1266      /**
1267       * Writes content from a document to the given stream in
1268       * an appropriate format.
1269       *
1270       * @param out - the stream to write to
1271       * @param doc - the source for the write
1272       * @param pos - the location in the document to get the content.
1273       * @param len - the amount to write out
1274       * @throws IOException - on any I/O error
1275       * @throws BadLocationException - if pos represents an invalid location
1276       * within the document
1277       */
1278      public void write(Writer out, Document doc, int pos, int len)
1279          throws IOException, BadLocationException
1280      {
1281        if (doc instanceof HTMLDocument)
1282          {
1283            HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len);
1284            writer.write();
1285          }
1286        else if (doc instanceof StyledDocument)
1287          {
1288            MinimalHTMLWriter writer = new MinimalHTMLWriter(out,
1289                                                             (StyledDocument) doc,
1290                                                             pos, len);
1291            writer.write();
1292          }
1293        else
1294          super.write(out, doc, pos, len);
1295      }
1296    
1297      /**
1298       * Gets the content type that the kit supports.
1299       * This kit supports the type text/html.
1300       *
1301       * @returns the content type supported.
1302       */
1303      public String getContentType()
1304      {
1305        return contentType;
1306      }
1307    
1308      /**
1309       * Creates a copy of the editor kit.
1310       *
1311       * @return a copy of this.
1312       */
1313      public Object clone()
1314      {
1315        // FIXME: Need to clone all fields
1316        HTMLEditorKit copy = (HTMLEditorKit) super.clone();
1317        copy.linkController = new LinkController();
1318        return copy;
1319      }
1320    
1321      /**
1322       * Copies the key/values in elements AttributeSet into set.
1323       * This does not copy component, icon, or element names attributes.
1324       * This is called anytime the caret moves over a different location.
1325       *
1326       * @param element - the element to create the input attributes for.
1327       * @param set - the set to copy the values into.
1328       */
1329      protected void createInputAttributes(Element element,
1330                                           MutableAttributeSet set)
1331      {
1332        set.removeAttributes(set);
1333        set.addAttributes(element.getAttributes());
1334        // FIXME: Not fully implemented.
1335      }
1336    
1337      /**
1338       * Called when this is installed into the JEditorPane.
1339       *
1340       * @param c - the JEditorPane installed into.
1341       */
1342      public void install(JEditorPane c)
1343      {
1344        super.install(c);
1345        c.addMouseListener(linkController);
1346        c.addMouseMotionListener(linkController);
1347        editorPane = c;
1348      }
1349    
1350      /**
1351       * Called when the this is removed from the JEditorPane.
1352       * It unregisters any listeners.
1353       *
1354       * @param c - the JEditorPane being removed from.
1355       */
1356      public void deinstall(JEditorPane c)
1357      {
1358        super.deinstall(c);
1359        c.removeMouseListener(linkController);
1360        c.removeMouseMotionListener(linkController);
1361        editorPane = null;
1362      }
1363    
1364      /**
1365       * Gets the AccessibleContext associated with this.
1366       *
1367       * @return the AccessibleContext for this.
1368       */
1369      public AccessibleContext getAccessibleContext()
1370      {
1371        // FIXME: Should return an instance of
1372        // javax.swing.text.html.AccessibleHTML$RootHTMLAccessibleContext
1373        // Not implemented yet.
1374        return null;
1375      }
1376    
1377      /**
1378       * Gets the action list. This list is supported by the superclass
1379       * augmented by the collection of actions defined locally for style
1380       * operations.
1381       *
1382       * @return an array of all the actions
1383       */
1384      public Action[] getActions()
1385      {
1386        return TextAction.augmentList(super.getActions(), defaultActions);
1387      }
1388    
1389      /**
1390       * Returns the default cursor.
1391       *
1392       * @return the default cursor
1393       */
1394      public Cursor getDefaultCursor()
1395      {
1396        if (defaultCursor == null)
1397          defaultCursor = Cursor.getDefaultCursor();
1398        return defaultCursor;
1399      }
1400    
1401      /**
1402       * Returns the cursor for links.
1403       *
1404       * @return the cursor for links.
1405       */
1406      public Cursor getLinkCursor()
1407      {
1408        if (linkCursor == null)
1409          linkCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
1410        return linkCursor;
1411      }
1412    
1413      /**
1414       * Sets the Cursor for links.
1415       *
1416       * @param cursor - the new cursor for links.
1417       */
1418      public void setLinkCursor(Cursor cursor)
1419      {
1420        linkCursor = cursor;
1421      }
1422    
1423      /**
1424       * Sets the default cursor.
1425       *
1426       * @param cursor - the new default cursor.
1427       */
1428      public void setDefaultCursor(Cursor cursor)
1429      {
1430        defaultCursor = cursor;
1431      }
1432    
1433      /**
1434       * Gets the input attributes used for the styled editing actions.
1435       *
1436       * @return the attribute set
1437       */
1438      public MutableAttributeSet getInputAttributes()
1439      {
1440        return inputAttributes;
1441      }
1442    
1443      /**
1444       * Get the set of styles currently being used to render the HTML elements.
1445       * By default the resource specified by DEFAULT_CSS gets loaded, and is
1446       * shared by all HTMLEditorKit instances.
1447       *
1448       * @return the style sheet.
1449       */
1450      public StyleSheet getStyleSheet()
1451      {
1452        if (styleSheet == null)
1453          {
1454            try
1455              {
1456                styleSheet = new StyleSheet();
1457                Class<?> c = HTMLEditorKit.class;
1458                InputStream in = c.getResourceAsStream(DEFAULT_CSS);
1459                InputStreamReader r = new InputStreamReader(in);
1460                styleSheet.loadRules(r,  null);
1461                r.close();
1462              }
1463            catch (IOException ex)
1464              {
1465                throw new RuntimeException("No style available.", ex);
1466              }
1467          }
1468        return styleSheet;
1469      }
1470    
1471      /**
1472       * Set the set of styles to be used to render the various HTML elements.
1473       * These styles are specified in terms of CSS specifications. Each document
1474       * produced by the kit will have a copy of the sheet which it can add the
1475       * document specific styles to. By default, the StyleSheet specified is shared
1476       * by all HTMLEditorKit instances.
1477       *
1478       * @param s - the new style sheet
1479       */
1480      public void setStyleSheet(StyleSheet s)
1481      {
1482        styleSheet = s;
1483      }
1484    
1485      /**
1486       * Returns <code>true</code> when forms should be automatically submitted
1487       * by the editor kit. Set this to <code>false</code> when you want to
1488       * intercept form submission. In this case you'd want to listen for
1489       * hyperlink events on the document and handle FormSubmitEvents specially.
1490       *
1491       * The default is <code>true</code>.
1492       *
1493       * @return <code>true</code> when forms should be automatically submitted
1494       *         by the editor kit, <code>false</code> otherwise
1495       *
1496       * @since 1.5
1497       *
1498       * @see #setAutoFormSubmission(boolean)
1499       * @see FormSubmitEvent
1500       */
1501      public boolean isAutoFormSubmission()
1502      {
1503        return autoFormSubmission;
1504      }
1505    
1506      /**
1507       * Sets whether or not the editor kit should automatically submit forms.
1508       *
1509       * @param auto <code>true</code> when the editor kit should handle form
1510       *        submission, <code>false</code> otherwise
1511       *
1512       * @since 1.5
1513       *
1514       * @see #isAutoFormSubmission()
1515       */
1516      public void setAutoFormSubmission(boolean auto)
1517      {
1518        autoFormSubmission = auto;
1519      }
1520    }