All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.simter.jxls.ext.EachMergeCommand Maven / Gradle / Ivy

The newest version!
package tech.simter.jxls.ext;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.jxls.area.Area;
import org.jxls.command.CellRefGenerator;
import org.jxls.command.EachCommand;
import org.jxls.common.*;
import org.jxls.transform.Transformer;
import org.jxls.transform.poi.PoiTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Extends {@link EachCommand} to support merge cells.
 *
 * @author RJ
 */
public class EachMergeCommand extends EachCommand {
  private static Logger logger = LoggerFactory.getLogger(EachMergeCommand.class);

  public static final String COMMAND_NAME = "each-merge";

  public EachMergeCommand() {
    super();
  }

  public EachMergeCommand(String var, String items, Direction direction) {
    super(var, items, direction);
  }

  public EachMergeCommand(String items, Area area) {
    super(items, area);
  }

  public EachMergeCommand(String var, String items, Area area) {
    super(var, items, area);
  }

  public EachMergeCommand(String var, String items, Area area, Direction direction) {
    super(var, items, area, direction);
  }

  public EachMergeCommand(String var, String items, Area area, CellRefGenerator cellRefGenerator) {
    super(var, items, area, cellRefGenerator);
  }

  @Override
  @SuppressWarnings("unchecked")
  public Size applyAt(CellRef cellRef, Context context) {
    // collect sub command areas
    List childAreas = this.getAreaList().stream()
      .flatMap(area1 -> area1.getCommandDataList().stream())
      .flatMap(commandData -> commandData.getCommand().getAreaList().stream())
      .collect(Collectors.toList());
    List childAreaRefs = childAreas.stream()
      .map(Area::getAreaRef).collect(Collectors.toList());

    // register AreaListener for parent command area
    Area parentArea = this.getAreaList().get(0);
    MergeCellListener listener = new MergeCellListener(getTransformer(), parentArea.getAreaRef(), childAreaRefs,
      ((Collection) context.getVar(this.getItems())).size()); // 总数据量
    logger.info("register listener {} to {} from {}", listener, parentArea.getAreaRef(), cellRef);
    parentArea.addAreaListener(listener);

    // register AreaListener for all sub command area
    childAreas.forEach(area -> {
      logger.info("register listener {} to {} by parent", listener, area.getAreaRef());
      area.addAreaListener(listener);
    });

    // standard dealing
    return super.applyAt(cellRef, context);
  }

  /**
   * The {@link AreaListener} for merge cells.
   */
  public static class MergeCellListener implements AreaListener {
    private final PoiTransformer transformer;
    private int parentStartColumn;                         // parent command start column
    private int[] childStartColumns;                       // all sub command start column
    private final int[] mergeColumns;                      // to merge columns
    private final List records = new ArrayList<>(); // 0 - start column, 1 - end column
    private int parentCount;

    private int childRow;
    private int parentProcessed;
    private String sheetName;

    MergeCellListener(Transformer transformer, AreaRef parent, List children, int parentCount) {
      this.transformer = (PoiTransformer) transformer;
      this.parentCount = parentCount;
      this.parentStartColumn = parent.getFirstCellRef().getCol();

      // find all sub command columns
      int[] childCols = children.stream()
        .flatMapToInt(ref -> IntStream.range(ref.getFirstCellRef().getCol(), ref.getLastCellRef().getCol() + 1))
        .distinct().sorted()
        .toArray();

      // find all sub command start column
      this.childStartColumns = children.stream()
        .mapToInt(ref -> ref.getFirstCellRef().getCol())
        .distinct().sorted().toArray();

      // get columns to merge by filter childCols
      this.mergeColumns = IntStream.range(parent.getFirstCellRef().getCol(), parent.getLastCellRef().getCol() + 1)
        .filter(parentCol -> IntStream.of(childCols).noneMatch(childCol -> childCol == parentCol))
        .toArray();

      if (logger.isDebugEnabled()) {
        logger.debug("parentArea={}", parent);
        logger.debug("parentStartColumn={}", parentStartColumn);
        logger.debug("childStartColumns={}", childStartColumns);
        logger.debug("mergeColumns={}", mergeColumns);
        logger.debug("childCols={}", childCols);
      }
    }

