【问题标题】:Split a XWPFRun into multiple runs将 XWPFRun 拆分为多个运行
【发布时间】:2019-07-27 14:08:23
【问题描述】:

我正在尝试通过自动将其中的一些关键字加粗来修改现有 Word 文档。举个例子:

敏捷的棕色狐狸跳过懒惰的狗。 (1)

会变成:

敏捷的棕色狐狸跳过懒惰的。 (2)

我的问题是 (1) 是一次运行,而 (2) 变成 5 次运行(5 作为 dog 之后的句号不是粗体,但它是一个细节)。我得到了多次运行。完全没问题。

问题 #1:

有没有办法在同一个段落中轻松地将一个运行拆分为多个运行?我没有成功。

问题 #2:

由于我没有设法拆分运行,我尝试创建一个新段落,但它确实不理想并将运行添加到它。我设法完全复制了一个段落并修改了重复段落中的运行,我保留了样式(这是预期的)但我在重复的段落中丢失了 cmets。

理想情况下,我想将运行分开(在段落内),但如果不可能有一个更好的克隆器:

  public static void cloneRun(XWPFRun source, XWPFRun clone) {
    CTRPr rPr = clone.getCTR().isSetRPr()
        ? clone.getCTR().getRPr()
        : clone.getCTR().addNewRPr();
    rPr.set(source.getCTR().getRPr());
    clone.setText(source.getText(0));
  }

