如何将 HTML 转换为格式良好的 DOCX,样式属性完好无损

How to convert HTML to well formed DOCX with styling attributes intact

我正在尝试使用 docx4j 将 HTML5 文件转换为 docx。更大的图景是 HTML 包含阿拉伯语数据和英语数据。我在 HTML 中为元素设置了样式。我的 HTML 在 chrome 上看起来很整洁,但是当我使用 docx4j 转换为 docx 时,阿拉伯文本格式丢失了。在 MS word 上,它显示我的阿拉伯语文本设置了粗体样式,但它不是粗体。同样,RTL 方向也丢失了。表格从 RTL 反转为 LTR。 作为一种解决方法,我使用 BufferedWriter 生成 .doc 文件,该文件与我的 HTML 文件与样式属性相匹配,但 html 中存在 Base64 图像,而 .doc 文件中没有。因此,需要转换为 .docx 格式。我的要求是从我的 HTML 生成的可编辑文档。 请指导我,因为我一直在挠头。没有源示例代码也能正常工作。

这是我用来将 HTML 转换为 docx 的代码。

public boolean convertHTMLToDocx(String inputFilePath, String outputFilePath, boolean headerFlag,
        boolean footerFlag,String orientation, String logoPath, String margin, JSONObject json,boolean isArabic) {
    boolean conversionFlag;
    boolean orientationFlag = false;
    try {
        if(!orientation.equalsIgnoreCase("Y")){
            orientationFlag = true;
        }
        String stringFromFile = FileUtils.readFileToString(new File(inputFilePath), "UTF-8");
        String unescaped = stringFromFile;
        WordprocessingMLPackage wordMLPackage  = WordprocessingMLPackage.createPackage();
        NumberingDefinitionsPart ndp = new NumberingDefinitionsPart();
        wordMLPackage.getMainDocumentPart().addTargetPart(ndp);
        ndp.unmarshalDefaultNumbering();

        ImportXHTMLProperties.setProperty("docx4j-ImportXHTML.Bidi.Heuristic", true);
        ImportXHTMLProperties.setProperty("docx4j-ImportXHTML.Element.Heading.MapToStyle", true);
        ImportXHTMLProperties.setProperty("docx4j-ImportXHTML.fonts.default.serif", "Frutiger LT Arabic 45 Light");
        ImportXHTMLProperties.setProperty("docx4j-ImportXHTML.fonts.default.sans-serif", "Frutiger LT Arabic 45 Light");
        ImportXHTMLProperties.setProperty("docx4j-ImportXHTML.fonts.default.monospace", "Frutiger LT Arabic 45 Light");

        XHTMLImporterImpl xHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
        xHTMLImporter.setHyperlinkStyle("Hyperlink");
        xHTMLImporter.setParagraphFormatting(FormattingOption.CLASS_PLUS_OTHER);
        xHTMLImporter.setTableFormatting(FormattingOption.CLASS_PLUS_OTHER);
        xHTMLImporter.setRunFormatting(FormattingOption.CLASS_PLUS_OTHER);

        wordMLPackage.getMainDocumentPart().getContent().addAll(xHTMLImporter.convert(unescaped, ""));

        XmlUtils.marshaltoString(wordMLPackage.getMainDocumentPart().getJaxbElement(),true,true);
        File output = new File(outputFilePath);

        wordMLPackage.save(output);

        Console.log("file path where it is stored is" + " " + output.getAbsolutePath());
        if (headerFlag || footerFlag) {
            File file = new File(outputFilePath);
            InputStream in = new FileInputStream(file);

            wordMLPackage = WordprocessingMLPackage.load(in);
            if (headerFlag) {
                // set Header 
            }
            if (footerFlag) {
                // set Footer
            }

            wordMLPackage.save(file);
            Console.log("Finished editing the word document");
        }
        conversionFlag = true;
    } catch (InvalidFormatException e) {
        Error.log("Invalid format found:-" + getStackTrace(e));
        conversionFlag = false;
    } catch (Exception e) {
        Error.log("Error while converting:-" + getStackTrace(e));
        conversionFlag = false;
    }

    return conversionFlag;
}

这是我处理它的方法。这不是最好的方法,但是是的,我已经看到它在组织中得到实施。在这些方法中,他们在应用程序服务器上创建 war 文件,用于将静态和动态内容托管到 HTTP 请求。

因此,我使用一个简单的字节数组写入 .doc 文件而不是 .docx。 这样,最终的 word 文档将与 html 完全相同。我遇到的唯一问题是没有显示二进制图像。只有一个框出现在图像的位置。

所以,我写了两个文件:

