【读取excel】样式获取
请教读取excel的时候,通过现有 api 或者某种方式怎么获取到单元格样式? 方便后续使用。
读的话目前只解析了Numfmt,其它样式未解析,后续会补上请持续关注本项目了解最新进度
嗯,我可以先用哪个api呢,后续可以关注项目了解进度,现在样式只有numfmt也没关系。
- 首先在ExcelReader#init方法大约570有一段Styles.load(s);代码初始化加载样式文件
- 解析Cell的时候样式保存在org.ttzero.excel.reader.Cell对象的xf属性中,xf为style index并不是style
- 在Row对象中需要增加public int getStyle(){ }方法,调用Styles#getStyleByIndex方法使用第2步的style index换取style
- 然后就可以Styles#getNumFmt, Styles#getFill, Styles#getFont, Styles#getBorder 等方法获取具体的样式了
有兴趣的话可以尝试实现xlsx格式的样式解析
- 首先在ExcelReader#init方法大约570有一段Styles.load(s);代码初始化加载样式文件
- 解析Cell的时候样式保存在org.ttzero.excel.reader.Cell对象的xf属性中,xf为style index并不是style
- 在Row对象中需要增加public int getStyle(){ }方法,调用Styles#getStyleByIndex方法使用第2步的style index换取style
- 然后就可以Styles#getNumFmt, Styles#getFill, Styles#getFont, Styles#getBorder 等方法获取具体的样式了
有兴趣的话可以尝试实现xlsx格式的样式解析
3q, 对第 3 点 还有点疑问。我搜索代码发现 ExcelReader#init 570 获取到的 styles 在599设置到了 sheet 中,是不是我如果需要获取 cell 的 style,可以通过 sheet.getStyles().getStyleByIndex(row.getCell(0).xf). 不明白为什么说通过 row 去获取style。把业务样板代码写好了来尝试做除fmt外的样式解析。
- 首先在ExcelReader#init方法大约570有一段Styles.load(s);代码初始化加载样式文件
- 解析Cell的时候样式保存在org.ttzero.excel.reader.Cell对象的xf属性中,xf为style index并不是style
- 在Row对象中需要增加public int getStyle(){ }方法,调用Styles#getStyleByIndex方法使用第2步的style index换取style
- 然后就可以Styles#getNumFmt, Styles#getFill, Styles#getFont, Styles#getBorder 等方法获取具体的样式了
有兴趣的话可以尝试实现xlsx格式的样式解析
请教一下这种用法有问题吗?

