JXEditorPane.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:24k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: JXEditorPane.java,v 1.17 2005/10/14 15:37:49 kleopatra Exp $
  3.  *
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx;
  22. import java.awt.Component;
  23. import java.awt.datatransfer.Clipboard;
  24. import java.awt.datatransfer.DataFlavor;
  25. import java.awt.datatransfer.Transferable;
  26. import java.awt.event.ActionEvent;
  27. import java.awt.event.ItemEvent;
  28. import java.awt.event.ItemListener;
  29. import java.beans.PropertyChangeEvent;
  30. import java.beans.PropertyChangeListener;
  31. import java.io.IOException;
  32. import java.io.Reader;
  33. import java.io.StringReader;
  34. import java.net.URL;
  35. import java.util.HashMap;
  36. import java.util.Map;
  37. import java.util.Vector;
  38. import java.util.regex.MatchResult;
  39. import java.util.regex.Matcher;
  40. import java.util.regex.Pattern;
  41. import javax.swing.ActionMap;
  42. import javax.swing.DefaultComboBoxModel;
  43. import javax.swing.DefaultListCellRenderer;
  44. import javax.swing.JComboBox;
  45. import javax.swing.JComponent;
  46. import javax.swing.JEditorPane;
  47. import javax.swing.JList;
  48. import javax.swing.KeyStroke;
  49. import javax.swing.SwingUtilities;
  50. import javax.swing.event.CaretEvent;
  51. import javax.swing.event.CaretListener;
  52. import javax.swing.event.UndoableEditEvent;
  53. import javax.swing.event.UndoableEditListener;
  54. import javax.swing.text.AttributeSet;
  55. import javax.swing.text.BadLocationException;
  56. import javax.swing.text.Document;
  57. import javax.swing.text.EditorKit;
  58. import javax.swing.text.Element;
  59. import javax.swing.text.MutableAttributeSet;
  60. import javax.swing.text.Segment;
  61. import javax.swing.text.SimpleAttributeSet;
  62. import javax.swing.text.StyleConstants;
  63. import javax.swing.text.StyledDocument;
  64. import javax.swing.text.StyledEditorKit;
  65. import javax.swing.text.html.HTML;
  66. import javax.swing.text.html.HTMLDocument;
  67. import javax.swing.text.html.HTMLEditorKit;
  68. import javax.swing.undo.CannotRedoException;
  69. import javax.swing.undo.CannotUndoException;
  70. import javax.swing.undo.UndoManager;
  71. import org.jdesktop.swingx.action.ActionManager;
  72. import org.jdesktop.swingx.action.Targetable;
  73. /**
  74.  * An extended editor pane which has the following features built in:
  75.  * <ul>
  76.  *   <li>Text search
  77.  *   <li>undo/redo
  78.  *   <li>simple html/plain text editing
  79.  * </ul>
  80.  *
  81.  * @author Mark Davidson
  82.  */
  83. public class JXEditorPane extends JEditorPane implements /*Searchable, */Targetable {
  84. //    private Matcher matcher;
  85.     private UndoableEditListener undoHandler;
  86.     private UndoManager undoManager;
  87.     private CaretListener caretHandler;
  88.     private JComboBox selector;
  89.     // The ids of supported actions. Perhaps this should be public.
  90.     private final static String ACTION_FIND = "find";
  91.     private final static String ACTION_UNDO = "undo";
  92.     private final static String ACTION_REDO = "redo";
  93.     /*
  94.      * These next 3 actions are part of a *HACK* to get cut/copy/paste
  95.      * support working in the same way as find, undo and redo. in JTextComponent
  96.      * the cut/copy/paste actions are _not_ added to the ActionMap. Instead,
  97.      * a default "transfer handler" system is used, apparently to get the text
  98.      * onto the system clipboard.
  99.      * Since there aren't any CUT/COPY/PASTE actions in the JTextComponent's action
  100.      * map, they cannot be referenced by the action framework the same way that
  101.      * find/undo/redo are. So, I added the actions here. The really hacky part
  102.      * is that by defining an Action to go along with the cut/copy/paste keys,
  103.      * I loose the default handling in the cut/copy/paste routines. So, I have
  104.      * to remove cut/copy/paste from the action map, call the appropriate 
  105.      * method (cut, copy, or paste) and then add the action back into the
  106.      * map. Yuck!
  107.      */
  108.     private final static String ACTION_CUT = "cut";
  109.     private final static String ACTION_COPY = "copy";
  110.     private final static String ACTION_PASTE = "paste";
  111.     private TargetableSupport targetSupport = new TargetableSupport(this);
  112.     private Searchable searchable;
  113.     
  114.     public JXEditorPane() {
  115.         init();
  116.     }
  117.     public JXEditorPane(String url) throws IOException {
  118.         super(url);
  119.         init();
  120.     }
  121.     public JXEditorPane(String type, String text) {
  122.         super(type, text);
  123.         init();
  124.     }
  125.     public JXEditorPane(URL initialPage) throws IOException {
  126.         super(initialPage);
  127.         init();
  128.     }
  129.     private void init() {
  130.         setEditorKitForContentType("text/html", new SloppyHTMLEditorKit());
  131.         addPropertyChangeListener(new PropertyHandler());
  132.         getDocument().addUndoableEditListener(getUndoableEditListener());
  133.         initActions();
  134.     }
  135.     private class PropertyHandler implements PropertyChangeListener {
  136.         public void propertyChange(PropertyChangeEvent evt) {
  137.             String name = evt.getPropertyName();
  138.             if (name.equals("document")) {
  139.                 Document doc = (Document)evt.getOldValue();
  140.                 if (doc != null) {
  141.                     doc.removeUndoableEditListener(getUndoableEditListener());
  142.                 }
  143.                 doc = (Document)evt.getNewValue();
  144.                 if (doc != null) {
  145.                     doc.addUndoableEditListener(getUndoableEditListener());
  146.                 }
  147.             }
  148.         }
  149.     }
  150.     // pp for testing
  151.     CaretListener getCaretListener() {
  152.         return caretHandler;
  153.     }
  154.     // pp for testing
  155.     UndoableEditListener getUndoableEditListener() {
  156.         if (undoHandler == null) {
  157.             undoHandler = new UndoHandler();
  158.             undoManager = new UndoManager();
  159.         }
  160.         return undoHandler;
  161.     }
  162.     /**
  163.      * Overidden to perform document initialization based on type.
  164.      */
  165.     public void setEditorKit(EditorKit kit) {
  166.         super.setEditorKit(kit);
  167.         if (kit instanceof StyledEditorKit) {
  168.             if (caretHandler == null) {
  169.                 caretHandler = new CaretHandler();
  170.             }
  171.             addCaretListener(caretHandler);
  172.         }
  173.     }
  174.     /**
  175.      * Register the actions that this class can handle.
  176.      */
  177.     protected void initActions() {
  178.         ActionMap map = getActionMap();
  179.         map.put(ACTION_FIND, new Actions(ACTION_FIND));
  180.         map.put(ACTION_UNDO, new Actions(ACTION_UNDO));
  181.         map.put(ACTION_REDO, new Actions(ACTION_REDO));
  182.         map.put(ACTION_CUT, new Actions(ACTION_CUT));
  183.         map.put(ACTION_COPY, new Actions(ACTION_COPY));
  184.         map.put(ACTION_PASTE, new Actions(ACTION_PASTE));
  185.         // this should be handled by the LF!
  186.         KeyStroke findStroke = KeyStroke.getKeyStroke("control F");
  187.         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
  188.     }
  189.     // undo/redo implementation
  190.     private class UndoHandler implements UndoableEditListener {
  191.         public void undoableEditHappened(UndoableEditEvent evt) {
  192.             undoManager.addEdit(evt.getEdit());
  193.             updateActionState();
  194.         }
  195.     }
  196.     /**
  197.      * Updates the state of the actions in response to an undo/redo operation.
  198.      */
  199.     private void updateActionState() {
  200.         // Update the state of the undo and redo actions
  201.         Runnable doEnabled = new Runnable() {
  202.                 public void run() {
  203.                     ActionManager manager = ActionManager.getInstance();
  204.                     manager.setEnabled(ACTION_UNDO, undoManager.canUndo());
  205.                     manager.setEnabled(ACTION_REDO, undoManager.canRedo());
  206.                 }
  207.             };
  208.         SwingUtilities.invokeLater(doEnabled);
  209.     }
  210.     /**
  211.      * A small class which dispatches actions.
  212.      * TODO: Is there a way that we can make this static?
  213.      */
  214.     private class Actions extends UIAction {
  215.         Actions(String name) {
  216.             super(name);
  217.         }
  218.         public void actionPerformed(ActionEvent evt) {
  219.             String name = getName();
  220.             if (ACTION_FIND.equals(name)) {
  221.                 find();
  222.             }
  223.             else if (ACTION_UNDO.equals(name)) {
  224.                 try {
  225.                     undoManager.undo();
  226.                 } catch (CannotUndoException ex) {
  227.                     ex.printStackTrace();
  228.                 }
  229.                 updateActionState();
  230.             }
  231.             else if (ACTION_REDO.equals(name)) {
  232.                 try {
  233.                     undoManager.redo();
  234.                 } catch (CannotRedoException ex) {
  235.                     ex.printStackTrace();
  236.                 }
  237.                 updateActionState();
  238.             } else if (ACTION_CUT.equals(name)) {
  239.                 ActionMap map = getActionMap();
  240.                 map.remove(ACTION_CUT);
  241.                 cut();
  242.                 map.put(ACTION_CUT, this);
  243.             } else if (ACTION_COPY.equals(name)) {
  244.                 ActionMap map = getActionMap();
  245.                 map.remove(ACTION_COPY);
  246.                 copy();
  247.                 map.put(ACTION_COPY, this);
  248.             } else if (ACTION_PASTE.equals(name)) {
  249.                 ActionMap map = getActionMap();
  250.                 map.remove(ACTION_PASTE);
  251.                 paste();
  252.                 map.put(ACTION_PASTE, this);
  253.             }
  254.             else {
  255.                 System.out.println("ActionHandled: " + name);
  256.             }
  257.         }
  258.     }
  259.     /**
  260.      * Retrieves a component which will be used as the paragraph selector.
  261.      * This can be placed in the toolbar.
  262.      * <p>
  263.      * Note: This is only valid for the HTMLEditorKit
  264.      */
  265.     public JComboBox getParagraphSelector() {
  266.         if (selector == null) {
  267.             selector = new ParagraphSelector();
  268.         }
  269.         return selector;
  270.     }
  271.     /**
  272.      * A control which should be placed in the toolbar to enable
  273.      * paragraph selection.
  274.      */
  275.     private class ParagraphSelector extends JComboBox implements ItemListener {
  276.         private Map itemMap;
  277.         public ParagraphSelector() {
  278.             // The item map is for rendering
  279.             itemMap = new HashMap();
  280.             itemMap.put(HTML.Tag.P, "Paragraph");
  281.             itemMap.put(HTML.Tag.H1, "Heading 1");
  282.             itemMap.put(HTML.Tag.H2, "Heading 2");
  283.             itemMap.put(HTML.Tag.H3, "Heading 3");
  284.             itemMap.put(HTML.Tag.H4, "Heading 4");
  285.             itemMap.put(HTML.Tag.H5, "Heading 5");
  286.             itemMap.put(HTML.Tag.H6, "Heading 6");
  287.             itemMap.put(HTML.Tag.PRE, "Preformatted");
  288.             // The list of items
  289.             Vector items = new Vector();
  290.             items.addElement(HTML.Tag.P);
  291.             items.addElement(HTML.Tag.H1);
  292.             items.addElement(HTML.Tag.H2);
  293.             items.addElement(HTML.Tag.H3);
  294.             items.addElement(HTML.Tag.H4);
  295.             items.addElement(HTML.Tag.H5);
  296.             items.addElement(HTML.Tag.H6);
  297.             items.addElement(HTML.Tag.PRE);
  298.             setModel(new DefaultComboBoxModel(items));
  299.             setRenderer(new ParagraphRenderer());
  300.             addItemListener(this);
  301.             setFocusable(false);
  302.         }
  303.         public void itemStateChanged(ItemEvent evt) {
  304.             if (evt.getStateChange() == ItemEvent.SELECTED) {
  305.                 applyTag((HTML.Tag)evt.getItem());
  306.             }
  307.         }
  308.         private class ParagraphRenderer extends DefaultListCellRenderer {
  309.             public ParagraphRenderer() {
  310.                 setOpaque(true);
  311.             }
  312.             public Component getListCellRendererComponent(JList list,
  313.                                                           Object value,
  314.                                                           int index,
  315.                                                           boolean isSelected,
  316.                                                           boolean cellHasFocus) {
  317.                 super.getListCellRendererComponent(list, value, index, isSelected,
  318.                                                    cellHasFocus);
  319.                 setText((String)itemMap.get(value));
  320.                 return this;
  321.             }
  322.         }
  323.         // TODO: Should have a rendererer which does stuff like:
  324.         // Paragraph, Heading 1, etc...
  325.     }
  326.     /**
  327.      * Applys the tag to the current selection
  328.      */
  329.     protected void applyTag(HTML.Tag tag) {
  330.         Document doc = getDocument();
  331.         if (!(doc instanceof HTMLDocument)) {
  332.             return;
  333.         }
  334.         HTMLDocument hdoc = (HTMLDocument)doc;
  335.         int start = getSelectionStart();
  336.         int end = getSelectionEnd();
  337.         Element element = hdoc.getParagraphElement(start);
  338.         MutableAttributeSet newAttrs = new SimpleAttributeSet(element.getAttributes());
  339.         newAttrs.addAttribute(StyleConstants.NameAttribute, tag);
  340.         hdoc.setParagraphAttributes(start, end - start, newAttrs, true);
  341.     }
  342.     /**
  343.      * The paste method has been overloaded to strip off the <html><body> tags
  344.      * This doesn't really work.
  345.      */
  346.     public void paste() {
  347.         Clipboard clipboard = getToolkit().getSystemClipboard();
  348.         Transferable content = clipboard.getContents(this);
  349.         if (content != null) {
  350.             DataFlavor[] flavors = content.getTransferDataFlavors();
  351.             try {
  352.                 for (int i = 0; i < flavors.length; i++) {
  353.                     if (String.class.equals(flavors[i].getRepresentationClass())) {
  354.                         Object data = content.getTransferData(flavors[i]);
  355.                         if (flavors[i].isMimeTypeEqual("text/plain")) {
  356.                             // This works but we lose all the formatting.
  357.                             replaceSelection(data.toString());
  358.                             break;
  359.                         } 
  360.                     }
  361.                 }
  362.             } catch (Exception ex) {
  363.                 ex.printStackTrace();
  364.             }
  365.         }
  366.     }
  367.     private void find() {
  368.         SearchFactory.getInstance().showFindInput(this, getSearchable());
  369.     }
  370.     /**
  371.      * 
  372.      * @returns a not-null Searchable for this editor.  
  373.      */
  374.     public Searchable getSearchable() {
  375.         if (searchable == null) {
  376.             searchable = new DocumentSearchable();
  377.         }
  378.         return searchable;
  379.     }
  380.     /**
  381.      * sets the Searchable for this editor. If null, a default 
  382.      * searchable will be used.
  383.      * 
  384.      * @param searchable
  385.      */
  386.     public void setSearchable(Searchable searchable) {
  387.         this.searchable = searchable;
  388.     }
  389.     
  390.     public class DocumentSearchable implements Searchable {
  391.         public int search(String searchString) {
  392.             return search(searchString, -1);
  393.         }
  394.         public int search(String searchString, int columnIndex) {
  395.             return search(searchString, columnIndex, false);
  396.         }
  397.         
  398.         public int search(String searchString, int columnIndex, boolean backward) {
  399.             Pattern pattern = null;
  400.             if (!isEmpty(searchString)) {
  401.                 pattern = Pattern.compile(searchString, 0);
  402.             }
  403.             return search(pattern, columnIndex, backward);
  404.         }
  405.         /**
  406.          * checks if the searchString should be interpreted as empty.
  407.          * here: returns true if string is null or has zero length.
  408.          * 
  409.          * @param searchString
  410.          * @return
  411.          */
  412.         protected boolean isEmpty(String searchString) {
  413.             return (searchString == null) || searchString.length() == 0;
  414.         }
  415.         public int search(Pattern pattern) {
  416.             return search(pattern, -1);
  417.         }
  418.         public int search(Pattern pattern, int startIndex) {
  419.             return search(pattern, startIndex, false);
  420.         }
  421.         int lastFoundIndex = -1;
  422.         MatchResult lastMatchResult;
  423.         String lastRegEx;
  424.         /**
  425.          * @return start position of matching string or -1
  426.          */
  427.         public int search(Pattern pattern, final int startIndex,
  428.                 boolean backwards) {
  429.             if ((pattern == null)
  430.                     || (getDocument().getLength() == 0)
  431.                     || ((startIndex > -1) && (getDocument().getLength() < startIndex))) {
  432.                 updateStateAfterNotFound();
  433.                 return -1;
  434.             }
  435.             int start = startIndex;
  436.             if (maybeExtendedMatch(startIndex)) {
  437.                 if (foundExtendedMatch(pattern, start)) {
  438.                     return lastFoundIndex;
  439.                 }
  440.                 start++;
  441.             }
  442.             int length;
  443.             if (backwards) {
  444.                 start = 0;
  445.                 if (startIndex < 0) {
  446.                     length = getDocument().getLength() - 1;
  447.                 } else {
  448.                     length = -1 + startIndex;
  449.                 }
  450.             } else {
  451.                 // start = startIndex + 1;
  452.                 if (start < 0)
  453.                     start = 0;
  454.                 length = getDocument().getLength() - start;
  455.             }
  456.             Segment segment = new Segment();
  457.             try {
  458.                 getDocument().getText(start, length, segment);
  459.             } catch (Exception ex) {
  460.                 ex.printStackTrace();
  461.             }
  462.             Matcher matcher = pattern.matcher(segment.toString());
  463.             MatchResult currentResult = getMatchResult(matcher, !backwards);
  464.             if (currentResult != null) {
  465.                 updateStateAfterFound(currentResult, start);
  466.             } else {
  467.                 updateStateAfterNotFound();
  468.             }
  469.             return lastFoundIndex;
  470.         }
  471.         /**
  472.          * Search from same startIndex as the previous search. 
  473.          * Checks if the match is different from the last (either 
  474.          * extended/reduced) at the same position. Returns true
  475.          * if the current match result represents a different match 
  476.          * than the last, false if no match or the same.
  477.          * 
  478.          * @param pattern
  479.          * @param start
  480.          * @return
  481.          */
  482.         private boolean foundExtendedMatch(Pattern pattern, int start) {
  483.             // JW: logic still needs cleanup...
  484.             if (pattern.pattern().equals(lastRegEx)) {
  485.                 return false;
  486.             }
  487.             int length = getDocument().getLength() - start;
  488.             Segment segment = new Segment();
  489.             try {
  490.                 getDocument().getText(start, length, segment);
  491.             } catch (Exception ex) {
  492.                 ex.printStackTrace();
  493.             }
  494.             Matcher matcher = pattern.matcher(segment.toString());
  495.             MatchResult currentResult = getMatchResult(matcher, true);
  496.             if (currentResult != null) {
  497.                 // JW: how to compare match results reliably?
  498.                 // the group().equals probably isn't the best idea...
  499.                 // better check pattern?
  500.                 if ((currentResult.start() == 0) && 
  501.                    (!lastMatchResult.group().equals(currentResult.group()))) {
  502.                     updateStateAfterFound(currentResult, start);
  503.                     return true;
  504.                 } 
  505.             }
  506.             return false;
  507.         }
  508.         /**
  509.          * Checks if the startIndex is a candidate for trying a re-match.
  510.          * 
  511.          * 
  512.          * @param startIndex
  513.          * @return true if the startIndex should be re-matched, false if not.
  514.          */
  515.         private boolean maybeExtendedMatch(final int startIndex) {
  516.             return (startIndex >= 0) && (startIndex == lastFoundIndex);
  517.         }
  518.         /**
  519.          * @param currentResult
  520.          * @param offset
  521.          * @return
  522.          */
  523.         private int updateStateAfterFound(MatchResult currentResult, final int offset) {
  524.             int end = currentResult.end() + offset;
  525.             int found = currentResult.start() + offset; 
  526.             select(found, end);
  527.             getCaret().setSelectionVisible(true);
  528.             lastFoundIndex = found;
  529.             lastMatchResult = currentResult;
  530.             lastRegEx = ((Matcher) lastMatchResult).pattern().pattern();
  531.             return found;
  532.         }
  533.         /**
  534.          * @param matcher
  535.          * @return
  536.          */
  537.         private MatchResult getMatchResult(Matcher matcher, boolean  useFirst) {
  538.             MatchResult currentResult = null;
  539.             while (matcher.find()) {
  540.                 currentResult = matcher.toMatchResult();
  541.                 if (useFirst) break;
  542.             }
  543.             return currentResult;
  544.         }
  545.         /**
  546.          */
  547.         private void updateStateAfterNotFound() {
  548.             lastFoundIndex = -1;
  549.             lastMatchResult = null;
  550.             lastRegEx = null;
  551.             setCaretPosition(getSelectionEnd());
  552.         }
  553.     }
  554.     
  555.     public boolean hasCommand(Object command) {
  556.         return targetSupport.hasCommand(command);
  557.     }
  558.     public Object[] getCommands() {
  559.         return targetSupport.getCommands();
  560.     }
  561.     public boolean doCommand(Object command, Object value) {
  562.         return targetSupport.doCommand(command, value);
  563.     }
  564.     /**
  565.      * Listens to the caret placement and adjusts the editing
  566.      * properties as appropriate.
  567.      *
  568.      * Should add more attributes as required.
  569.      */
  570.     private class CaretHandler implements CaretListener {
  571.         public void caretUpdate(CaretEvent evt) {
  572.             StyledDocument document = (StyledDocument)getDocument();
  573.             int dot = evt.getDot();
  574.             Element elem = document.getCharacterElement(dot);
  575.             AttributeSet set = elem.getAttributes();
  576.             ActionManager manager = ActionManager.getInstance();
  577.             manager.setSelected("font-bold", StyleConstants.isBold(set));
  578.             manager.setSelected("font-italic", StyleConstants.isItalic(set));
  579.             manager.setSelected("font-underline", StyleConstants.isUnderline(set));
  580.             elem = document.getParagraphElement(dot);
  581.             set = elem.getAttributes();
  582.             // Update the paragraph selector if applicable.
  583.             if (selector != null) {
  584.                 selector.setSelectedItem(set.getAttribute(StyleConstants.NameAttribute));
  585.             }
  586.             switch (StyleConstants.getAlignment(set)) {
  587.                 // XXX There is a bug here. the setSelected method
  588.                 // should only affect the UI actions rather than propagate
  589.                 // down into the action map actions.
  590.             case StyleConstants.ALIGN_LEFT:
  591.                 manager.setSelected("left-justify", true);
  592.                 break;
  593.             case StyleConstants.ALIGN_CENTER:
  594.                 manager.setSelected("center-justify", true);
  595.                 break;
  596.             case StyleConstants.ALIGN_RIGHT:
  597.                 manager.setSelected("right-justify", true);
  598.                 break;
  599.             }
  600.         }
  601.     }
  602.     
  603.     /**
  604.      * Handles sloppy HTML. This implementation currently only looks for
  605.      * tags that have a / at the end (self-closing tags) and fixes them
  606.      * to work with the version of HTML supported by HTMLEditorKit
  607.      * <p>TODO: Need to break this functionality out so it can take pluggable
  608.      * replacement code blocks, allowing people to write custom replacement
  609.      * routines. The idea is that with some simple modifications a lot more
  610.      * sloppy HTML can be rendered correctly.
  611.      *
  612.      * @author rbair
  613.      */
  614.     private static final class SloppyHTMLEditorKit extends HTMLEditorKit {
  615.         public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
  616.             //read the reader into a String
  617.             StringBuffer buffer = new StringBuffer();
  618.             int length = -1;
  619.             char[] data = new char[1024];
  620.             while ((length = in.read(data)) != -1) {
  621.                 buffer.append(data, 0, length);
  622.             }
  623.             //TODO is this regex right?
  624.             StringReader reader = new StringReader(buffer.toString().replaceAll("/>", ">"));
  625.             super.read(reader, doc, pos);
  626.         }
  627.     }    
  628. }