1st- 从 html 文件中读取我所有的二进制图像标签,并使用 Base64 解码器对图像进行解码。将所有解码图像保存在我的服务器主机的磁盘上,创建该文件的路径,并将 html 中所有此类 img 标签的 src 属性替换为磁盘上的此位置。 (新位置前面有 http://{remote_server}:{remote_port}/{war_deployment_descriptor}/images/

2nd- 我在部署在服务器上的 war 文件中创建了一个简单的 servlet,它监听 /images 上的获取请求,并在收到带有路径名的获取请求时,在 OutputStream 上返回图像。

瞧,图片开始出现了。

免责声明 - 但是,这些图像在您的网络之外是不可见的。我很幸运能够严格遵守客户的网络。要使它们在网络外部可用,您可以请求您的 IT 团队允许在开放网络或您希望可用性的网络上提供图像服务路径。问题就解决了。

编辑 - 您可以创建一个新的 war 文件来托管这些图像或使用生成这些图像的文件。


我的经历- 对于英文文档,请使用 docx4j 进行 .docx 转换。 对于阿拉伯语、希伯来语或其他 RTL 语言,请按上述方式进行 .doc 转换。所有此类 .doc 文档都可以轻松地从 MS Word 转换为 .docx。

列出两个文件,请根据需要更改:

File1.java

        public static void writeHTMLDatatoDoc(String content, String inputHTMLFile,String outputDocFile,String uniqueName) throws Exception {
            String baseTag = getRemoteServerURL()+"/{war_deployment_desciptor}/images?image=";
            String tag = "Image_";
            String ext = ".png";
            String srcTag = "";
            String pathOnServer = getDiskPath() + File.separator + "TemplateGeneration"
                    + File.separator + "generatedTemplates" + File.separator + uniqueName + File.separator + "images" + File.separator;
    
            int i = 0;
            boolean binaryimgFlag = false;
    
            Pattern p = Pattern.compile("<img [^>]*src=[\\"']([^\\"^']*)");
            Matcher m = p.matcher(content);
            while (m.find()) {
                String src = m.group();
                int startIndex = src.indexOf("src=") + 5;
                int endIndex = src.length();
                
                // srcTag will contain data as data:image/png;base64,AAABAAEAEBAAAAEAGABoAw.........
                // Replace this whole later with path on local disk
                srcTag = src.substring(startIndex, src.length());
                
                if(srcTag.contains("base64")) {
                    binaryimgFlag = true;
                }
                if(binaryimgFlag) {
                    
                    // Extract image mime type and image extension from srcTag containing binary image
                    ext = extractMimeType(srcTag);
                    if(ext.lastIndexOf(".") != -1 && ext.lastIndexOf(".") != 0)
                        ext = ext.substring(ext.lastIndexOf(".")+1);
                    else 
                        ext = ".png";
                    
                    // read files already created for the different documents for this unique entity.
                    // The location contains all image files as Image_{i}.{image_extension}
                    // Sort files and read max counter in image names. 
                    // Increase value of i to generate next image as Image_{incremented_i}.{image_entension}
                    i = findiDynamicallyFromFilesCreatedForWI(pathOnServer);
                    i++; // Increase count for next image
                    
                    // save whole data to replace later
                    String srcTagBegin = srcTag; 
                    
                    // Remove data:image/png;base64, from srcTag , so I get only encoded image data.
                    // Decode this using Base64 decoder.
                    srcTag = srcTag.substring(srcTag.indexOf(",") + 1, srcTag.length());
                    byte[] imageByteArray = decodeImage(srcTag);
                    
                    // Constrcu replacement tag
                    String replacement = baseTag+pathOnServer+tag+i+ext;
                    replacement = replacement.replace("\", "/");
    
                    // Writing image inside local directory on server
                    FileOutputStream imageOutFile = new FileOutputStream(pathOnServer+tag+i+ext);
                    imageOutFile.write(imageByteArray);
                    content = content.replace(srcTagBegin, replacement);
                    imageOutFile.close();
                }
            }
            
            //Re write HTML file
            writeHTMLData(content,inputHTMLFile);
    
            // write content to doc file
            writeHTMLData(content,outputDocFile);
        }
    
        public static int findiDynamicallyFromFilesCreatedForWI(String pathOnServer) {
            String path = pathOnServer;
            int nextFileCount = 0;
            String number = "";
            String[] dirListing = null;
            File dir = new File(path);
            dirListing = dir.list();
            if(dirListing.length != 0) {
                Arrays.sort(dirListing);
                int length = dirListing.length;
                int index = dirListing[length - 1].indexOf('.');
                number = dirListing[length - 1].substring(0,index);
                int index1 = number.indexOf('_');
                number = number.substring(index1+1,number.length());
                nextFileCount = Integer.parseInt(number);
            }
            return nextFileCount;
        }
    
        private static String extractMimeType(final String encoded) {
            final Pattern mime = Pattern.compile("^data:([a-zA-Z0-9]+/[a-zA-Z0-9]+).*,.*");
            final Matcher matcher = mime.matcher(encoded);
            if (!matcher.find())
                return "";
            return matcher.group(1).toLowerCase();
        }
    
        private static void writeHTMLData(String inputData, String outputFilepath) {
            BufferedWriter writer = null;
            try {
                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(outputFilepath)), Charset.forName("UTF-8")));
                writer.write(inputData);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(writer != null)
                        writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static byte[] decodeImage(String imageDataString) {
            return Base64.decodeBase64(imageDataString);
        }
    
        private static String readHTMLData(String inputFile) {
            String data = "";
            String str = "";
    
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(new FileInputStream(new File(inputFile)), StandardCharsets.UTF_8))) {
                while ((str = reader.readLine()) != null) {
                    data += str;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return data;
        }

File2.java

 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import com.newgen.clos.logging.consoleLogger.Console;
 public class ImageServlet extends HttpServlet {
     public void init() throws ServletException {
     public ImageServlet() {
         super();
     }
 
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         String param = request.getParameter("image");
         Console.log("Image Servlet executed");
         Console.log("File Name Requested: " + param);
         param.replace("\"", "");
         param.replace("%20"," ");
         File file = new File(param);
         response.setHeader("Content-Type", getServletContext().getMimeType(param));
         response.setHeader("Content-Length", String.valueOf(file.length()));
         response.setHeader("Content-Disposition", "inline; filename=\"" + param + "\"");
         Files.copy(file.toPath(), response.getOutputStream());
     }
 }