StyledEditorKit文本对齐不正常

时间:2016-02-16 10:17:36

标签: java html swing jtextpane text-alignment

我正在尝试在用Java生成的html文档中实现用户可选择的文本对齐。我试过了:

JMenuItem leftAlignMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
JMenuItem centerMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
JMenuItem rightAlignMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));

以及此主题的各种变化。选择菜单项会使文本在文本窗格中正确对齐,并将相应的html标记添加到保存的文档中。问题是,添加标签后,单击另一个对齐菜单项并不会更改它,因此无法多次更改默认(左)文本对齐并保存更改。

我知道我不是第一个遇到此问题的人,但到目前为止我还没有找到任何解决方案,所以任何帮助都会受到最高的赞赏。

这是我的" M" CVE,遗憾的是仍然很大,但我无法删除更多代码,或者它不会证明问题:

package aligntest;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.JFrame;

public class AlignTest extends JFrame implements ActionListener {

    private HTMLDocument doc; // Stores the formatted text.
    private JTextPane textPane = new JTextPane(); // The Pane itself.
    String FilePath = "";  // Stores the file path.

        public AlignTest() { // This method is called automatically when the app is launched.
            HTMLEditorKit editorKit = new HTMLEditorKit();
            doc = (HTMLDocument)editorKit.createDefaultDocument();  
            init(); // Calls interface method below.
    }

    public static void main(String[] args) {
        AlignTest editor = new AlignTest();
    }
        public void init(){

            JMenuBar menuBar = new JMenuBar();
            getContentPane().add(menuBar, BorderLayout.NORTH);
            JMenu fileMenu = new JMenu("File"); 
            JMenu alignMenu = new JMenu("Text Align");

            menuBar.add(fileMenu);
            menuBar.add(alignMenu);

            JMenuItem openItem = new JMenuItem("Open"); //
            JMenuItem saveItem = new JMenuItem("Save"); //

            openItem.addActionListener(this);
            saveItem.addActionListener(this);

            fileMenu.add(openItem);
            fileMenu.add(saveItem);

            JMenuItem leftAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
            JMenuItem centerMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
            JMenuItem rightAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));

            leftAlignMenuItem.setText("Left");
            centerMenuItem.setText("Center");
            rightAlignMenuItem.setText("Right");

            alignMenu.add(leftAlignMenuItem);
            alignMenu.add(centerMenuItem);
            alignMenu.add(rightAlignMenuItem);

            textPane = new JTextPane(doc); // Create object from doc and set this as value of textPane.
            textPane.setContentType("text/html"); // textPane holds html.
            JScrollPane scrollPane = new JScrollPane(textPane); // textPane in JScrollPane to allow scrolling if more text than space.
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // Get screen size to use below.
            Dimension scrollPaneSize = new Dimension(1*screenSize.width/2,1*screenSize.height/2); // Together with next line, sets dimensions of textPane relative to screen size.
            scrollPane.setPreferredSize(scrollPaneSize);
            getContentPane().add(scrollPane, BorderLayout.SOUTH);

            pack();
            setLocationRelativeTo(null);        
            show(); // Actually displays the interface.

        }

        public void actionPerformed(ActionEvent ae) { // Method called with action commands from interface objects above.  Which action depends on the text of the interface element.
            String actionCommand = ae.getActionCommand();           
        if (actionCommand.compareTo("Open") == 0){ // Calls method when action command received.
            openDocument();
        } else if (actionCommand.compareTo("Save") == 0){
            saveDocument();
                }
        }

        public void saveDocument(){

            String FP = FilePath;  // This paragraph calls Save As instead of Save if file not already saved.
            String unsaved = "";
            int saved = FP.compareTo(unsaved);
            if (saved == 0) {
                saveDocumentAs();
            } else {
                save();
            }
        }

        public void saveDocumentAs(){                
            JFileChooser SaveDialog = new javax.swing.JFileChooser();
            int returnVal = SaveDialog.showSaveDialog(this);

            if (returnVal == JFileChooser.APPROVE_OPTION) {
                java.io.File saved_file = SaveDialog.getSelectedFile();
                FilePath = saved_file.toString();

                save();
            }
        }

        public void save(){
            try {
                WriteFile objPane = new WriteFile(FilePath, false);
                String PaneText = textPane.getText();  // Gets text from Title Pane.
                objPane.writeToFile(PaneText);
            }
            catch (Exception ex) {
            }
        }

        public void openDocument(){

            JFileChooser OpenDialog = new javax.swing.JFileChooser(); // Creates file chooser object.
            int returnVal = OpenDialog.showOpenDialog(this);  // Defines 'returnVal' according to what user clicks in file chooser.

            if (returnVal == JFileChooser.APPROVE_OPTION) { // Returns value depending on whether user clicks 'yes' or 'OK' etc.
                java.io.File file = OpenDialog.getSelectedFile(); // Gets path of selected file.
                FilePath = file.toString( ); // Converts path of selected file to String.

// The problem seems to be related to the code that starts here...
                try {
                    ReadFile readPane = new ReadFile(FilePath);  // Creates "readPane" object from "FilePath" string, using my ReadFile class.
                    String[] aryPane = readPane.OpenFile();  // Creates string array "aryPane" from "readPane" object.

                    int i;  // Creates integer variable "i".
                    String PaneText = "";

                    for (i=0; i < aryPane.length; i++) {  //  Creates a for loop with starting "i" value of 0, adding 1 to i each time round and ending when i = the number of lines in the aryLines array.
                        PaneText = PaneText + aryPane[i];  //  Add present line to "PaneText".
                    }
                    textPane.setText(PaneText);  // Displays "PaneText" in "TextPane".

                } catch (Exception ex) {
// and ends here.  This code also calls ReadFile, so code in that class may be at fault.

                }
                }
            }
}

