What You ll Need

Java > Core SWING advanced programming > 4. JEDITORPANE AND THE SWING HTML PACKAGE > Saving Document Content

 

Saving Document Content

So far, we have only used JEditorPane to display documents but, as its name suggests, it can also be used as a document editor. To use a component as an editor requires three things:

  • It must be capable of loading documents from an external source.

  • It must be possible to make changes to the loaded representation of the document.

  • There must be a way to store the updated state to the original location or to another file.

You've just seen that JEditorPane supports the first of these via its read method, the setText method, or the setPage method and via the read methods of the EditorKit for the document being loaded. Because JEditorPane is derived from JTextComponent, once it has been loaded with some content and a Document created, you can use the usual editing capabilities to modify the content. You can, for example, use the cursor keys to move the caret around the screen and insert, delete, and modify text. You can also copy, delete, and paste text between the JEditorPane and the clipboard. All these capabilities are available if you make the JEditorPane editable, which is the default state.

JEditorPane provides a write method that allows you to save its current state to an external location, defined as follows:

 public void write (Writer out) throws IOException 

This method, which is actually inherited from JTextComponent, writes all of the current Document model to the file (or whatever else) that the Writer argument corresponds to. This brings up a couple of questions:

  • In what format is the output written?

  • How is it possible to save RTF files, which do not work well with the 16-bit data paths that writers represent?

Document Output Format

When you save the content of a JEditorPane, it is actually written out by the installed EditorKit, in the format appropriate for that EditorKit. In other words, an HTML file is written out as HTML and a plain text file as plain text. While it might be obvious that you can read a plain text file into a JEditorPane and write it back without losing any information, for file formats that contain internal formatting and other attributes that are not directly displayed, it is not so clear that the process of saving the content will not lose any information. For example, HTML uses tags to encode font and color changes. When the HTML document is loaded, those tags affect the attributes that are stored within the Document and cause the appropriate effects on the screen. However, there are often several different ways to write the HTML to achieve a particular effect. Is the exact HTML preserved when it is written back out?

Core Note

Actually, writing out plain text is not quite as simple as was implied above. There are two common ways to delimit line boundaries within text files: UNIX systems use a single newline character (\n), whereas files on DOS and Windows platforms use the 2-byte \r\n sequence. As you saw in "Document Loading," however, the DefaultEditorKit, which handles plain text documents for JEditorPane, creates a Document property called EndOfLinestringproperty that records the convention that was detected when the original file was read. This property is used to insert the correct delimiters when the file content is written back.



The HTMLEditorKit writes out HTML that preserves what it displays on the screen. In most cases, the HTML you get will be very close to that which was in the original file. As you'll see later in this chapter, the HTML tags from the original input source are preserved within the Document, making it possible to produce a fairly accurate reproduction of the original file. However, the exact file layout is not necessarily preserved the indentation, for example, is not likely to be the same in the output file as it was in the file originally read, but comments that appear in the input file will be retained and written to the output file in their original locations.

You can see an example of this by typing the following command:

 java AdvancedSwing.Chapter4.EditorPaneExample6 

This program is similar to the examples that you have already seen in this chapter, apart from the fact that it expects you to supply a filename rather than a URL and it also has a Save button. Furthermore, if the file that you load into the JEditorPane was writable, the JEditorPane itself has editing enabled. In the same directory as the example program is a simple HTML file that you can use to see how the tags from the source file are affected when they are written back out.

Core Note

You can find the source code for this example on the book's CD-ROM. We'll look at the code that implements the action performed by the Save button later in this chapter.



The content of this HTML file is as follows:

 <HTML> <HEAD> <TITLE>JEditorPane HTML Example</TITLE> </HEAD> <BODY> <!-- A preserved commment --> <Hl>JEditorPane HTML Example</Hl> <P> This example shows that <FONT COLOR="red" SIZE=+1>HTML attributes </FONT> are preserved by <TT>JEditorPane</TT> <!-- End of text --> </BODY> </HTML> 

To load this file into the JEditorPane, type its filename (not URL) into the input field and press RETURN. If you installed the examples from the CD-ROM into the directory c:\AdvancedSwing\Examples, the appropriate filename to type would be:

 c:\AdvancedSwing\Examples\AdvancedSwing\Chapter4\    EditorPaneExample6.html 