写法是正确的,但是基建好像不对,并没有拿到样式,晚点我尝试下
不好意思可能误导你了,因为Styles.load(s);并没有装载到styleIndex,所以styles.getStyleByIndex(cell.xf)返回的是-1,无法拿到样式,需要实现Styles#load方法才可以的
不好意思可能误导你了,因为Styles.load(s);并没有装载到styleIndex,所以styles.getStyleByIndex(cell.xf)返回的是-1,无法拿到样式,需要实现Styles#load方法才可以的
Styles#load 要实现才可以,是不是意味着现在没有任何方法可以拿到任何样式? 有什么变通的方案没,希望能尽快拿到 numfmt 跑通业务。
我稍后推一个分支获取numfmts
int style = styles.of(cell.xf); NumFmt numFmt= styles.getNumFmt(style);
这样写就可以了
int style = styles.of(cell.xf); NumFmt numFmt= styles.getNumFmt(style); 这样写就可以了
多谢支持,已经取到了。 int style = styles.of(cell.xf),这里获取的 style 是不是就是完整的 style,只是暂时只有 NumFmt 的位数设置了,其他的还没有实现而没有设置。 我需要做个样式复制的功能,所以如果此 style 代表的就是完整的 style,那就可以直接使用,后续再去补充其他位数的实现。
你理解的很正确,这个样式就是全部的样式,只是现在只有numfmt才会放进map。 但是这样样式并不能用于复制,样式需要先addNumFmt进目标styles才会生效,而这个值大概率和原值不同,换句话说你不能直接在目标styles上使用of方法添加新样式
继续请教,从 a excel 的单元格获取到多个 style(styles.of(cell.xf)),然后现在需要写 b excel,希望能用从 a 里的 style 直接设置 b。不知道最佳实践如何处理比较好。 或者说这个场景现在框架未考虑,需要去实现?
需要完全解析才可以的,当前虽然可以拿到完全的样式值但是各个主体(字体,边框,填充,和格式化)并没有解析,通过当前的style只能得到水平/垂直对其,是否折行。
当然如果目标和原有着相同样式是可以通过of方法复制的,我说的是整个styles.xml完全一样,比如模版
今天网络访问不了github,我的建议是如果是模版的话优先使用easyexcel,它有完整的功能,eec有计划支持模版但短时间还无法发布,部分对内存和性能要求的功能可以继续使用eec
fix#312分支增增加对样式的解析但并未经过完全测试,你可以fork本项目并切到fix#312分支进行BUG修改,欢迎提交PR。
需要完全解析才可以的,当前虽然可以拿到完全的样式值但是各个主体(字体,边框,填充,和格式化)并没有解析,通过当前的style只能得到水平/垂直对其,是否折行。 当然如果目标和原有着相同样式是可以通过of方法复制的,我说的是整个styles.xml完全一样,比如模版
目标是我要生成的一个新文件,在新文件内写入内容,这种目标的styles.xml和模板不相同呀,能请教下你说的模块复制怎么处理吗,我是需要在生成的新文件内完全复用模板的样式。
你可以clone本项目并切到fix#312分支,该分支已实现获取单元格样式(row#getStyle ),可以install到本地使用
可以使用如下代码获取单元格样式
reader.sheet(0).reset().dataRows().forEach(row -> {
Cell cell = row.getCell(3);
int style = row.getCellStyle(cell);
NumFmt numFmt = row.getStyles().getNumFmt(style);
println(numFmt);
Font font = row.getStyles().getFont(style);
println(font);
Fill fill = row.getStyles().getFill(style);
println(fill);
});
如果只是简单字段替换的话可以参照TemplateTest测试类,只是目前还不支持表格,可自行扩展实现
@Test public void testTemplate() throws IOException {
try (InputStream fis = Files.newInputStream(testResourceRoot().resolve("template.xlsx"))) {
// Map data
Map<String, Object> map = new HashMap<>();
map.put("name", "guanquan.wang");
map.put("score", 90);
map.put("date", "2019-05-05");
map.put("desc", "暑假");
// java bean
// BindEntity entity = new BindEntity();
// entity.score = 67;
// entity.name = "张三";
// entity.date = new Date(System.currentTimeMillis());
new Workbook("模板导出")
.withTemplate(fis, map)
.writeTo(defaultTestPath);
}
}
testTemplate
我其实就是想用模板这种,但我的模板只有样式,和你预设的这种场景有点不匹配。
比如模板原来有 a,b 两个 sheet,我可以基于当前这个模板再去创建 c, d, e 三个 sheet,希望在写入 c,d,e三个 sheet 的的cell可以直接用 a,b 两个 sheet 中的样式,写完 c, d, e sheet 后再去删除 a, b 两个sheet。
可以加我微信聊吧,听你描述好像并不太难的样子,如果没有敏感信息的话可以将样本和预期的效果发邮箱我详细看下,邮箱地址点我头像查看
可以加我微信聊吧,听你描述好像并不太难的样子,如果没有敏感信息的话可以将样本和预期的效果发邮箱我详细看下,邮箱地址点我头像查看
已发示例邮件
微信号已在邮箱中回复,如何收件箱没有的话就去垃圾邮件中找找
我写了一个示例,读取模板某一行做为范本追写数据
- 自定义模板实现
public static class MyEmbedTemplate<T> extends EmbedTemplate {
private int sheetId, copyRowNum; // 指定要复制的Sheet和要复制的行(从1开始)
private final Supplier<List<T>> supplier; // 获取数据用
private ExtBufferedWriter bw; // 写文件
private int rowNum; // 记录当前行号
private TemplateRow tr;
public MyEmbedTemplate(Path zipPath, Workbook wb, int sheetId, int copyRowNum, Supplier<List<T>> supplier) {
super(zipPath, wb);
this.sheetId = sheetId;
this.copyRowNum = copyRowNum;
this.supplier = supplier;
this.rowNum = copyRowNum;
}
@Override
protected int bindSstData() {
return 0;
}
@Override
protected int bindSheetData() {
// Read content
Path contentTypePath = zipPath.resolve("[Content_Types].xml");
SAXReader reader = new SAXReader();
Document document;
try {
document = reader.read(Files.newInputStream(contentTypePath));
} catch (DocumentException | IOException e) {
return 0;
}
// Find Override
List<Element> overrides = document.getRootElement().elements("Override");
for (Element e : overrides) {
if (Const.ContentType.SHEET.equals(e.attributeValue("ContentType"))) {
Path sheetPath = zipPath.resolve(e.attributeValue("PartName").substring(1));
String sheetId = sheetPath.getFileName().toString();
sheetId = sheetId.substring(5, sheetId.length() - 4);
System.out.println("读取 " + sheetId);
// 只操作第一个Sheet页
if (sheetId.equals(String.valueOf(this.sheetId))) {
try {
writeSheet1(sheetPath);
} catch (IOException ex) {
throw new ExcelWriteException("Write data failed", ex);
}
}
}
}
return 0;
}
// Write template sheet, returns rows
protected int writeSheet1(Path path) throws IOException {
// 解析模板行
this.tr = parseTemplateRow(path);
bw = new ExtBufferedWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8));
// 写头部
bw.write(tr.header);
// 循环写行数据
List<T> list = supplier.get();
if (list != null && !list.isEmpty()) {
// 解析实体与列进行匹配,这里可以借助于ListSheet
ListSheet<T> listSheet = new ListSheet<>(list);
listSheet.setSheetWriter(new XMLWorksheetWriter());
listSheet.setWorkbook(new Workbook());
org.ttzero.excel.entity.Column[] columns = listSheet.getAndSortHeaderColumns();
Cell[] cells = tr.getCells();
for (org.ttzero.excel.entity.Column col : columns) {
if (col.getRealColIndex() <= cells.length) {
Cell c = cells[col.getRealColIndex() - 1];
Class<?> clazz = col.getClazz();
if (isString(clazz)) {
c.t = Cell.INLINESTR;
}
else if (isInt(clazz) || isLong(clazz) || isFloat(clazz) || isDouble(clazz) || isBigDecimal(clazz)) {
c.t = Cell.NUMERIC;
} else c.t = Cell.INLINESTR;
}
}
tr.columns = columns;
for (T t : list) writeRow(t);
for (; (list = supplier.get()) != null && !list.isEmpty();)
for (T t : list) writeRow(t);
} else bw.write(tr.tmp); // 如果没有数据的话写入原本的模板行
// 写尾部
bw.write(tr.tail);
bw.close();
return 0;
}
private TemplateRow parseTemplateRow(Path path) {
SAXReader reader = new SAXReader();
Document document;
try {
document = reader.read(Files.newInputStream(path));
} catch (DocumentException | IOException e) {
throw new ExcelWriteException("Miss the template Worksheet " + path);
}
List<Element> rows = document.getRootElement().element("sheetData").elements("row");
if (rows.size() < copyRowNum) {
throw new ExcelWriteException("Specify row " + copyRowNum + " is large than template sheet rows " + rows.size());
}
Element row = rows.get(copyRowNum - 1);
String span = Styles.getAttr(row, "spans");
String customHeight = Styles.getAttr(row, "customHeight"), ht = Styles.getAttr(row, "ht"); // 如果customHeight为1或者true则ht属性生效
String customFormat = Styles.getAttr(row, "customFormat"), rs = Styles.getAttr(row, "s"); // 如果customFormat为1或者true则rs属性生效
int i = span.indexOf(':');
int[] spans = i > 0 ? new int[]{Integer.parseInt(span.substring(0, i)), Integer.parseInt(span.substring(i + 1))} : new int[]{0, Integer.parseInt(span)};
int[] xfs = new int[spans[1]];
List<Element> list = row.elements();
for (Element c : list) {
String cr = Styles.getAttr(c, "r"), cs = Styles.getAttr(c, "s");
long r = ExcelReader.cellRangeToLong(cr);
int ci = (int) (r & 0x7FFF), s = StringUtil.isNotEmpty(cs) ? Integer.parseInt(cs) : 0;
xfs[ci - 1] = s;
}
String sheetData;
try {
InputStream is = Files.newInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int n;
byte[] bytes = new byte[1 << 11];
while ((n = is.read(bytes)) > 0) {
bos.write(bytes, 0, n);
}
sheetData = new String(bos.toByteArray(), 0, bos.size(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new ExcelWriteException("");
}
for (int n = 1; n <= copyRowNum; n++) i = sheetData.indexOf("<row ", i + 1);
int j = sheetData.indexOf("</row>", i) + 6;
String header = sheetData.substring(0, i), tmp = sheetData.substring(i, j), tail = sheetData.substring(j);
TemplateRow tr = new TemplateRow();
tr.header = header;
tr.tail = tail;
tr.tmp = tmp;
tr.ht = ht;
tr.customHeight = ("1".equals(customHeight) || "true".equalsIgnoreCase(customHeight)) && StringUtil.isNotEmpty(ht);
tr.rs = rs;
tr.customFormat = ("1".equals(customFormat) || "true".equalsIgnoreCase(customFormat)) && StringUtil.isNotEmpty(rs);
tr.calloc(spans[1]); // 分配列
Cell[] cells = tr.getCells();
int col = 0;
for (Cell c : cells) {
c.i = (short) ++col;
c.xf = xfs[col - 1]; // Style index
}
return tr;
}
private void writeRow(T o) throws IOException {
Cell[] cells = tr.getCells();
org.ttzero.excel.entity.Column[] columns = tr.columns;
int len = cells.length;
// 写行
startRow(len);
try {
boolean notNull = o != null;
for (int i = 0; i < columns.length; i++) {
Object e;
ListSheet.EntryColumn column = (ListSheet.EntryColumn) columns[i];
// Clear cells
Cell cell = cells[column.getRealColIndex() - 1];
if (column.isIgnoreValue())
e = null;
else if (notNull) {
if (column.getMethod() != null)
e = column.getMethod().invoke(o);
else if (column.getField() != null)
e = column.getField().get(o);
else e = o;
}
else e = null;
if (e != null) {
if (cell.t == Cell.NUMERIC) {
writeNumeric(Long.parseLong(e.toString()), rowNum, column.getRealColIndex(), cell.xf);
} else {
writeString(e.toString(), rowNum, column.getRealColIndex(), cell.xf);
}
} else writeNull(rowNum, column.getRealColIndex(), cell.xf);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ExcelWriteException(e);
}
// 行尾
endRow();
}
protected int startRow(int columns) throws IOException {
bw.write("<row r=\"");
bw.writeInt(rowNum);
// default data row height 16.5
bw.write("\" spans=\"1:");
bw.writeInt(columns);
if (tr.customHeight) {
bw.write("\" ht=\"");
bw.write(tr.ht);
}
if (tr.customFormat) {
bw.write("\" s=\"");
bw.write(tr.rs);
}
bw.write("\">");
return rowNum;
}
protected void endRow() throws IOException {
rowNum++; // 行+1
bw.write("</row>");
}
protected void writeNumeric(long l, int row, int col, int xf) throws IOException {
bw.write("<c r=\"");
bw.write(int2Col(col));
bw.writeInt(row);
bw.write("\" s=\"");
bw.writeInt(xf);
bw.write("\"><v>");
bw.write(l);
bw.write("</v></c>");
}
protected void writeNull(int row, int col, int xf) throws IOException {
bw.write("<c r=\"");
bw.write(int2Col(col));
bw.writeInt(row);
bw.write("\" s=\"");
bw.writeInt(xf);
bw.write("\"/>");
}
protected void writeString(String s, int row, int col, int xf) throws IOException {
// The limit characters per cell check
if (s != null && s.length() > Const.Limit.MAX_CHARACTERS_PER_CELL) {
throw new ExcelWriteException("Characters per cell out of limit. size=" + s.length()
+ ", limit=" + Const.Limit.MAX_CHARACTERS_PER_CELL);
}
bw.write("<c r=\"");
bw.write(int2Col(col));
bw.writeInt(row);
if (StringUtil.isEmpty(s)) {
bw.write("\" s=\"");
bw.writeInt(xf);
bw.write("\"/>");
} else {
bw.write("\" t=\"inlineStr\" s=\"");
bw.writeInt(xf);
bw.write("\"><is><t>");
bw.escapeWrite(s); // escape text
bw.write("</t></is></c>");
}
}
public static class TemplateRow extends Row {
public String header, tail, tmp;
public String ht, rs;
public boolean customHeight, customFormat;
public org.ttzero.excel.entity.Column[] columns;
}
}
- 指定需要复制的worksheet的列
@Test public void testTemplate312() throws IOException {
try (InputStream fis = Files.newInputStream(Paths.get("template.xlsx"))) {
Workbook workbook = new Workbook("Issue#312").withTemplate(fis, new Object()); // 这里不需要指定数据
// 查询条件
GoodsQueryVo vo = new GoodsQueryVo();
vo.setPage(1);
vo.setPageSize(100);
workbook.setWorkbookWriter(new XMLWorkbookWriter() {
@Override
public Path template() throws IOException {
// Store template stream as zip file
Path temp = FileUtil.mktmp(Const.EEC_PREFIX);
ZipUtil.unzip(workbook.getTemplate(), temp);
// Bind data 其中参数1表示第一个Sheet页 4表示第4行做为模板行
EmbedTemplate bt = new MyEmbedTemplate<>(temp, workbook, 1, 4, () -> {
List<Goods> goods = goodsService.query(vo); // 查询数据
vo.setPage(vo.getPage() + 1); // 页码+1
return goods;
});
if (bt.check()) { // Check files
bt.bind(workbook.getBind());
}
// Zip compress
Path zipFile = ZipUtil.zipExcludeRoot(temp, temp);
// Delete source files
FileUtil.rm_rf(temp.toFile(), true);
// Close shared string table
workbook.getSst().close();
return zipFile;
}
}).writeTo(defaultTestPath);
}
}
如果想要使用其它单元格的样式,可以通过ExcelReader读取拿到Cell#xf,写数据的时候使用该xf即可。
导出的对象字段上需要添加@ExcelColumn(colIndex = x) 顺序与模板一样
刚刚看到这个问题,刚好我写过应该能解决你的问题,希望对你有帮助,以下代码是完整复刻sheet,即完整复制样式
//样式类 @NoArgsConstructor @Data @AllArgsConstructor public class MyStyleProcessor implements StyleProcessor { List<CellStyle> cellStyles; @Override public int build(Object o, int style, Styles styles) { CellStyle cellStyle = cellStyles.get(0); cellStyles.remove(0); return cellStyle.getStyle(style,styles); }
}
//样式类 @Data @Accessors(chain = true) @NoArgsConstructor public class CellStyle {
public CellStyle(String name,int style,Styles styles){
// 获取字体
this.font = styles.getFont(style);
// 获取边框
this.border = styles.getBorder(style);
// 获取填充
this.fill = styles.getFill(style);
// 获取格式化
this.numFmt = styles.getNumFmt(style);
this.horizontal = styles.getHorizontal(style);
this.vertica = styles.getVertical(style);
}
public int getStyle(int style,Styles styles){
styles.clearFont(style);
styles.clearFill(style);
styles.clearBorder(style);
if (numFmt==null){
return styles.addFont(font)|styles.addBorder(border)|styles.addFill(fill)|Horizontals.CENTER|Verticals.CENTER;
}
styles.clearNumFmt(style);
return styles.addNumFmt(numFmt)|styles.addFont(font)|styles.addBorder(border)|styles.addFill(fill)|Horizontals.CENTER|Verticals.CENTER;
}
private String name;
private Font font;
private Border border;
private Fill fill;
private NumFmt numFmt;
private int horizontal = Horizontals.CENTER;
private int vertica = Verticals.CENTER;
}
//核心代码 public static void main(String[] args) {
Path path = Paths.get(你的模板excel);
try (ExcelReader reader = ExcelReader.read(path,COPY_ON_MERGED)) {
Workbook wb = new Workbook();
ArrayList<Map<String,?>> objects = new ArrayList<>();
List<Column> columns = new ArrayList<>(); //每一列
List<List<CellStyle>> cellStylesList = new ArrayList<>(); //每一列
Sheet sheet = reader.sheet(0); //MergeSheet
List<Dimension> mergeCells = getMergeCells(sheet);
reader.sheet(0).rows().forEach(row -> {
HashMap<String, String> dataMap = new HashMap<>();
for (int i = columns.size(); i < row.getLastColumnIndex(); i++) { //创建列
ArrayList<CellStyle> cellStyles = new ArrayList<>();
columns.add(new Column(i+"",i+"").setStyleProcessor(new MyStyleProcessor(cellStyles)));
cellStylesList.add(cellStyles);
}
for (int i = 0; i < row.getLastColumnIndex(); i++) { //行的每一格
Cell cell = row.getCell(i);
String name = row.getString(cell);
dataMap.put(i+"", StringUtils.isNoneBlank(name)? name:"");
// 第一步 获取Styles对象
Styles styles = row.getStyles();
// 第二步 获取指定单元格样式
int style = row.getCellStyle(cell);
cellStylesList.get(i).add(new CellStyle(name,style,styles));
}
objects.add(dataMap);
});
ListMapSheet tmpSheet = new ListMapSheet("sheet", objects, columns.toArray(new Column[columns.size()]));
tmpSheet.ignoreHeader();
tmpSheet.putExtProp(Const.ExtendPropertyKey.MERGE_CELLS, mergeCells);
wb.addSheet(tmpSheet);
wb.writeTo(new File(excel输出路径));
} catch (IOException e) {
e.printStackTrace();
}
}
public static List<Dimension> getMergeCells(Object o){ //获取合并格
try {
Method[] methods = ReflectUtils.getMethods(o.getClass()); //TODO 注意这里要使用 getDeclaredMethods,工具类使用的是hutool
Method method = Arrays.stream(methods).filter(m -> m.getName().equals("getMergeCells")).findFirst().get();
method.setAccessible(true);
return (List<Dimension>) method.invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@zhangmuto 获取哪些单元格被合并了可以通过MergeSheet#getMergeCells直接获取,像这样List<Dimension> mergeCells = reader.sheet(0).asMergeSheet().getMergeCells();,不需要反射遍历。
像上面的代码你读取时候已经指定了COPY_ON_MERGED属性,所以调用asMergedSheet并不会增加额外开销,这里使用类型强转也可以,如果未指定COPY_ON_MERGED属性调用asMergedSheet方法会跳到尾部读取合并信息,所以asMergeSheet方法是安全的。