【问题讨论】:

    标签: java ms-word apache-poi


    【解决方案1】:

    How do I change color of a particular word document using apache poi? 中,出于格式化原因,我展示了一种拆分XWPFRuns 的算法。这仅用于格式化一个字符,它不会克隆运行属性。但是基本显示出来了。我们必须查看整个段落,因为只有插入运行的方法。而且我们需要循环遍历运行文本字符,因为所有拆分成单词的方法都会导致标点符号出现问题,同时将单词重新组合成一个段落。

    缺少的是一种将运行属性从原始运行克隆到新添加的运行属性的方法。这可以通过克隆底层的w:rPr 元素来完成。

    那么整个方法就是遍历段落中的所有运行。如果我们有一个带有关键字的运行,则将运行文本拆分为字符。然后遍历该运行中的所有字符并缓冲它们。如果缓冲的字符流以关键字结尾,则将当前缓冲的所有字符(关键字除外)设置为实际运行的文本。然后为格式化的关键字插入新的运行,并从原始运行克隆运行属性。将关键字设置为运行并进行其他格式化。然后为下一个字符插入一个新的运行,并从原始运行中克隆运行属性。对于段落中的每次运行,依此类推。

    完整示例:

    import java.io.*;
    import org.apache.poi.xwpf.usermodel.*;
    import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
    
    import org.apache.xmlbeans.XmlObject;
    import org.apache.xmlbeans.XmlCursor;
    
    import java.util.*;
    import java.awt.Desktop;
    
    public class WordFormatWords {
    
     static void cloneRunProperties(XWPFRun source, XWPFRun dest) { // clones the underlying w:rPr element
      CTR tRSource = source.getCTR();
      CTRPr rPrSource = tRSource.getRPr();
      if (rPrSource != null) {
       CTRPr rPrDest = (CTRPr)rPrSource.copy();
       CTR tRDest = dest.getCTR();
       tRDest.setRPr(rPrDest);
      }
     }
    
     static void formatWord(XWPFParagraph paragraph, String keyword, Map<String, String> formats) {
      int runNumber = 0;
      while (runNumber < paragraph.getRuns().size()) { //go through all runs, we cannot use for each since we will possibly insert new runs
       XWPFRun run = paragraph.getRuns().get(runNumber);
       XWPFRun run2 = run;
       String runText = run.getText(0);
       if (runText != null && runText.contains(keyword)) { //if we have a run with keyword in it, then
    
        // This code part is to manage comment ranges.
        // Do we have commentRangeEnd immediately after the run?
        // If so then remember that in a cursor.
        XmlCursor commentRangeEndCursor = null; 
        XmlCursor cursor = run.getCTR().newCursor();
        cursor.toEndToken();
        if (cursor.hasNextToken()) {
         cursor.toNextToken();
         XmlObject commentRangeEnd = cursor.getObject();
         if (commentRangeEnd != null && commentRangeEnd instanceof CTMarkupRange) {
          commentRangeEndCursor = cursor;
         }
        }
    
        char[] runChars = runText.toCharArray(); //split run text into characters
        StringBuffer sb = new StringBuffer();
        for (int charNumber = 0; charNumber < runChars.length; charNumber++) { //go through all characters in that run
         sb.append(runChars[charNumber]); //buffer all characters
         runText = sb.toString();
         if (runText.endsWith(keyword)) { //if the bufferend character stream ends with the keyword  
          //set all chars, which are current buffered, except the keyword, as the text of the actual run
          run.setText(runText.substring(0, runText.length() - keyword.length()), 0); 
          run2 = paragraph.insertNewRun(++runNumber); //insert new run for the formatted keyword
          cloneRunProperties(run, run2); // clone the run properties from original run
          run2.setText(keyword, 0); // set the keyword in run
          for (String toSet : formats.keySet()) { // do the additional formatting
           if ("color".equals(toSet)) {
            run2.setColor(formats.get(toSet));
           } else if ("bold".equals(toSet)) {
            run2.setBold(Boolean.valueOf(formats.get(toSet)));
           }
          }
          run2 = paragraph.insertNewRun(++runNumber); //insert a new run for the next characters
          cloneRunProperties(run, run2); // clone the run properties from original run
          run = run2;
          sb = new StringBuffer(); //empty the buffer
         } 
        }
        run.setText(sb.toString(), 0); //set all characters, which are currently buffered, as the text of the actual run
    
        // This code part is to manage comment ranges.
        // If we had remembered commentRangeEnd, then move this to here now.
        if(commentRangeEndCursor != null) {
         cursor = run.getCTR().newCursor();
         cursor.toEndToken();
         if (cursor.hasNextToken()) {
          cursor.toNextToken();
          commentRangeEndCursor.moveXml(cursor);
         }
         cursor.dispose();
         commentRangeEndCursor.dispose();
        }
    
       }
       runNumber++;
      }
     }
    
    
     public static void main(String[] args) throws Exception {
    
      XWPFDocument doc = new XWPFDocument(new FileInputStream("source.docx"));
    
      String[] keywords = new String[]{"fox", "dog"};
      Map<String, String> formats = new HashMap<String, String>();
      formats.put("bold", "true");
      formats.put("color", "DC143C");
    
      for (XWPFParagraph paragraph : doc.getParagraphs()) { //go through all paragraphs
       for (String keyword : keywords) {
        formatWord(paragraph, keyword, formats);
       }
      }
    
      FileOutputStream out = new FileOutputStream("result.docx");
      doc.write(out);
      out.close();
      doc.close();
    
      System.out.println("Done");
      Desktop.getDesktop().open(new File("result.docx"));
    
     }
    }
    

    此代码还关注XML 标记范围元素,例如紧跟在运行的r 元素之后的commentRangeEnd。这种标记范围元素用于标记其他元素组的开始和结束。例如,应用注释的一组文本运行元素位于commentRangeStartcommentRangeEnd 之间,具有相同的id

    如果在需要拆分的运行之后紧跟commentRangeEnd,那么我们在游标中记住它。然后在拆分运行后,我们将这个commentRangeEnd 立即移动到最后一个新插入的运行后面。所以 cmets 应该保持正确。

    当然,即使这样也会有一些缺点,因为 Microsoft Word 有时在文本运行中存储文本的方式很笨拙。当Microsoft Word 是源时,没有唯一的通用解决方案。

    【讨论】:

    • 你使用了函数run.getText(0),但是当你有一个更复杂的运行时会发生什么?例如包含 3 个子节点(除了属性节点)的 Run:Text (w:t)、Break (w:br) 和另一个 Text(w:t) ?我在 Apache POI 中找不到获取 Run 的所有子节点的方法...
    猜你喜欢
    • 1970-01-01
    • 2014-12-29
    • 1970-01-01
    • 2018-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    • 1970-01-01
    相关资源
    最近更新 更多