In this example, you type a filename, not a URL, because the file is going to be modified when you've made some changes to it, so a local filename, not the URL of a resource somewhere on the other side of the Internet, is required. Nevertheless, the example still has to convert this filename to a URL so that JEditorPane can use it. For the sake of readers using JDK 1.1, if you look at the source code on the CD-ROM you'll see that it performs the conversion like this:

 String fileName = textField.getText( ).trim( ); File file = new File (fileName); absolutePath = file.getAbsolutePath(); String url = "file:///" + absolutePath; URL newURL = new URL(url); 

In other words, convert the content of the text field to an absolute path name and put file:/// in front of it, and then pass the result to the URL constructor. In Java 2, the File class has a convenience method that does this job for you. If you're using Java 2, you can replace the above with this:

 String fileName = textField.getText().trim(); File file = new File (fileName); URL newURL = file.toURL(); 

The result of loading this file is shown in Figure 4-7.

Figure 4-7. Viewing an HTML document with editing enabled.
graphics/04fig07.gif

Because the original document was writable, the example code makes the JEditorPane editable. As you can see, this makes a major difference to the way in which the HTML document is displayed. As well as the rendered HTML, you can also see tags from the HTML that would not normally be seen, including the HEAD and TITLE tags and the comments, which appear in boxes in positions reflecting their actual locations in the source file. If you now press the Save button, the JEditorPane write method will be called to write the document content back to the file system. So that you can easily see the changes that are made as a result of reading the document into the JEditorPane and writing it back out again, the output filename is the same as the input name but with the suffix .save added. The result of saving this particular document is shown below.

 <html>    <head>    <title>JEditorPane HTML Example </title>    </head>    <body>       <!-- A preserved commment -->       <hl>          JEditorPane HTML Example </hl>       <P>         This example shows that <font color="red" size="+l">HTML attributes </font> are preserved by  <tt>JEditorPane</tt><!-- End of text -->       </p>    </body> </html> 

You'll notice that the tags are all preserved, but their indentation (and case) is not and that the comments are also retained. These changes occur because the document is actually written out by the write method of the HTMLEditorKit, which generates HTML from the HTMLDocument that was built when the file was read. If you add or delete some text within the JEditorPane and save the content again, you'll find that your updates are reflected in the HTML that is written to the output file. There is only one JEditorPane write method, which requires a Writer, but the abstract base class EditorKit requires the implementation of two lower-level write methods:

 public void write (Writer out, Document doc, int pos, int len)                       throws IOException, BadLocationException public void write (OutputStream os, Document doc, int pos, int len)                     throws IOException, BadLocationException 

These more primitive APIs theoretically allow you to write out only a portion of a document, although not all implementations actually support this in particular, the RTFEditorKit ignores the pos and len arguments and simply creates an external representation of the entire document.

The JEditorPane write method maps directly to the first of the EditorKit write methods shown earlier. There is no way to use the second method via JEditorPane, but we'll show an example that uses it in the next section. Whether you write out the document content from the JEditorPane or directly from the EditorKit, you need to create a Writer corresponding to the file or other object that you would like to direct the output to. This sounds simple, but actually there is a catch awaiting the unwary. Suppose you want to write the content of the JEditorPane to a file on your local system, as the example program you previously saw does. It looks like the simplest thing in the world to achieve this just create a FileWriter:

 FileWriter writer = new FileWriter (fileName); pane.write (writer); writer.close (); 

The problem here, though, is the same as the one we explored in "Character Set Handling". All files are held in some 8-bit encoding, to which the Writer must translate the Unicode from the JEditorPane's Document model. You saw earlier that it is possible to create a Reader that accepts an encoding name and an Inputstream as parameters, and performs the translation implied by the encoding on the data from the Inputstream. A FileWriter actually creates an OutputStream, which is an 8-bit data channel, but uses the platform's default encoding when writing to it. This may not be what you want. If it isn't, you can take the more complex route of constructing your own Writer by wrapping an OutputStreamWriter around a suitable OutputStream. When you create the OutputStream, you can specify the encoding that you require. For example, to create a Writer that encodes in Windows Cyrillic, the following code can be used:

 Writer writer = new OutputStreamWriter (                 new FileOutputStream (fileName), "cp1251"); 

That only leaves the problem of knowing which encoding to use. Unfortunately, the JEditorPane doesn't help here. When the document was loaded, it will have been assumed to be in the local encoding (unless you created your own Reader with a different encoding and called the appropriate read method), but if the document contained HTML, for example, the encoding may have changed as a result of an HTTP-EQUIV tag that supplied the charset attribute. Although the JEditorPane sees this and changes its internal encoding, there is currently no way to get this information from outside the JEditorPane. Therefore, at least for the present, you either have to be content with saving the data in your platform s local encoding, or you need to allow the user to specify the encoding when saving content from a JEditorPane.

