在Java Swing中从HTML复制img

gun*_*ker 6 java swing image

JTextPane text;
text.setText("somewords <img src=\"file:///C:/filepath/fire.png\" text=\"[fire1]\" title=\"[fire2]\" alt=\"[fire3]\" style=\"width:11px;height:11px;\"> otherwords");
Run Code Online (Sandbox Code Playgroud)

给我这个 在此输入图像描述,这是预期的.但是当我突出显示它并复制粘贴它时,我会得到"somewords otherwords".在复制时在Firefox中完成的同样的事情将粘贴"somewords [fire3] otherwords"(它替代alt文本用于图像).有没有办法在复制alt文本时复制此行为,或者复制图片的任何其他指示?我猜它不是内置功能,所以我可能需要知道的是应该重载以模仿这种行为.

它用于输出/聊天窗口,因此重要的是当用户引用它时它包含图像(如表情)


更新:成功覆盖copyAction方法......现在怎么办?

// (should) allow copying of alt text in place of images
class CustomEditorKit extends HTMLEditorKit {
    Action[] modifiedactions;
    CustomEditorKit() {
        int whereat=-1;
        modifiedactions=super.getActions();
        for(int k=0;k<super.getActions().length;k++) {
            if(super.getActions()[k] instanceof CopyAction) //find where they keep the copyaction
            {
                whereat=k;
                modifiedactions[whereat]=new CustomCopyAction(); //and replace it with a different one
            }
        }
    }
    @Override
    public Action[] getActions() {
        return modifiedactions; //returns the modified version instead of defaultActions
    }
    public static class CustomCopyAction extends TextAction {
        public CustomCopyAction() {
            super(copyAction); 
        }