它还必须调用以下两个类中的方法才能工作:

package aligntest;

import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;

public class ReadFile {

    private String path;

    public ReadFile(String file_path) {
        path = file_path;
    }

    public String[] OpenFile() throws IOException {

        FileReader fr = new FileReader(path);
        BufferedReader textReader = new BufferedReader(fr);

        int numberOfLines = readLines( );
        String[] textData = new String[numberOfLines];

        int i;

        for (i=0; i < numberOfLines; i++) {
            textData[i] = textReader.readLine();
        }

        textReader.close( );
            return textData;
    }


    int readLines() throws IOException {

        FileReader file_to_read = new FileReader(path);
        BufferedReader bf = new BufferedReader(file_to_read);

        String aLine;
        int numberOfLines = 0;

        while ((aLine = bf.readLine()) != null) {
            numberOfLines++;
        }
        bf.close();

        return numberOfLines;
    }

}

&安培;

package aligntest;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;

public class WriteFile {

    private String path;
    private boolean append_to_file = false;

    public WriteFile(String file_path) {
        path = file_path;
    }

    public WriteFile(String file_path, boolean append_value) {
        path = file_path;
    }

    public WriteFile(File SectionPath, boolean success) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    public void writeToFile( String textLine ) throws IOException {
        FileWriter write = new FileWriter(path, append_to_file);
        PrintWriter print_line = new PrintWriter(write);

        print_line.printf( "%s" + "%n" , textLine);

        print_line.close();
}
}

问题似乎与打开文档(第126-138行)或“ReadFile&#39; class:在另一个程序中查看保存的文件时,我可以看到标签正在更改,直到文档关闭,然后再次使用&#39; AlignTest&#39;。在此之后,任何对齐更改都不会反映在html中。

希望有人能提供帮助。

编辑:以下是&#39; AlignTest&#39;制作的一些HTML。如果将其粘贴到文本文件中,然后在&#39; AlignTest&#39;中打开。它应该重现问题:&#39; AlignTest&#39;无法更改对齐标记。

<html>
  <head>
    <meta id="_moz_html_fragment">

  </head>
  <body>
    <p align="right" style="margin-top: 0pt">
      Another
    </p>
  </body>
</html>

3 个答案:

答案 0 :(得分:1)

事实证明这比我想象的要困难得多。让我解释一下幕后发生的事情然后我会给出几个场景。