Saving RTF Files

Saving RTF documents from JEditorPane is not straightforward because of the 8-bit nature of the encoding that RTF uses. If you try to invoke the JEditorPane write method when an RTF document is installed, you'll get an exception with an error message informing you that RTF is an 8-bit format. Nevertheless, you can still save an RTF document by using the lower-level RTFEditorKit write method, which accepts an Outputstream as an argument. In fact, EditorPaneExample6 does just that. To try this out, load an RTF document by typing the filename

 c:\AdvancedSwing\Examples\AdvancedSwing\Chapter4\LM.rtf 

into the input field and pressing RETURN, and then pressing the Save button. This creates a file called lm.rtf.save in the same directory as the original. If you edit the document in the JEditorPane before you save it, the changes you make will be reflected in the output file. This example is able to save both HTML and RTF files, even though one format requires you to use a raw Outputstream and to save via the EditorKit, while the user uses a Writer and can be saved from JEditorPane. The distinction between these two is made in the ActionListener that handles events from the Save button, the implementation of which is shown in the code extract in Listing 4-4.

Listing 4-4 Saving RTF Documents
 saveButton.addActionListener(new ActionListener() {    public void actionPerformed(ActionEvent evt) {       try {          String type = pane.getContentType();          Outputstream os = new BufferedOutputStream(                   new FileOutputStream(file + ".save"));          pane.setEditable(false);          textField.setEnabled(false) ;          saveButton.setEnabled(false) ;          f.setCursor(Cursor.getPredefinedCursor(                                        Cursor.WAIT_CURSOR));          Document doc = pane.getDocument();          int length = doc.getLength();          if (type.endsWith("/rtf")) {             // Saving RTF - use the OutputStream             try {                pane.getEditorKit().write(                                        os, doc, 0, length);                os.close();             } catch (BadLocationException ex) {             }          } else {             // Not RTF - use a Writer.             Writer w = new OutputStreamWriter(os);             pane.write(w);             w.close();          }        } catch (IOException e) {          JOptionPane.showMessageDialog(pane,             new String[] {                "Unable to save file",                file.getAbsolutePath(),             }, "File Save Error",             JOptionPane.ERROR_MESSAGE);       }       pane.setEditable(file.canWrite ());       textField.setEnabled(true);       saveButton.setEnabled(file.canWrite());       f.setCursor(Cursor.getDefaultCursor());    } }); 

This code creates a new file to which the document will be written by using a FileOutputStream, and then wraps it in a BufferedOutputStream for improved performance. The resulting OutputStream can be used directly if the document in the JEditorPane is RTF and, if it is not, it can be used instead to create an OutputStreamWriter that can be given to the JEditorPane write method. The choice between these two is made here by examining the installed document's content type if the string returned by the JEditorPane getContentType method ends in /rtf, the document is assumed to be RTF. Other ways to achieve the same thing would be to check whether the JEditorPane is using an RTFEditorKit or an RTFDocument. A minor drawback of these techniques, however, is that this code would have a runtime dependency on one of these classes, which would cause the RTF support to be loaded into your Java VM as a result of saving an HTML file or a plain text file. The implementation shown earlier avoids this possibility.

Notice that, as mentioned in the previous section, if it is necessary to create a Writer, the OutputStreamWriter constructor that does not accept an encoding name is used, because there is no way to know the proper encoding without user intervention. In a production application, if this were an issue you would need to prompt the user for the correct encoding to use.

Core Note

If you load and save the file lm.rtf from the examples installed from the CD-ROM, you'll notice that the saved file is much smaller than the original 3K compared to an initial size of 700K! The reason for this is that the current implementation of RTF does not handle images, which is why the image isn't visible when you load the document. Because the image isn't part of the loaded RTFDocument, it doesn't get written back to the output file.



Document Type Conversion

Because JEditorPane has built-in support for a number of document types, it is reasonable to wonder whether it is possible to use it as a means of converting one document format to another. An example of this might be to load an HTML document and save it in RTF. The basis for this question is the fact that the loading process converts the source format into an internal representation that has some level of commonality over all the supported formats. Each EditorKit also has a write method that can translate this internal form to the external format that it knows about.

The answer to this question is that, in general, reliable document conversions are not possible. Although the result of loading an HTML document is similar to that obtained when loading the same content from an RTF files each document type has many private attributes that can be interpreted properly only by its own EditorKit. You'll see in the next section that the HTML support makes extensive use of private attributes to represent HTML tags from the input file. These attributes would be incomprehensible to the RTF EditorKit. As a result, if you write the content of a document using an EditorKit other than the one that loaded it you will, at best, get a poor representation of the original, perhaps containing only plain text.

You can experiment with the various document conversions available by using a modified version of the previous example that allows you to load from and save into any of the supported document formats. To try out this example, type the command:

 java AdvancedSwing.Chapter4.EditorPaneExample7 

This application looks the same as the previous one, except that it has a set of radio boxes that allow you to chose the format in which you want to save the output document, as shown in Figure 4-8.

Figure 4-8. An application that saves files in various formats.
graphics/04fig08.gif

After loading a document, select the format in which you would like to save it and press the Save button. The result of saving the document is written to the program's standard output, so if you intend to look closely at what is generated, you might want to redirect the standard output to a file when you start the program, like this:

 java AdvancedSwing.Chapter4.EditorPaneExample7 >       c:\temp\savedfile 

In Figure 4-8, we've loaded an HTML page that you can find in the examples on the CD-ROM. If you have installed the examples in the usual location, you can load this file by typing the following into the filename field:

 c:\AdvancedSwing\Examples\AdvancedSwing\Chapter4\LM.html 

If you save this in plain text, you'll get simply the text, grouped into paragraphs. Saving it in HTML produces an almost perfect representation of the original the tags themselves aren't exactly the same, as you saw earlier, but the result of reloading the saved document into a Web browser or the JEditorPane is the same as loading the original. This is, of course, what you might expect if you load HTML using HTMLEditorKit and then save it using the same EditorKit.

Saving the HTML as RTF loses the image (as it would even if the document had been in RTF format originally) and some of the color and font formatting. The reason for the somewhat inaccurate conversion to RTF is that HTML documents do not store all their attributes in the same way as you saw in Chapter 2, so there are fewer attributes available for the RTF generator (which only understands the ones shown in Chapter 2) to look at. You'll see how the attributes implied by the HTML tags are actually stored later in this chapter.

The code that implements the Save button in this example is shown in Listing 4-5.

Listing 4-5 Saving in Different Formats
 button.addActionListener(new ActionListener() {    public void actionPerformed(ActionEvent evt) {       Writer w = null;       OutputStream os = System.out;       String contentType;       if (plain. isSelected()) {          contentType = "text/plain";          w = new OutputStreamWriter(os);       } else if (html.isSelected()) {          contentType = "text/html";          w = new OutputStreamWriter(os);       } else {          contentType = "text/rtf";       }       EditorKit kit =              pane.getEditorKitForContentType(contentType);       try {          if (w != null) {             kit.write (w, pane.getDocument( ),             0, pane.getDocument( ).getLength( ));             w.flush( );          } else {             kit.write (os, pane.getDocument( ),                0, pane.getDocument( ).getLength( ));             os.flush( );          }       } catch (Exception e) {          System.out.println ("Write failed");       }    } }); 

When the button is pressed, the target content type is obtained by checking the state of the radio boxes. The content type is used to get the appropriate EditorKit by calling the JEditorPane getEditorKitForContentType method, and then the document content is written out by calling the EditorKit write method passing it the installed Document. Note that, as ever, there is special code here that handles writing out RTF, which requires an Outputstream instead of a Writer; writing RTF uses the standard output stream directly, whereas it is wrapped with an OutputstreamWriter for the other content types. Notice that it is not necessary to install the EditorKit for the output format into the JEditorPane to have it write its output and, in fact, doing so would lose the content of the loaded Document because the JEditorPane setEditorKit method creates a new, empty Document, as you have already seen.

You should experiment with loading documents of all three types and saving them in each of the other formats. An interesting case arises when you load a plain text or RTF document and try to save it in HTML. The HTMLEditorKit has special code that detects that the Document it is working with is not an HTMLDocument and, when this happens, it tries to create a reasonable copy of the original using the attributes stored at the character and paragraph level by the code that created the Document. In the case of a plain document, there will be no attributes to reproduce, so only the usual header and body tags along with paragraph tags to break the text at newline boundaries are written out. With RTF, though, the effect is different. You can see this by loading the LM.rtf file that was used earlier in this chapter and then saving it as HTML. The saved file starts like this:

 <html>   <head>     <style>       <!--         p.Default Paragraph Font {           underline:;           italic:;           bold:normal;         }         p.heading 1 {           RightIndent:0.0;           SpaceBelow:12.0;           LeftIndent:0.0;           SpaceAbove:3.0;           FirstLineIndent:0.0;         }         p.Normal {           LeftIndent:0.0;           FirstLineIndent: 0 . 0 ;           RightIndent: 0 . 0 ;         }         P.H4 {           RightIndent:0.0;           SpaceBelow: 13 . 0;           LeftIndent: 0 . 0;           SpaceAbove:6.5;           FirstLineIndent:12.0;         }       -->     </style>   </head>   <body>     <p class=heading 1>        <font style="color: #000000; font-size: 14;                                     font-family: Arial;">         <b>The Apollo Lunar Module</b>       </font>     </p> 

The header contains an HTML style sheet, which defines styles to be used with HTML tags later in the document. You can see an example of this at the end of the extract shown earlier, in which the text between the start and end paragraph tags has the "heading 1" style applied to it. This style is defined by the style sheet to have a particular amount of spacing before and after the text and no special indentation. The styles in the style sheets are created from the Styles in the Document that the HTML writer is given to process (refer to Chapter 2 for a full discussion of Styles and StyleContexts). In this case, the Styles will have been created by the RTF reader to represent the formatting in the original RTF file.

The automatic creation of a style sheet from a StyleContext also works (after a fashion) for documents that you handcraft using JTextPane and gives you a way to save them in external form. Because JTextPane is a subclass of JEditorPane, everything that we have discussed in this chapter applies also to JTextPane. To demonstrate the point, type the following command:

 java AdvancedSwing.Chapter4.EditorPaneExample8 

This command runs a slightly modified version of an example that was shown in Chapter 2 (see Figure 2-6 for the original application), consisting of a JTextPane containing a Document with several custom Styles applied to it, which looks like Figure 4-9. This version was created simply by adding the same visual elements as were used in EditorPaneExample7, along with the code to handle the Save button that you saw in the discussion of that example. Because JTextPane is a subclass of JEditorPane, the same code continues to work with JTextPane.

Figure 4-9. An application that uses JTextPane content in various formats.
graphics/04fig09.gif

Select HTML and press Save and you'll get an HTML page that more-or-less corresponds to the styling that was applied in the JTextPane. If you look at the output, you'll see that it again starts with an HTML style sheet:

 <style>    <!--    p.MainStyle {       LeftIndent:16.0;       RightIndent:16.0;       FirstLineIndent:16.0;       family:serif;       size :12;    }    p.CompPara {       SpaceAbove:16.0;    }    p.Heading2 {       LeftIndent:8.0;       foreground:java.awt.Color[r=255,g=0,b=0];       FirstLineIndent:0.0;       bold:true;       family:serif;       size:16;    }    p.ConstantWidth {       foreground:java.awt.Color[r=0,g=255,b=0];       family:monospaced;    }    p.Component {       component:       javax.swing.JLabel[,0,0,399x319,alignmentX=0.0,           alignmentY=null,border=,flags=0,maximumSize=          minimumSize=,preferredSize= ,           defaultIcon=javax.swing.ImageIcon@e5db8825,          disabledIcon=,horizontalAlignment=CENTER,           horizontalTextPosition=CENTER,iconTextGap=4,          labelFor=,text=Displaying text with attributes,           verticalAlignment=CENTER,verticalTextPosition=BOTTOM];      }    --> </style> 

Core Alert

The output above was obtained using a beta release of Swing 1.1.1. In the version of Java 2 available at the time of writing (version 1.2.2), this example does not work due to a Swing bug a NullpointerException occurs when the document is being saved in HTML format and you'll just see a message that reads "Write failed."



Listing 2-3 in Chapter 2 showed most of the styles that were created when building this Document, with the exception of the Component style that was added in the following example. If you compare the styles in Listing 2-3 with those in the style sheet, you'll see that they match. Unfortunately, the style sheet is useless because the attributes held in the Document do not map to attributes recognized by HTML; if you load the page back into a browser, you'll get what looks like plain text arranged into paragraphs. If you want to use this feature to generate correct HTML from the content of a JTextPane, you'll either have to be prepared to edit the output to replace the names defined in the styleConstants class with the attributes that are valid within style sheets or, better, create a subclass of HTMLEditorKit that has an HTML generator that maps to the correct attributes automatically.

Core Note

Ideally, this mapping should be performed by the standard HTMLEditorKit and perhaps in a future release of Swing this will be done.



 

 



Core Swing
Core Swing: Advanced Programming
ISBN: 0130832928
EAN: 2147483647
Year: 1999
Pages: 55
Authors: Kim Topley

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net