        @Override
        public void actionPerformed(ActionEvent e) { //need to change this to substitute images with text, preferably their alt text.
            JTextComponent target = getTextComponent(e);
            //target.getText() gives full body of html, unbounded by selection area
            if (target != null) {
                target.copy(); //a confusing and seemingly never ending labyrinth of classes and methods
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

VGR*_*VGR 2

我能想到实现这一点的唯一方法是编写自己的TransferHandler,并重写 getSourceActions 和 exportToClipboard 方法。

您可以自己将 HTML 转换为纯文本,而不是让 Swing 使用JTextPane 的getSelectedText方法,通过递归转换HTML Document的每个Element ,在 Element 具有IMGNameAttribute并且还具有ALT的情况下自定义转换属性。

这是我想出的:

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.IOException;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;

import java.awt.EventQueue;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.TransferHandler;

import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTML;

public class HTMLCopier
extends TransferHandler {
    private static final long serialVersionUID = 1;

    private final Collection<DataFlavor> flavors;

    HTMLCopier() {
        Collection<DataFlavor> flavorList = new LinkedHashSet<>();
        Collections.addAll(flavorList,
            new DataFlavor(String.class, null),
            DataFlavor.stringFlavor);

        String[] mimeTypes = {
            "text/html", "text/plain"
        };
        Class<?>[] textClasses = {
            Reader.class, String.class, CharBuffer.class, char[].class
        };
        Class<?>[] byteClasses = {
            InputStream.class, ByteBuffer.class, byte[].class
        };
        String[] charsets = {
            Charset.defaultCharset().name(),
            StandardCharsets.UTF_8.name(),
            StandardCharsets.UTF_16.name(),
            StandardCharsets.UTF_16BE.name(),
            StandardCharsets.UTF_16LE.name(),
            StandardCharsets.ISO_8859_1.name(),
            "windows-1252",
            StandardCharsets.US_ASCII.name(),
        };

        try {
            flavorList.add(new DataFlavor(
                DataFlavor.javaJVMLocalObjectMimeType +
                "; class=" + String.class.getName()));

            for (String mimeType : mimeTypes) {
                for (Class<?> textClass : textClasses) {
                    flavorList.add(new DataFlavor(String.format(
                        "%s; class=\"%s\"",
                        mimeType, textClass.getName())));
                }
                for (String charset : charsets) {
                    for (Class<?> byteClass : byteClasses) {
                        flavorList.add(new DataFlavor(String.format(
                            "%s; charset=%s; class=\"%s\"",
                            mimeType, charset, byteClass.getName())));
                    }
                }
            }

            for (String mimeType : mimeTypes) {
                flavorList.add(new DataFlavor(String.format(
                    "%s; charset=unicode; class=\"%s\"",
                    mimeType, InputStream.class.getName())));
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        this.flavors = Collections.unmodifiableCollection(flavorList);
    }

    @Override
    public int getSourceActions(JComponent component) {
        return COPY_OR_MOVE;
    }

    @Override
    public void exportToClipboard(JComponent component,
                                  Clipboard clipboard,
                                  int action) {
        JTextPane pane = (JTextPane) component;
        Document doc = pane.getDocument();

        int start = pane.getSelectionStart();
        int end = pane.getSelectionEnd();

        final String html;
        final String plainText;
        try {
            StringWriter writer = new StringWriter(end - start);
            pane.getEditorKit().write(writer, doc, start, end - start);
            html = writer.toString();

            StringBuilder plainTextBuilder = new StringBuilder();
            appendTextContent(doc.getDefaultRootElement(), start, end,
                plainTextBuilder);
            plainText = plainTextBuilder.toString();
        } catch (BadLocationException | IOException e) {
            throw new RuntimeException(e);
        }

        Transferable contents = new Transferable() {
            @Override
            public boolean isDataFlavorSupported(DataFlavor flavor) {
                return flavors.contains(flavor);
            }

            @Override
            public DataFlavor[] getTransferDataFlavors() {
                return flavors.toArray(new DataFlavor[0]);
            }

            @Override
            public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException,
                   IOException {

                String data;
                if (flavor.isMimeTypeEqual("text/html")) {
                    data = html;
                } else {
                    data = plainText;
                }

                Class<?> dataClass = flavor.getRepresentationClass();
                if (dataClass.equals(char[].class)) {
                    return data.toCharArray();
                }
                if (flavor.isRepresentationClassReader()) {
                    return new StringReader(data);
                }
                if (flavor.isRepresentationClassCharBuffer()) {
                    return CharBuffer.wrap(data);
                }
                if (flavor.isRepresentationClassByteBuffer()) {
                    String charset = flavor.getParameter("charset");
                    return Charset.forName(charset).encode(data);
                }
                if (flavor.isRepresentationClassInputStream()) {
                    String charset = flavor.getParameter("charset");
                    return new ByteArrayInputStream(
                        data.getBytes(charset));
                }
                if (dataClass.equals(byte[].class)) {
                    String charset = flavor.getParameter("charset");
                    return data.getBytes(charset);
                }
                return data;
            }
        };

        clipboard.setContents(contents, null);

        if (action == MOVE) {
            pane.replaceSelection("");
        }
    }

    private void appendTextContent(Element element,
                                   int textStart,
                                   int textEnd,
                                   StringBuilder content)
    throws BadLocationException {
        int start = element.getStartOffset();
        int end = element.getEndOffset();
        if (end < textStart || start >= textEnd) {
            return;
        }

        start = Math.max(start, textStart);
        end = Math.min(end, textEnd);

        AttributeSet attr = element.getAttributes();
        Object tag = attr.getAttribute(AttributeSet.NameAttribute);

        if (tag.equals(HTML.Tag.HEAD) ||
            tag.equals(HTML.Tag.TITLE) ||
            tag.equals(HTML.Tag.COMMENT) ||
            tag.equals(HTML.Tag.SCRIPT)) {

            return;
        }

        if (tag.equals(HTML.Tag.INPUT) ||
            tag.equals(HTML.Tag.TEXTAREA) ||
            tag.equals(HTML.Tag.SELECT)) {

            // Swing doesn't provide a way to read input values
            // dynamically (as far as I know;  I could be wrong).
            return;
        }

        if (tag.equals(HTML.Tag.IMG)) {
            Object altText = attr.getAttribute(HTML.Attribute.ALT);
            if (altText != null) {
                content.append(altText);
            }
            return;
        }

        if (tag.equals(HTML.Tag.CONTENT)) {
            content.append(
                element.getDocument().getText(start, end - start));
            return;
        }

        int count = element.getElementCount();
        for (int i = 0; i < count; i++) {
            appendTextContent(element.getElement(i), textStart, textEnd,
                content);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JTextPane text = new JTextPane();
                text.setContentType("text/html");
                text.setEditable(false);
                text.setText("somewords <img src=\"file:///C:/filepath/fire.png\" text=\"[fire1]\" title=\"[fire2]\" alt=\"[fire3]\" style=\"width:11px;height:11px;\"> otherwords");

                text.setTransferHandler(new HTMLCopier());

                JFrame window = new JFrame("HTML Copier");
                window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                window.getContentPane().add(new JScrollPane(text));
                window.pack();
                window.setLocationByPlatform(true);
                window.setVisible(true);

                text.selectAll();
                text.copy();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:更新了代码以仅将突出显示的文本正确放置在剪贴板上。