    @Override
    public void beforeApplyAtCell(CellRef cellRef, Context context) {
    }

    @Override
    public void afterApplyAtCell(CellRef cellRef, Context context) {
    }

    @Override
    public void beforeTransformCell(CellRef srcCell, CellRef targetCell, Context context) {
    }

    // remember that all sub command AreaListener is invoked before parent command AreaListener.
    // This class use this feature to do the merge work.
    @Override
    public void afterTransformCell(CellRef srcCell, CellRef targetCell, Context context) {
      if (parentProcessed == 0) this.sheetName = targetCell.getSheetName();

      if (targetCell.getCol() == parentStartColumn) { // main command process
        this.parentProcessed++;

        logger.debug("parent: srcCell={}, targetCell={} [{}, {}]", srcCell, targetCell,
          targetCell.getRow(), targetCell.getCol());

        //should be recorded just on necessary
        if (targetCell.getRow() < this.childRow) this.records.add(new int[]{targetCell.getRow(), this.childRow});

        // merge work invoke on the last
        if (this.parentProcessed == this.parentCount) {
          Workbook workbook = transformer.getWorkbook();
          Sheet sheet = workbook.getSheet(sheetName);
          doMerge(sheet, this.records, this.mergeColumns, srcCell);
        }
        this.childRow = 0;

        // record the current row number of sub command process
      } else if (IntStream.of(childStartColumns).anyMatch(col -> col == targetCell.getCol())) {
        this.childRow = Math.max(this.childRow, targetCell.getRow());

        logger.debug("child: srcCell={}, targetCell={} [{}, {}]", srcCell, targetCell,
          targetCell.getRow(), targetCell.getCol());
      }
    }

    private static void doMerge(Sheet sheet, List records, int[] mergeColumns, CellRef srcCell) {
      if (logger.isDebugEnabled()) {
        logger.debug("merge: sheetName={}, records={}", sheet.getSheetName(),
          records.stream().map(startEnd -> "[" + startEnd[0] + "," + startEnd[1] + "]")
            .collect(Collectors.joining(",")));
      }
      records.forEach(startEnd -> merge4Row(sheet, startEnd[0], startEnd[1], mergeColumns, srcCell));
    }

    private static void merge4Row(Sheet sheet, int fromRow, int toRow, int[] mergeColumns, CellRef srcCell) {
      if (fromRow >= toRow) {
        logger.warn("No need to merge because same row:fromRow={}, toRow={}", fromRow, toRow);
        return;
      }
      Cell originCell;
      CellStyle originCellStyle;
      CellRangeAddress region;
      for (int col : mergeColumns) {
        logger.debug("fromRow={}, toRow={}, col={}", fromRow, toRow, col);
        region = new CellRangeAddress(fromRow, toRow, col, col);
        sheet.addMergedRegion(region);

        //firstCell = sheet.getRow(fromRow).getCell(col);
        originCell = sheet.getRow(srcCell.getRow()).getCell(col);
        if (originCell == null) {
          logger.info("Missing cell: row={}, col={}", fromRow, col);
        }
        if (originCell != null) {
          // copy originCell style to the merged cell
          originCellStyle = originCell.getCellStyle();
          RegionUtil.setBorderTop(originCellStyle.getBorderTopEnum(), region, sheet);
          RegionUtil.setBorderRight(originCellStyle.getBorderRightEnum(), region, sheet);
          RegionUtil.setBorderBottom(originCellStyle.getBorderBottomEnum(), region, sheet);
          RegionUtil.setBorderLeft(originCellStyle.getBorderLeftEnum(), region, sheet);
        } else {
          RegionUtil.setBorderTop(BorderStyle.THIN, region, sheet);
          RegionUtil.setBorderRight(BorderStyle.THIN, region, sheet);
          RegionUtil.setBorderBottom(BorderStyle.THIN, region, sheet);
          RegionUtil.setBorderLeft(BorderStyle.THIN, region, sheet);
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy