DefaultCellCommentHandler.java

  1. package com.gh.mygreen.xlsmapper;

  2. import java.nio.charset.Charset;
  3. import java.util.Arrays;
  4. import java.util.Optional;

  5. import org.apache.poi.hssf.usermodel.HSSFRichTextString;
  6. import org.apache.poi.ss.usermodel.Cell;
  7. import org.apache.poi.ss.usermodel.ClientAnchor;
  8. import org.apache.poi.ss.usermodel.Comment;
  9. import org.apache.poi.ss.usermodel.CreationHelper;
  10. import org.apache.poi.ss.usermodel.Drawing;
  11. import org.apache.poi.ss.usermodel.RichTextString;
  12. import org.apache.poi.ss.usermodel.Row;
  13. import org.apache.poi.ss.usermodel.Sheet;
  14. import org.apache.poi.ss.util.CellRangeAddress;
  15. import org.apache.poi.xssf.usermodel.XSSFRichTextString;
  16. import org.slf4j.Logger;
  17. import org.slf4j.LoggerFactory;

  18. import com.gh.mygreen.xlsmapper.annotation.XlsCommentOption;
  19. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  20. import com.gh.mygreen.xlsmapper.util.CellPosition;

  21. /**
  22.  * {@link CellCommentHandler}の標準の実装。
  23.  *
  24.  * @since 2.1
  25.  * @author T.TSUCHIE
  26.  *
  27.  */
  28. public class DefaultCellCommentHandler implements CellCommentHandler {

  29.     private static final Logger logger = LoggerFactory.getLogger(DefaultCellCommentHandler.class);
  30.    
  31.     /**
  32.      * コメントの縦方向の開始位置。
  33.      * 行数分で表現する。
  34.      */
  35.     private int vertialPrefix = 1;
  36.    
  37.     /**
  38.      * コメントの横方向の開始位置。
  39.      * 列数分で表現する。
  40.      */
  41.     private int horizontalPrefix = 1;
  42.    
  43.     /**
  44.      * コメントの縦方向の最大サイズ。
  45.      * 行数分で表現する。
  46.      */
  47.     private int maxVerticalSize = 4;
  48.    
  49.     /**
  50.      * コメントの列方向の最大サイズ。
  51.      * 列数分で表現する。
  52.      */
  53.     private int maxHorizontalSize = 3;
  54.    
  55.     @Override
  56.     public Optional<String> handleLoad(final Cell cell, Optional<XlsCommentOption> commentOption) {
  57.        
  58.         Comment comment = getMergedCellComment(cell);
  59.         if(comment == null) {
  60.             return Optional.empty();
  61.         }
  62.        
  63.         String commentText = comment.getString().getString();
  64.         return Optional.of(commentText);
  65.     }
  66.    
  67.     /**
  68.      * 結合を考慮したセルのコメントを取得する。
  69.      * @param cell 元となるセル。
  70.      * @return コメント。コメントが設定されていなければ、nullを返す。
  71.      */
  72.     private Comment getMergedCellComment(final Cell cell) {
  73.         Comment comment = cell.getCellComment();
  74.         if(comment != null) {
  75.             return comment;
  76.         }
  77.        
  78.         final Sheet sheet = cell.getSheet();
  79.         final int size = sheet.getNumMergedRegions();
  80.        
  81.         for(int i=0; i < size; i++) {
  82.             final CellRangeAddress range = sheet.getMergedRegion(i);
  83.             if(!range.isInRange(cell)) {
  84.                 continue;
  85.             }
  86.            
  87.             // nullでないセルを取得する。
  88.             for(int rowIdx=range.getFirstRow(); rowIdx <= range.getLastRow(); rowIdx++) {
  89.                 final Row row = sheet.getRow(rowIdx);
  90.                 if(row == null) {
  91.                     continue;
  92.                 }

  93.                 for(int colIdx=range.getFirstColumn(); colIdx <= range.getLastColumn(); colIdx++) {
  94.                     final Cell valueCell = row.getCell(colIdx);
  95.                     if(valueCell == null) {
  96.                         continue;
  97.                     }

  98.                     comment = valueCell.getCellComment();
  99.                     if(comment != null) {
  100.                         return comment;
  101.                     }
  102.                 }
  103.             }
  104.         }
  105.        
  106.         return null;
  107.        
  108.     }

  109.     @Override
  110.     public void handleSave(final Cell cell, final Optional<String> text, final Optional<XlsCommentOption> commentOption) {
  111.        
  112.         if(!text.isPresent()) {
  113.             // コメントが空のとき
  114.             commentOption.ifPresent(option -> {
  115.                 if(option.removeIfEmpty()) {
  116.                     // コメントが空のとき既存のコメントを削除する
  117.                     cell.removeCellComment();
  118.                 }
  119.             });
  120.             return;
  121.         }
  122.        
  123.         final Sheet sheet = cell.getSheet();
  124.         final CreationHelper helper = sheet.getWorkbook().getCreationHelper();
  125.         final Drawing<?> drawing = sheet.createDrawingPatriarch();
  126.        
  127.         final Comment comment;
  128.         RichTextString richText = helper.createRichTextString(text.get());
  129.         if(cell.getCellComment() == null) {
  130.             ClientAnchor anchor = createAnchor(drawing, text.get(), cell, commentOption);
  131.             comment = drawing.createCellComment(anchor);
  132.             applyCommentFormat(richText, cell);
  133.         } else {
  134.             // 既存のコメントが存在する場合は、書式やサイズをコピーして使用する。
  135.             comment = cell.getCellComment();
  136.             RichTextString orgText = comment.getString();
  137.             if(orgText.numFormattingRuns() > 0) {
  138.                 copyCommentFormat(richText, orgText);
  139.             } else {
  140.                 applyCommentFormat(richText, cell);
  141.             }
  142.         }
  143.        
  144.         comment.setString(richText);
  145.        
  146.         // コメントの表示状態の更新
  147.         commentOption.ifPresent(option -> comment.setVisible(option.visible()));
  148.        
  149.         cell.setCellComment(comment);
  150.        
  151.     }
  152.    
  153.     /**
  154.      * コメントの位置、サイズを作成する。
  155.      * @param drawing
  156.      * @param text 書き込むコメント
  157.      * @param cell 書込み対象のセル
  158.      * @param commentOption コメントのオプション
  159.      * @return コメントの表示位置
  160.      */
  161.     protected ClientAnchor createAnchor(final Drawing<?> drawing, final String text, final Cell cell,
  162.             final Optional<XlsCommentOption> commentOption) {
  163.         final CellPosition address = CellPosition.of(cell);
  164.        
  165.         // コメントを開業で分割し、最長の行を取得する。
  166.         String[] split = text.split("\r\n|\r|\n");
  167.         int maxLength = Arrays.stream(split)
  168.                 .mapToInt(str -> str.getBytes(Charset.forName("Windows-31j")).length)
  169.                 .max().orElse(0);
  170.        
  171.         /*
  172.          * コメントの横サイズ。文字数(バイト数)をもとに決定。
  173.          * ・1セルの文字数を元に出す。
  174.          * ・columnWidthは、1文字の幅を1/256にしたものが単位となる。
  175.          * ・最大3列分とする。
  176.          */
  177.         int charPerColumn = cell.getSheet().getColumnWidth(cell.getColumnIndex())/256;
  178.         int commentColumnSize = (int)Math.ceil(maxLength*1.0 / charPerColumn);
  179.        
  180.         int columnSize = commentColumnSize;
  181.         int lineWrappingCount = 0;
  182.         if(commentColumnSize > maxHorizontalSize) {
  183.             columnSize = maxHorizontalSize;
  184.             // 行の折り返し回数を計算する
  185.             lineWrappingCount = commentColumnSize / maxHorizontalSize;
  186.         }
  187.        
  188.         if(commentOption.isPresent() && commentOption.get().horizontalSize() > 0) {
  189.             // 直接指定されている場合
  190.             columnSize = commentOption.get().horizontalSize();
  191.             // 行の折り返し回数を計算する
  192.             lineWrappingCount = columnSize / maxHorizontalSize;
  193.         }
  194.        
  195.         // コメントの縦サイズ。行数をもとに決定。
  196.         int rowSize = split.length + lineWrappingCount > maxVerticalSize ? maxVerticalSize : split.length + lineWrappingCount;
  197.         if(commentOption.isPresent() && commentOption.get().verticalSize() > 0) {
  198.             // 直接指定されている場合
  199.             rowSize = commentOption.get().verticalSize();
  200.         }
  201.        
  202.         return drawing.createAnchor(
  203.                 0, 0, 0, 0,
  204.                 address.getColumn() + horizontalPrefix, address.getRow() + vertialPrefix,
  205.                 address.getColumn() + horizontalPrefix + columnSize, address.getRow() + vertialPrefix + rowSize);
  206.     }
  207.    
  208.     /**
  209.      * 新規にコメントの装飾を設定する。
  210.      * セルの装飾に合わせる。
  211.      *
  212.      * @param toRichText 設定先のコメント
  213.      * @param cell コメントを設定する先のセル
  214.      */
  215.     protected void applyCommentFormat(final RichTextString toRichText, final Cell cell) {
  216.        
  217.         toRichText.applyFont(cell.getSheet().getWorkbook().getFontAt(cell.getCellStyle().getFontIndexAsInt()));
  218.        
  219.     }
  220.    
  221.     /**
  222.      * 既にコメントが設定されているときのコメントの装飾を設定する。
  223.      * 既存のコメントの装飾をコピーするが、そのとき、1つ目のフォント設定のみとする。
  224.      *
  225.      * @param toRichText コピー先
  226.      * @param fromrichText コピー元
  227.      */
  228.     protected void copyCommentFormat(final RichTextString toRichText, final RichTextString fromrichText) {
  229.        
  230.         if(toRichText instanceof XSSFRichTextString) {
  231.             toRichText.applyFont(((XSSFRichTextString)fromrichText).getFontOfFormattingRun(0));
  232.            
  233.         } else if(toRichText instanceof HSSFRichTextString) {
  234.             toRichText.applyFont(((HSSFRichTextString)fromrichText).getFontOfFormattingRun(0));
  235.            
  236.         } else {
  237.             logger.warn("not suuported exdcel format comment : {}", toRichText.getClass().getName());
  238.         }
  239.        
  240.     }

  241.     /**
  242.      * コメントの縦方向の開始位置を取得する。
  243.      * 行数分で表現する。
  244.      * @return
  245.      */
  246.     public int getVertialPrefix() {
  247.         return vertialPrefix;
  248.     }

  249.     /**
  250.      * コメントの縦方向の開始位置を設定する。
  251.      * 行数分で表現する。
  252.      * @param vertialPrefix コメントの縦方向の開始位置。(0以上)
  253.      * @throws IllegalArgumentException {@literal vertialPrefix < 0}
  254.      */
  255.     public void setVertialPrefix(int vertialPrefix) {
  256.         ArgUtils.notMin(vertialPrefix, 0, "vertialPrefix");
  257.         this.vertialPrefix = vertialPrefix;
  258.     }

  259.     /**
  260.      * コメントの横方向の開始位置を取得する。
  261.      * 列数分で表現する。
  262.      * @return
  263.      */
  264.     public int getHorizontalPrefix() {
  265.         return horizontalPrefix;
  266.     }

  267.     /**
  268.      * コメントの横方向の開始位置を設定する。
  269.      * 列数分で表現する。
  270.      * @param horizontalPrefix コメントの横方向の開始位置。(0以上)
  271.      * @throws IllegalArgumentException {@literal horizontalPrefix < 0}
  272.      */
  273.     public void setHorizontalPrefix(int horizontalPrefix) {
  274.         ArgUtils.notMin(horizontalPrefix, 0, "horizontalPrefix");
  275.         this.horizontalPrefix = horizontalPrefix;
  276.     }
  277.    
  278.     /**
  279.      * コメントの縦方向の最大サイズを取得する。
  280.      * 行数分で表現する。
  281.      * @return the maxVerticalSize
  282.      */
  283.     public int getMaxVerticalSize() {
  284.         return maxVerticalSize;
  285.     }

  286.    
  287.     /**
  288.      * コメントの縦方向の最大サイズを設定する。
  289.      * 行数分で表現する。
  290.      * @param maxVerticalSize コメントの縦方向の最大サイズ。(1以上)
  291.      * @throws IllegalArgumentException {@literal maxVerticalSize < 1}
  292.      */
  293.     public void setMaxVerticalSize(int maxVerticalSize) {
  294.         ArgUtils.notMin(maxVerticalSize, 1, "maxVerticalSize");
  295.         this.maxVerticalSize = maxVerticalSize;
  296.     }

  297.    
  298.     /**
  299.      * コメントの列方向の最大サイズ。
  300.      * 列数分で表現する。
  301.      * maxHorizontalSize を取得する
  302.      * @return the maxHorizontalSize
  303.      */
  304.     public int getMaxHorizontalSize() {
  305.         return maxHorizontalSize;
  306.     }

  307.    
  308.     /**
  309.      * コメントの列方向の最大サイズ。
  310.      * 列数分で表現する。
  311.      * @param maxHorizontalSize コメントの横方向の最大サイズ。(1以上)
  312.      * @throws IllegalArgumentException {@literal maxHorizontalSize < 1}
  313.      */
  314.     public void setMaxHorizontalSize(int maxHorizontalSize) {
  315.         ArgUtils.notMin(maxHorizontalSize, 1, "maxHorizontalSize");
  316.         this.maxHorizontalSize = maxHorizontalSize;
  317.     }

  318.    
  319. }