AlignmentAction的操作会将setParagraphAttributes设置为boolean replace来调用文档的false。在setParagraphAttributes中,通过Alignment.XXX将给定属性(MutableAttributeSet.addAttributes)添加到段落标记的当前属性列表中。效果如下:

  1. 如果段落标记没有任何对齐指令,则会添加HTML align="xxx"。该文件将使用此新属性保存。
  2. 如果段落标记只有HTML属性,则会添加内联CSS属性:text-align=xxx。该文件仅使用HTML属性保存(CSS属性被丢弃,我不知道为什么,"可能需要替换为')。
  3. 如果段落标记具有HTML和CSS属性,则会修改CSS标记。这些文件仅保存为HTML文件。
  4. 如果段落标记只有CSS属性,则会对其进行修改。该文件具有从CSS转换的新HTML属性。
  5. 摘要是,由于某种原因,无论运行时存在哪些属性,都只能保存HTML属性。由于未进行修改,因此必须先将其删除,然后再添加新属性。可能需要使用不同的作者。

    尝试解决方案的一种方法是创建自己的对齐操作并将替换值设置为true。问题是它取代了整个段落元素:

    <html>
      <head>
        <meta id="_moz_html_fragment">
    
      </head>
      <body>
        <body align="center">
          Another
        </body>
      </body>
    </html>
    

    您需要做的是访问元素并“手动”替换属性。创建一个扩展HTMLDocument@override setParagraphAttributes的类,使其包含行

    // attr is the current attribute set of the paragraph element
    attr.removeAttribute(HTML.Attribute.ALIGN); // remove the HTML attribute
    

    attr.addAttributes(s); // s is the given attributes containing the Alignment.XXX style.
    

    然后按照上述1-4个方案保存文件将保持正确对齐。

    最终,您会想要使用HTML解析器,例如jsoup;只是Google for Java HTML解析器。另请参阅Which HTML Parser is the best?

答案 1 :(得分:0)

以下是我在JTextPane中更改文本对齐方式(也可以在其他Swing组件中使用):

public void alignLeft() {
        String text = textPane.getText();
        text = text.replace("<p style=\"margin-top: 0\">", "<p align=left style=\"margin-top: 0\">");
        text = text.replace("align=\"center\"", "align=\"left\"");
        text = text.replace("align=\"right\"", "align=\"left\"");
        textPane.setText(text);

和中心和右对齐的等价物。

如果有人在考虑使用请注意:

  • 我没有彻底测试过它
  • 它将更改JTextPane中所有文本的对齐方式 - 用户无法定义哪个文本对齐。

答案 2 :(得分:0)

我试图实现@ user1803551建议的解决方案,但是就像我在上面的评论中所说的那样,我找不到在只读的AttributeSet上使用removeAttribute()的方法。

我实现了建议的解决方案的不同版本,克隆了除对齐相关属性外的所有段落的AttributeSet。然后,使用带有replace = true的setParagraphAttributes覆盖当前属性,并使用带有replace = false的setParagraphAttributes应用请求的修改。看来效果很好。

public class ExtendedHTMLDocument extends HTMLDocument {

    @Override
    public void setParagraphAttributes(int offset, int length, AttributeSet attr, boolean replace) {
        AttributeSet paragraphAttributes = this.getParagraphElement(offset).getAttributes();
        MutableAttributeSet to =  new SimpleAttributeSet();
        Enumeration<?> keys = paragraphAttributes.getAttributeNames();
        String value = "";
        while (keys.hasMoreElements()) {
            Object key = keys.nextElement();
            if (key instanceof CSS.Attribute) {
                if (!key.equals(CSS.Attribute.TEXT_ALIGN)) {
                    value = value + " " + key + "=" + paragraphAttributes.getAttribute(key) + ";";
                }
            }
            else {
                if (!key.equals(HTML.Attribute.ALIGN)) {
                    to.addAttribute(key, paragraphAttributes.getAttribute(key));
                }
            }
        }
        if (value.length() > 0) {
            to.addAttribute(HTML.Attribute.STYLE, value);
        }
        super.setParagraphAttributes(offset, length, to, true);
        super.setParagraphAttributes(offset, length, attr, replace);
    }

}