001/* HTMLEditorKit.java --
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text.html;
040
041
042import java.awt.event.ActionEvent;
043import java.awt.event.MouseAdapter;
044import java.awt.event.MouseEvent;
045import java.awt.event.MouseMotionListener;
046import java.awt.Cursor;
047import java.awt.Point;
048
049import java.io.IOException;
050import java.io.InputStream;
051import java.io.InputStreamReader;
052import java.io.Reader;
053import java.io.Serializable;
054import java.io.StringReader;
055import java.io.Writer;
056import java.net.MalformedURLException;
057import java.net.URL;
058
059import javax.accessibility.Accessible;
060import javax.accessibility.AccessibleContext;
061
062import javax.swing.Action;
063import javax.swing.JEditorPane;
064import javax.swing.SwingUtilities;
065import javax.swing.event.HyperlinkEvent;
066import javax.swing.text.AttributeSet;
067import javax.swing.text.BadLocationException;
068import javax.swing.text.Document;
069import javax.swing.text.EditorKit;
070import javax.swing.text.Element;
071import javax.swing.text.MutableAttributeSet;
072import javax.swing.text.StyleConstants;
073import javax.swing.text.StyledDocument;
074import javax.swing.text.StyledEditorKit;
075import javax.swing.text.TextAction;
076import javax.swing.text.View;
077import javax.swing.text.ViewFactory;
078import javax.swing.text.html.parser.ParserDelegator;
079
080/* Move these imports here after javax.swing.text.html to make it compile
081   with jikes.  */
082import gnu.javax.swing.text.html.parser.GnuParserDelegator;
083import gnu.javax.swing.text.html.parser.HTML_401F;
084
085/**
086 * @author Lillian Angel (langel at redhat dot com)
087 */
088public 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}