📄 htmlpane.java
字号:
// (c) 2003 Allen I Holub. All rights reserved.
package com.holub.ui.HTML;
import com.hexidec.ekit.component.ExtendedHTMLDocument;
import com.hexidec.ekit.component.RelativeImageView;
import com.holub.net.UrlUtil;
import com.holub.tools.Log;
import com.holub.ui.AncestorAdapter;
import com.holub.ui.HTML.TagBehavior;
import com.holub.ui.HTML.FilterFactory;
import java.io.*;
import java.util.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import java.util.logging.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.text.*; // View Element ViewFactory
import javax.swing.text.html.*;
import javax.swing.event.*;
import javax.swing.border.*;
// See full documentation for this class in last-month's article
/*** ****************************************************************
* This class let's you build an HTML-based interface with JSP-like
* functionality that operates entirely within the confines of a
* client-side program---no web server is necessary.
* <code>HTMLPane</code> also fixes several problems in {@link JEditorPane}'s
* HTML support, but it is based on {@link JEditorPane}, so has many of its
* limitations.
* <p>
* <code>HTMLPane</code> augments {@link JEditorPane} in several ways:
* <ul>
* <li>An <code>HTMLPane</code> logs errors to the com.holub.ui logger
* rather than throwing exceptions in many situations. You can hook
* into these messages by creating a logger (described below).
* <li>I've improved the appearance of form elements a bit:
* <ul>
* <li>Radio buttons and text-input fields are now aligned properly
* with the surrounding text
* <li>The list created by a <code><select></code>
* element is now only a little wider than the contained text
* (instead of taking up the full screen).
* <li>Radio buttons and check boxes have transparent backgrounds so that
* they don't look weird against background textures and
* colors that aren't dark grey (<code>bgcolor="d0d0d0"</code>).
* </ul>
* <li>You can add {@linkplain #addTag custom tags} and handlers for them,
* so you add JSP-style behavior to your forms.
* <li>You can specify a local form-data handler that's invoked in
* response to both submit-style and cancel-style operations. That is,
* clicking a "Submit" button causes a chuck of code (that you provide)
* to execute rather than sending the form data to a remote server.
* <li>
* HTTP hyperlinks are supported, but note that the JDK 1.4 runtime
* does not support the format: "file://c:/file."
* You have to use "file:/c:/file" (one slash).
* Strictly speaking, the 1.4 behavior is actually "correct," but some
* the rules for forming a "correct" URL are not well known, and the
* lack of a support for a double slash might be frustrating.
* <li>
* Mailto: hyperlinks are supported, but only on the Windows platform.
* Subject lines have to be properly URL encoded, (with %20 used for spaces,
* etc.). For example:
* <PRE>
* <a href="mailto:allen@holub.com?subject=foo%20bar">
* </PRE>
* Attempts to use mailto on other platforms result in a
* logged warning, but are otherwise silently absorbed.
* <li>
* The <code><a target=_blank ...></code> attribute now correctly
* causes the page to pop up in its own window. The window is a
* <code>pack()</code>ed frame that holds an HTMLPane, so your custom-tag
* handlers will work in the popup.
* <li>
* Standard http: and file: hyperlinks and relative URLs are handled
* internally, but only those hyperlinks that reference files that end
* in certain {@linkplain #redirect(URL) file
* extensions} are processed.
* Other protocols (e.g. ftp:) are not supported.
* <li>
* <code>HTMLPane</code> supports a
* {@linkplain #addHostMapping(String,String) host-mapping feature}
* that let's you specify maps between the "host" part of a URL and an
* arbitrary map-to URL. All hyperlinks aimed at the host will end up
* at the map-to location. This way you can map network URLs to
* file:// URLs for testing or non-network use.
* </ul>
* <p>
* <font size=+1><b>Logging</b></font>
* </p>
* The <code>HTMLPanel</code> logs errors and warnings to the
* com.holub.ui logger.
* To view the messages from all programs that use <code>HTMLPanel</code>,
* modify the .../jre/lib/logging.properties file,
* setting the <i>.level</i> and
* <i>java.util.logging.ConsoleHandler.level</i>
* properties to <i>ALL</i>. You can also
* put the following code into your program to turn on logging for
* that program only:
* <PRE>
* static
* { Logger log = Logger.getLogger("com.holub.ui");
* Handler h = new ConsoleHandler();
* h. setLevel(Level.ALL);
* log.setLevel(Level.ALL);
* log.addHandler(h);
* }
* </PRE>
* You can turn logging off by specifying Level.OFF instead of ALL.
* The {@link com.holub.tools.Log} class provides convenience
* methods for controlling logging.
* <p>
* <a name="customTags">
* <font size=+1><b>Custom Tags</b></font>
* </a>
* <p>
* You can define custom tags that can be used in your .html input,
* in a manner similar to a custom JSP tag. Set up a new tab by calling
* {@link #addTag addTag(...)}, passing it
* an object that identifies a {@linkplain TagHandler tag handler}
* that's activated when the tag is encountered.
* (See {@link #addTag addTag(...)} for more information.)
* <p>
* Because java's {@link
* javax.swing.JEditorPane}
* underlies the current class, you can't define a namespace-style
* custom tag: a name like
* <code><holub:myTag ...></code> (that contains a colon)
* isn't recognized. You can use underscores, but that's a kludge.
* <p>
* Also, the current implementation of custom tags does not permit the
* element to have content. The problem is that the default parser
* does not pair non-HTML tags. That is, <code><foo></code>
* and <code></foo></code> are treated as completely
* independent tags with no connection between them. The only
* solution is to effectively rewrite the parser, but that's
* a lot of work for not much gain. Future versions of this
* class might permit content, however.
* <p> Several pre-built custom tags are provided. Add support for
* these by issuing one of the following calls to you <code>HTMLPane</code>:
* <PRE>
* pane.{@link #addTag addTag}( "size" , new {@link SizeHandler SizeHandler}() );
* pane.{@link #addTag addTag}( "inputAction", new {@link InputActionHandler InputActionHandler}(this));
* pane.{@link #addTag addTag}( "inputNumber", new {@link InputNumberHandler InputNumberHandler}() );
* pane.{@link #addTag addTag}( "inputDate" , new {@link InputDateHandler InputDateHandler}() );
* </PRE>
* (These tags are all installed for you if you use the
* {@link #HTMLPane(boolean)} constructor).
*
* <PRE>
<size height=400 width=400>
* </PRE>
* <p style="padding-left:2em">
* Specifies the size of the pane in pixels.
* </p>
* <PRE>
* <a name="inputAction">
<inputAction name="myName" value="text">
* </PRE>
* <p style="padding-left:2em">
* This tag inserts a submit-style button that causes actionPerformed
* messages to be sent to all registered action listeners (as if the
* submit button had been pressed). The method and action attributes
* of the passed FormActionEvent will be empty (not null) Strings, and the
* data Properties object holds the pair <code>name=<em>XXX</em></code>,
* where <em>name</em> was specified using the <em>name=xxx</em> attribute in
* the original tag, and <em>XXX</em> holds the value <em>true</em> if the button
* was pressed, <em>false</em> if it wasn't.
* Key=value pairs supplied by custom
* tag handlers [added using {@link #addTag addTag(...)}] are also available, but
* the data supplied from standard HTML tags
* (<em><input></em>, <em><textarea></em>, and <em><select></em>)
* <span style="text-decoration:underline;">is not available</span>.
* The value attribute also defines the text
* on the button face. This tag is here to make
* it easy to implement a "cancel" or "quit" operation,
* but you can actually use it as a generic button if don't need the
* form data from the
* <em><input></em>, <em><textarea></em>, or <em><select></em>
* tags.
* </p>
* </a>
* <PRE>
<inputNumber name="fred" value="0.0" min="0" max="100" precision="2" size=<em>n</em>>
* </PRE>
* <p style="padding-left:2em">
* Like an <input type=text> tag, but inputs a numeric value
* in the range <em>min</em> ≤ <em>N</em> ≤ <em>max</em>, with up to <em>precision</em> digits
* permitted to the right of the decimal point (<em>precision can be zero</em>).
* The optional <em>size</em> attribute is the width of the field in columns.
* </p>
* <PRE>
<inputDate name="fred" value="10/15/03" size=<em>n</em>>
* </PRE>
* <p style="padding-left:2em">
* A localized date-input field that does data validation when it looses focus or you hit Enter.
* Most common date formats are recognized, and a popup dialog lets you choose dates from
* a calendar.
* The initial value, if present, must specify a date. (An empty string isn't permitted.)
* If no value= attribute is specified, today's date is used as the initial value.
* The optional <em>size</em> attribute is the width of the field in columns.
* (An approximation is used to size the control.)
* </p>
*
* <p>
* <font size=+1><b>Form Processing</b></font>
* <p>
* Normally, when an HTML form is submitted, the JEditorPane tries to
* actually execute and HTTP POST or GET operation on a remote server,
* passing it the data associated with the form elements as name=value
* pairs.
* <p>
* You can modify form-processing behavior, so that a form is submitted
* to the current program rather than to some server out on the net somewhere.
* Just add an {@link java.awt.event.ActionListener} to the HTMLPane by
* calling {@link #addActionListener addActionListener(...)}. The
* {@link java.awt.event.ActionListener#actionPerformed} method of the listener
* is called when the user hits the submit button. The associated
* <code>ActionEvent</code> object is actually an instance of the
* {@link FormActionEvent} class, and you can get the submit
* data from it.
* You may add as many action listeners as you like (they are all passed
* same event object). This way, the handlers can
* examine the <code>action=</code> attribute of the <code><form></code>
* tag and process data only if the associated URL is of interest.
* Once you've added any form handlers,
* <u>all</u> form submissions will go to them rather than being posted to the
* target URL. Your handler can relay the data to the web if it likes, but
* it must do so if you want that behavior.
* <p>
* Here's an example of a simple form-submission handler that just prints
* all the form-related information on standard output.
* <PRE>
* pane.addActionListener
* ( new ActionListener()
* { public void actionPerformed( ActionEvent event )
* {
* FormActionEvent act = (FormActionEvent)event;
*
* System.out.println("\n"+ act.getActionCommand() );
* System.out.println("\t"+"method=" + act.method() );
* System.out.println("\t"+"action=" + act.action() );
* act.data().list( System.out );
* System.out.println("");
* try
* { act.source().setPage // display a "success" page
* ( new URL(
* "file://c:/src/com/holub/ui/HTML/test/submit.html")
* );
* }
* catch( Exception e )
* { e.printStackTrace();
* }
* System.out.println("");
* }
* }
* );
* </PRE>
* <p>
* <font size=+1><b>Known Problems</b></font>
* <p>
* This class is based on {@link JEditorPane}, which does not
* use the world's best HTML parser. The following problems that are caused
* by Sun's parser aren't fixed in the current implementation:
* <ol>
* <li>The parser is dog slow.
* <li>CSS support is seriously broken. Styles are useless.
* <li>None of the new HTML 4 or XHTML tags are handled.
* <li>The parser doesn't handle tables very well.
* Very simple table nesting is okay, but complicated stuff fails
* miserably.
* <li><code><applet></code> tags are not supported---they are
* actually obsolescent as of HTML 4---but <code><object></code>
* tags are supported.
* Use the latter to embed your applets in a <code><form></code>.
* See <a href="http://java.sun.com/j2se/1.4/docs/guide/plugin/developerGuide/usingTags.html">
* the Java-plugin docs</a> for a discussion of the correct way to do this.
* <li>The JDK 1.4 parser parser does not handle
* <code><input type=submit name=x value=y></code>
* correctly (The problem is fixed in JDK ver. 1.5).
* In particular, the name=value string is not placed in the
* form data as it should be, so you cannot use multiple
* type=submit input fields in a single form.
* The new <button> and <input type=button> elements aren't
* supported either, so you can't use that tag.
* <p>
* I've added a <inputAction value="Text"&rt; tag for simple
* situations, but the implementation of the cancel tag does not
* give you access to the data that would com in with a normal
* submit operation.
* <li>The <code>JEditorPane</code> base class doesn't understand
* <code><script></code>
* tags (or JavaScript)---it just
* displays as normal text the contents of all
* <code><script></code> elements that are not nested inside
* comments.
* Unfortunately,
* javadoc-generated HTML doesn't follow the nest-script-in-comments
* convention, so you can't easily use <code>JEditorPane</code>
* as a Java-documentation browser.
* <li>The {@link JEditorPane} doesn't do frames correctly when you customize
* it (as I've done here).
* Frames appear to work, but pages displayed within
* frames are processed as if you were using a standard {@link
* javax.swing.JEditorPane}, with
* none of the features described here available to you. Consequently,
* you can't really use HTML frames. You can, however, create several
* HTMLPane instances and arrange them inside a JPanel (or other
* container) using a GridbagLayout or GridLayout.
* </ol>
* <p>
* I'm hoping that at least some {@link EditorKit} behavior
* will eventually get fixed. My guess is that the more fundamental
* structural problems (like the broken frame stuff) will probably
* stay broken, so the only long term solution is to toss the
* Sun implementation and replace it with something that works.
* <p>
* <DL>
* <DT><B>Requires:</B></DT>
* <DD>JDK1.4 (Regular expressions and
* <code>assert</code> statements are used.)
* </DD>
* </DL>
*
* <!-- ====================== distribution terms ===================== -->
* <p><blockquote
* style="border-style: solid; border-width:thin; padding: 1em 1em 1em 1em;">
* <center>
* Copyright © 2003, Allen I. Holub. All rights reserved.
* </center>
* <br>
* <br>
* This code is distributed under the terms of the
* <a href="http://www.gnu.org/licenses/gpl.html"
* >GNU Public License</a> (GPL)
* with the following ammendment to section 2.c:
* <p>
* As a requirement for distributing this code, your splash screen,
* about box, or equivalent must include an my name, copyright,
* <em>and URL</em>. An acceptable message would be:
* <center>
* This program contains Allen Holub's <em>XXX</em> utility.<br>
* (c) 2003 Allen I. Holub. All Rights Reserved.<br>
* http://www.holub.com<br>
* </center>
* If your progam does not run interactively, then the foregoing
* notice must appear in your documentation.
* </blockquote>
* <!-- =============================================================== -->
* @author Allen I. Holub
*/
//public class HTMLPane extends JEditorPane
// EkitCore needs to extend from JTextPane
// HTMLPane handles well customized tags
public class HTMLPane extends JTextPane
{
private FilterFactory filterProvider = FilterFactory.NULL_FACTORY;
private static final Logger log = Logger.getLogger("com.holub.ui");
/** A map of actual host names to replacement names, set by
* {@link #addHostMapping}
*/
private static Map hostMap = null;
/** Maps tags to Publishers of {@linkplain TagHandler handlers} for
* that tag
*/
private Map tagHandlers =
Collections.synchronizedMap(new HashMap());
/** A list of all components provided by a TagHandler that
* support the {@link TagBehavior} interface.
*/
private ActionListener actionListeners = null;
/** A list of all JComponents that act as stand in for custom
* tags that also implement TagBehavior.
*/
private Collection contributors = new LinkedList();
/**
* The name used as a key to get the tag-name attribute out of
* the attributes passed to a TagHandler object.
*/
public static final String TAG_NAME = "<tagName>";
/**
* All controls created from HTML tags (except for multi-line text input)
* are positioned so that they're aligned
* properly with respect to the text baseline. This way a radio button,
* for example, will line up with the text next to it. If you create
* a control of your own to be displayed in place of to a custom tag,
* you may want to issue a:
* <PRE>
* widget.setAlignmentY( BASELINE_ALIGNMENT );
* </PRE>
* request to get it aligned the same way as the standard controls
*/
public static final float BASELINE_ALIGNMENT = 0.70F;
//@constructor-start
/**
* Create an empty pane. Populate it by calling
* {@link JEditorPane#setPage(URL)} or {@link JEditorPane#setText(String)}
* <PRE>
* HTMLPane form = new HTMLPane();
* form.setPage( new URL("file://test.html") );
* </PRE>
* (For reasons that are not clear to me, the URL and String versions
* of the base-class constructors don't work when called from
* a derived-class constructor, so these base-class constructors
* are not exposed here.)
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -