Source for javax.swing.text.html.HTMLEditorKit

   1: /* HTMLEditorKit.java --
   2:    Copyright (C) 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text.html;
  40: 
  41: 
  42: import java.awt.event.ActionEvent;
  43: import java.awt.event.MouseAdapter;
  44: import java.awt.event.MouseEvent;
  45: import java.awt.event.MouseMotionListener;
  46: import java.awt.Cursor;
  47: import java.awt.Point;
  48: 
  49: import java.io.IOException;
  50: import java.io.InputStream;
  51: import java.io.InputStreamReader;
  52: import java.io.Reader;
  53: import java.io.Serializable;
  54: import java.io.StringReader;
  55: import java.io.Writer;
  56: import java.net.MalformedURLException;
  57: import java.net.URL;
  58: 
  59: import javax.accessibility.Accessible;
  60: import javax.accessibility.AccessibleContext;
  61: 
  62: import javax.swing.Action;
  63: import javax.swing.JEditorPane;
  64: import javax.swing.SwingUtilities;
  65: import javax.swing.event.HyperlinkEvent;
  66: import javax.swing.text.AttributeSet;
  67: import javax.swing.text.BadLocationException;
  68: import javax.swing.text.Document;
  69: import javax.swing.text.EditorKit;
  70: import javax.swing.text.Element;
  71: import javax.swing.text.MutableAttributeSet;
  72: import javax.swing.text.StyleConstants;
  73: import javax.swing.text.StyledDocument;
  74: import javax.swing.text.StyledEditorKit;
  75: import javax.swing.text.TextAction;
  76: import javax.swing.text.View;
  77: import javax.swing.text.ViewFactory;
  78: import javax.swing.text.html.parser.ParserDelegator;
  79: 
  80: /* Move these imports here after javax.swing.text.html to make it compile
  81:    with jikes.  */
  82: import gnu.javax.swing.text.html.parser.GnuParserDelegator;
  83: import gnu.javax.swing.text.html.parser.HTML_401F;
  84: 
  85: /**
  86:  * @author Lillian Angel (langel at redhat dot com)
  87:  */
  88: public class HTMLEditorKit
  89:   extends StyledEditorKit
  90:   implements Serializable, Cloneable, Accessible
  91: {
  92:   
  93:   /**
  94:    * Fires the hyperlink events on the associated component
  95:    * when needed.
  96:    */
  97:   public static class LinkController
  98:     extends MouseAdapter
  99:     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.