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

org.ttzero.excel.entity.style.Styles Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, [email protected] All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ttzero.excel.entity.style;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ttzero.excel.manager.TopNS;
import org.ttzero.excel.entity.Storable;
import org.ttzero.excel.manager.Const;
import org.ttzero.excel.util.FileUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.ttzero.excel.util.StringUtil;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import static org.ttzero.excel.util.StringUtil.isEmpty;
import static org.ttzero.excel.util.StringUtil.isNotEmpty;

/**
 * Excel样式包含格式化{@link NumFmt}、字体{@link Font}、填充{@link Fill}、边框{@link Border}、
 * 垂直对齐{@link Verticals}和水平对齐{@link Horizontals}以及自动折行组成,样式存放在共享区域供多个工作表共用,
 * 每个样式值都是由这7种值进行组合而来。全局样式库统管样式读写,添加样式时会查询当前样式是否已存在,如果存在则直接返回该样式在库中的索引值
 * 否则添加到样式库末尾
 *
 * 

本工具简化了样式设计,基础的4种样式由数组保存,单元格样式值由一个{@code int}值保存, * 它可以极大压缩内存消耗和快速查找,但短板是可用的样式减少,最多只能包含256个格式化,64个字体,填充和边框, * 对于日常的导出来说应该是够用的,复杂场景就需要考虑将{@code int}扩大到{@code long}

* *

由于使用2进制位点保存各个样式,所以要修改样式时必须先清除原位点上的值然后再添加新样式,否则位点在进行“或”运行时将保留全部{@code 1} * 致使样式错乱,可以直接使用位运算来处理样式,也可以通过{@code Styles.modify}开头的方法来修改

* *
 *  Bit  | Contents
 * ------+---------
 *  0, 1 | 自动折行
 *  1, 3 | 水平对齐
 *  4, 2 | 垂直对齐
 *  6, 6 | 边框
 * 12, 6 | 填充
 * 18, 6 | 字体
 * 24. 8 | 格式化
* * @author guanquan.wang on 2017/10/13. */ @TopNS(prefix = "", uri = Const.SCHEMA_MAIN, value = "styleSheet") public class Styles implements Storable { /** * LOGGER */ private static final Logger LOGGER = LoggerFactory.getLogger(Styles.class); private final Map map; private final AtomicInteger counter; private int[] styleIndex; private Document document; private List fonts; private List numFmts; private List fills; private List borders; /** * Cache the data/time format style index. * It's use for fast test the cell value is a data or time value */ private Set dateFmtCache; private Styles() { map = new HashMap<>(); counter = new AtomicInteger(); styleIndex = new int[10]; } /** * 向全局样式库中添加样式,添加前会先查询是否已有相同样式,如果有则直接返回该样式在全局样式库中的索引值, * 不存在则将该样式添加到样式库末尾 * * @param s 样式值 * @return 样式索引 */ public int of(int s) { int n = map.getOrDefault(s, -1); if (n == -1) { n = counter.getAndIncrement(); map.put(s, n); if (n >= styleIndex.length) { styleIndex = Arrays.copyOf(styleIndex, styleIndex.length << 1); } styleIndex[n] = s; } return n; } /** * 通过样式索引查询样式值 * * @param styleIndex 样式索引 * @return 样式值,查找失败时返回{@code -1} */ public int getStyleByIndex(int styleIndex) { if (styleIndex >= counter.get()) { return -1; } return styleIndex >= 0 ? this.styleIndex[styleIndex] : -1; } /** * Returns the number of styles * * @return the total styles */ public int size() { return map.size(); } public static final int INDEX_NUMBER_FORMAT = 24; public static final int INDEX_FONT = 18; public static final int INDEX_FILL = 12; public static final int INDEX_BORDER = 6; public static final int INDEX_VERTICAL = 4; public static final int INDEX_HORIZONTAL = 1; public static final int INDEX_WRAP_TEXT = 0; /** * Create a general style * * @return Styles */ public static Styles create() { Styles self = new Styles(); self.document = createDocument(); self.numFmts = new ArrayList<>(); self.fonts = new ArrayList<>(); Font font1 = new Font("Calibri", 11); font1.setScheme("minor"); font1.setCharset(Charset.GB2312); self.addFont(font1); String lang = Locale.getDefault().toLanguageTag(); // Add chinese font Font font2 = new Font("宋体", 11); // cn if ("zh-CN".equals(lang)) { font2.setCharset(Charset.GB2312); } else if ("zh-TW".equals(lang)) { if (StringUtil.isBlank(font2.getName())) font2.setName("新細明體"); font2.setCharset(Charset.CHINESEBIG5); } // Other charset self.addFont(font2); self.fills = new ArrayList<>(); self.addFill(new Fill(PatternType.none)); self.addFill(new Fill(PatternType.gray125)); self.borders = new ArrayList<>(); self.addBorder(Border.parse("none")); self.addBorder(new Border(BorderStyle.THIN, new Color(191, 191, 191))); // cellXfs self.of(0); // General return self; } /** * 修改“边框”样式 * * @param style 原样式值 * @param newBorder 新“边框”样式 * @return 新样式值 */ public int modifyBorder(int style, Border newBorder) { return Styles.clearBorder(style) | addBorder(newBorder); } /** * 修改“格式化”样式 * * @param style 原样式值 * @param newNumFmt 新“格式化”样式 * @return 新样式值 */ public int modifyNumFmt(int style, NumFmt newNumFmt) { return Styles.clearNumFmt(style) | addNumFmt(newNumFmt); } /** * 修改"自动折行"样式 * * @param style 原样式值 * @param newWrapText true: 自动折行 * @return 新样式值 */ public int modifyWrapText(int style, boolean newWrapText) { return newWrapText ? style | 1 : Styles.clearWrapText(style); } /** * 修改“垂直对齐”样式 * * @param style 原样式值 * @param newVertical 新“垂直对齐”样式,必须使用{@link Verticals}定义的静态值 * @return 新样式值 */ public int modifyVertical(int style, int newVertical) { return Styles.clearVertical(style) | newVertical; } /** * 修改“水平对齐”样式 * * @param style 原样式值 * @param newHorizontal 新“水平对齐”样式,必须使用{@link Horizontals}定义的静态值 * @return 新样式值 */ public int modifyHorizontal(int style, int newHorizontal) { return Styles.clearHorizontal(style) | newHorizontal; } /** * 修改“填充”样式 * * @param style 原样式值 * @param newFill 新“填充”样式 * @return 新样式值 */ public int modifyFill(int style, Fill newFill) { return Styles.clearFill(style) | addFill(newFill); } /** * 修改“字体”样式 * * @param style 原样式值 * @param newFont 新“字体”样式 * @return 新样式值 */ public int modifyFont(int style, Font newFont) { return Styles.clearFont(style) | addFont(newFont); } /** * Create a general style * * @return Styles */ public static Styles forReader() { Styles styles = new Styles(); styles.numFmts = new ArrayList<>(); styles.fonts = new ArrayList<>(); styles.fills = new ArrayList<>(); styles.borders = new ArrayList<>(); return styles; } /** * Load the style file from disk * * @param is the style {@code InputStream} * @return the {@link Styles} Object */ public static Styles load(InputStream is) { // load styles.xml SAXReader reader = SAXReader.createDefault(); Document document; try { document = reader.read(is); } catch (DocumentException e) { LOGGER.warn("Read the style failed and ignore the style to continue.", e); Styles self = forReader(); // Add a default font self.addFont(new Font("Arial", 11, Color.black)); return self; } Styles self = new Styles(); Element root = document.getRootElement(); // Parse Number format self.numFmts = NumFmt.domToNumFmt(root); // Indexed Colors(部分Excel的indexed颜色与标准有所不同,这部分颜色会定义在标签下) Element colors = root.element("colors"); if (colors != null) colors = colors.element("indexedColors"); if (colors != null && colors.nodeCount() > 0) { List sub = colors.elements(); Color[] indexedColors = new Color[sub.size()]; int i = 0; for (Element e : sub) indexedColors[i++] = parseColor(e); // Parse Fonts self.fonts = Font.domToFont(root, indexedColors); // Parse Fills self.fills = Fill.domToFill(root, indexedColors); // Parse Borders self.borders = Border.domToBorder(root, indexedColors); } else { // Parse Fonts self.fonts = Font.domToFont(root); // Parse Fills self.fills = Fill.domToFill(root); // Parse Borders self.borders = Border.domToBorder(root); } // Cell xf Element cellXfs = root.element("cellXfs"); List sub = cellXfs.elements(); int i = 0; for (Element e : sub) { int style = 0; // NumFmt String numFmtId = getAttr(e, "numFmtId"); // applyNumberFormat = getAttr(e, "applyNumberFormat"); if (StringUtil.isNotEmpty(numFmtId) && !"0".equals(numFmtId)) { style |= Integer.parseInt(numFmtId) << INDEX_NUMBER_FORMAT; } // Font String fontId = getAttr(e, "fontId"); // applyFont = getAttr(e, "applyFont"); if (StringUtil.isNotEmpty(fontId) && !"0".equals(fontId)) { style |= Integer.parseInt(fontId) << INDEX_FONT; } // Fill String fillId = getAttr(e, "fillId"); // applyFill = getAttr(e, "applyFill"); if (StringUtil.isNotEmpty(fillId) && !"0".equals(fillId)) { style |= Integer.parseInt(fillId) << INDEX_FILL; } // Border String borderId = getAttr(e, "borderId"); // applyBorder = getAttr(e, "applyBorder"); if (StringUtil.isNotEmpty(borderId) && !"0".equals(borderId)) { style |= Integer.parseInt(borderId) << INDEX_BORDER; } // Alignment Element alignment = e.element("alignment"); if (alignment != null) { String horizontal = getAttr(alignment, "horizontal"); int index; if (StringUtil.isNotEmpty(horizontal) && (index = StringUtil.indexOf(Horizontals._names, horizontal)) >= 0) { style |= index << INDEX_HORIZONTAL; } String vertical = getAttr(alignment, "vertical"); if (StringUtil.isNotEmpty(vertical) && (index = StringUtil.indexOf(Verticals._names, vertical)) >= 0) { style |= index << INDEX_VERTICAL; } else style |= Verticals.BOTTOM; String wrapText = getAttr(alignment, "wrapText"); style |= ("1".equals(wrapText) || "true".equalsIgnoreCase(wrapText) ? 1 : 0) << INDEX_WRAP_TEXT; } self.map.put(style, i); if (i >= self.styleIndex.length) { self.styleIndex = Arrays.copyOf(self.styleIndex, self.styleIndex.length << 1); } self.styleIndex[i] = style; i++; } self.counter.set(i); // Test number format for (Integer styleIndex : self.map.values()) { self.isDate(styleIndex); } return self; } /** * 添加“格式化”,对格式化串去重处理 * *

返回样式值中“格式化”部分的2进制值,拿到这个值后可以与其它部分值进行“或”运算以组成最终的样式值

* * @param numFmt 格式化{@link NumFmt} * @return 样式值中“格式化”部分的2进制值 */ public final int addNumFmt(NumFmt numFmt) { if (numFmt.getId() < 0 || numFmt.getId() > 58) { if (isEmpty(numFmt.getCode())) { throw new NullPointerException("NumFmt code"); } int index = BuiltInNumFmt.indexOf(numFmt.getCode()); // Build-in NumFmt if (index > -1) { numFmt.setId(index); } else { int i = numFmts.indexOf(numFmt); if (i <= -1) { int id; if (numFmts.isEmpty()) { id = 164; // customer id } else { id = numFmts.get(numFmts.size() - 1).getId() + 1; } numFmt.setId(id); numFmts.add(numFmt); } else { numFmt.setId(numFmts.get(i).getId()); } } } return numFmt.getId() << INDEX_NUMBER_FORMAT; } /** * 添加“字体” * *

返回样式值中“字体”部分的2进制值,拿到这个值后可以与其它部分值进行“或”运算以组成最终的样式值

* * @param font 字体{@link Font} * @return 样式值中“字体”部分的2进制值 */ public final int addFont(Font font) { if (isEmpty(font.getName())) { throw new IllegalArgumentException("Font name not support."); } int i = fonts.indexOf(font); if (i <= -1) { i = fonts.size(); fonts.add(font); } return i << INDEX_FONT; } /** * 添加“填充” * *

返回样式值中“填充”部分的2进制值,拿到这个值后可以与其它部分值进行“或”运算以组成最终的样式值

* * @param fill 填充{@link Font} * @return 样式值中“填充”部分的2进制值 */ public final int addFill(Fill fill) { int i = fills.indexOf(fill); if (i <= -1) { i = fills.size(); fills.add(fill); } return i << INDEX_FILL; } /** * 添加“边框” * *

返回样式值中“边框”部分的2进制值,拿到这个值后可以与其它部分值进行“或”运算以组成最终的样式值

* * @param border 边框{@link Border} * @return 样式值中“边框”部分的2进制值 */ public final int addBorder(Border border) { int i = borders.indexOf(border); if (i <= -1) { i = borders.size(); borders.add(border); } return i << INDEX_BORDER; } public static int[] unpack(int style) { int[] styles = new int[7]; styles[0] = style >>> INDEX_NUMBER_FORMAT; styles[1] = style << 8 >>> (INDEX_FONT + 8); styles[2] = style << 14 >>> (INDEX_FILL + 14); styles[3] = style << 20 >>> (INDEX_BORDER + 20); styles[4] = style << 26 >>> (INDEX_VERTICAL + 26); styles[5] = style << 28 >>> (INDEX_HORIZONTAL + 28); styles[6] = style << 31 >>> (INDEX_WRAP_TEXT + 31); return styles; } public static int pack(int[] styles) { return styles[0] << INDEX_NUMBER_FORMAT | styles[1] << INDEX_FONT | styles[2] << INDEX_FILL | styles[3] << INDEX_BORDER | styles[4] << INDEX_VERTICAL | styles[5] << INDEX_HORIZONTAL | styles[6] << INDEX_WRAP_TEXT ; } private static final String[] attrNames = { "numFmtId" , "fontId" , "fillId" , "borderId" , "vertical" , "horizontal" , "wrapText" , "applyNumberFormat" , "applyFont" , "applyFill" , "applyBorder" , "applyAlignment" }; /** * Write style to disk * * @param styleFile the storage path * @throws IOException if I/O error occur */ @Override public void writeTo(Path styleFile) throws IOException { if (document == null) document = createDocument(); Element root = document.getRootElement(); // Number format if (!numFmts.isEmpty()) { Element element = document.getRootElement().element("numFmts"); element.attribute("count").setValue(String.valueOf(numFmts.size())); for (NumFmt numFmt : numFmts) numFmt.toDom(element); } // Font if (!fonts.isEmpty()) { Element element = document.getRootElement().element("fonts"); element.attribute("count").setValue(String.valueOf(fonts.size())); for (Font font : fonts) font.toDom(element); } // Fill if (!fills.isEmpty()) { Element element = document.getRootElement().element("fills"); element.attribute("count").setValue(String.valueOf(fills.size())); for (Fill fill : fills) fill.toDom(element); } // Border if (!borders.isEmpty()) { Element element = document.getRootElement().element("borders"); element.attribute("count").setValue(String.valueOf(borders.size())); for (Border border : borders) border.toDom(element); } Element cellXfs = root.element("cellXfs").addAttribute("count", String.valueOf(map.size())); for (int i = 0, len = counter.get(); i < len; i++) { int[] styles = unpack(styleIndex[i]); Element newXf = cellXfs.addElement("xf"); newXf.addAttribute(attrNames[0], String.valueOf(styles[0])) .addAttribute(attrNames[1], String.valueOf(styles[1])) .addAttribute(attrNames[2], String.valueOf(styles[2])) .addAttribute(attrNames[3], String.valueOf(styles[3])) .addAttribute("xfId", "0") ; int start = 7; if (styles[0] > 0) { newXf.addAttribute(attrNames[start], "1"); } if (styles[1] > 0) { newXf.addAttribute(attrNames[start + 1], "1"); } if (styles[2] > 0) { newXf.addAttribute(attrNames[start + 2], "1"); } if (styles[3] > 0) { newXf.addAttribute(attrNames[start + 3], "1"); } if ((styles[4] | styles[5] | styles[6]) > 0) { newXf.addAttribute(attrNames[start + 4], "1"); } Element subEle = newXf.addElement("alignment").addAttribute(attrNames[4], Verticals._names[styles[4]]); if (styles[5] > 0) { subEle.addAttribute(attrNames[5], Horizontals._names[styles[5]]); } if (styles[6] > 0) { subEle.addAttribute(attrNames[6], "1"); } } FileUtil.writeToDiskNoFormat(document, styleFile); } public static Document createDocument() { DocumentFactory factory = DocumentFactory.getInstance(); TopNS ns = Styles.class.getAnnotation(TopNS.class); Element rootElement; if (ns != null) { rootElement = factory.createElement(ns.value(), ns.uri()[0]); } else { rootElement = factory.createElement("styleSheet", Const.SCHEMA_MAIN); } // number format rootElement.addElement("numFmts").addAttribute("count", "0"); // font rootElement.addElement("fonts").addAttribute("count", "0"); // fill rootElement.addElement("fills").addAttribute("count", "0"); // border rootElement.addElement("borders").addAttribute("count", "0"); // cellStyleXfs Element cellStyleXfs = rootElement.addElement("cellStyleXfs").addAttribute("count", "1"); cellStyleXfs.addElement("xf") // General style .addAttribute("borderId", "0") .addAttribute("fillId", "0") .addAttribute("fontId", "0") .addAttribute("numFmtId", "0") .addElement("alignment") .addAttribute("vertical", "center"); // cellXfs rootElement.addElement("cellXfs").addAttribute("count", "0"); // cellStyles Element cellStyles = rootElement.addElement("cellStyles").addAttribute("count", "1"); cellStyles.addElement("cellStyle") .addAttribute("builtinId", "0") .addAttribute("name", "常规") .addAttribute("xfId", "0"); return factory.createDocument(rootElement); } ////////////////////////clear style/////////////////////////////// /** * 清除样式中的“格式化” * * @param style 样式值 * @return 清除“格式化”后的样式 */ public static int clearNumFmt(int style) { return style & (-1 >>> 32 - INDEX_NUMBER_FORMAT); } /** * 清除样式中的“字体” * * @param style 样式值 * @return 清除“字体”后的样式 */ public static int clearFont(int style) { return style & ~((-1 >>> 32 - (INDEX_NUMBER_FORMAT - INDEX_FONT)) << INDEX_FONT); } /** * 清除样式中的“填充” * * @param style 样式值 * @return 清除“填充”后的样式 */ public static int clearFill(int style) { return style & ~((-1 >>> 32 - (INDEX_FONT - INDEX_FILL)) << INDEX_FILL); } /** * 清除样式中的“边框” * * @param style 样式值 * @return 清除“边框”后的样式 */ public static int clearBorder(int style) { return style & ~((-1 >>> 32 - (INDEX_FILL - INDEX_BORDER)) << INDEX_BORDER); } /** * 清除样式中的“垂直对齐” * * @param style 样式值 * @return 清除“垂直对齐”后的样式 */ public static int clearVertical(int style) { return style & ~((-1 >>> 32 - (INDEX_BORDER - INDEX_VERTICAL)) << INDEX_VERTICAL); } /** * 清除样式中的“水平对齐” * * @param style 样式值 * @return 清除“水平对齐”后的样式 */ public static int clearHorizontal(int style) { return style & ~((-1 >>> 32 - (INDEX_VERTICAL - INDEX_HORIZONTAL)) << INDEX_HORIZONTAL); } /** * 清除样式中的“自动折行” * * @param style 样式值 * @return 清除“自动折行”后的样式 */ public static int clearWrapText(int style) { return style & ~(-1 >>> 32 - (INDEX_HORIZONTAL - INDEX_WRAP_TEXT)); } ////////////////////////default border style///////////////////////////// public static int defaultCharBorderStyle() { return (1 << INDEX_BORDER) | (1 << INDEX_FONT) | Horizontals.CENTER; } public static int defaultStringBorderStyle() { return (1 << INDEX_BORDER) | (1 << INDEX_FONT) | Horizontals.LEFT; } public static int defaultIntBorderStyle() { return (1 << INDEX_BORDER) | (1 << INDEX_FONT) | Horizontals.RIGHT; } public static int defaultDoubleBorderStyle() { return (1 << INDEX_BORDER) | (1 << INDEX_FONT) | Horizontals.RIGHT; } ////////////////////////default style///////////////////////////// public static int defaultCharStyle() { return (1 << INDEX_FONT) | Horizontals.CENTER; } public static int defaultStringStyle() { return (1 << INDEX_FONT) | Horizontals.LEFT; } public static int defaultIntStyle() { return (1 << INDEX_FONT) | Horizontals.RIGHT; } public static int defaultDoubleStyle() { return (1 << INDEX_FONT) | Horizontals.RIGHT; } ////////////////////////////////Check style//////////////////////////////// /** * 判断样式是否包含“格式化" * * @param style 样式值 * @return true: 包含”格式化“ */ public static boolean hasNumFmt(int style) { return style >>> INDEX_NUMBER_FORMAT != 0; } /** * 判断样式是否包含“字体" * * @param style 样式值 * @return true: 包含”字体“ */ public static boolean hasFont(int style) { return true; // Font is required } /** * 判断样式是否包含“填充" * * @param style 样式值 * @return true: 包含”填充“ */ public static boolean hasFill(int style) { return style << 14 >>> (INDEX_FILL + 14) != 0; } /** * 判断样式是否包含“边框" * * @param style 样式值 * @return true: 包含”边框“ */ public static boolean hasBorder(int style) { return style << 20 >>> (INDEX_BORDER + 20) != 0; } /** * 判断样式是否包含“垂直对齐" * * @param style 样式值 * @return true: 包含”垂直对齐“(非默认) */ public static boolean hasVertical(int style) { return style << 26 >>> (INDEX_VERTICAL + 26) != 0; } /** * 判断样式是否包含“水平对齐" * * @param style 样式值 * @return true: 包含”水平对齐“(非默认) */ public static boolean hasHorizontal(int style) { return style << 28 >>> (INDEX_HORIZONTAL + 28) != 0; } /** * 判断样式是否自动折行 * * @param style 样式值 * @return true: 自动折行 */ public static boolean hasWrapText(int style) { return (style & 1) > 0; } ////////////////////////////////To object////////////////////////////////// /** * 获取样式中的格式化值 * * @param style 样式值 * @return 格式化或 {@code null} */ public NumFmt getNumFmt(int style) { int n = style >>> INDEX_NUMBER_FORMAT; if (n <= 0) return null; NumFmt fmt = null; // 优先从自定义队列中查找 for (NumFmt e : numFmts) { if (e.id == n) { fmt = e; break; } } // 不在内置队列中则从内置格式化队列中查找 if (fmt == null) { // 内置格式化不全可能返回null fmt = BuiltInNumFmt.get(n); if (fmt == null && n <= 58) fmt = new NumFmt().setId(n); } return fmt; } /** * 获取样式中的格式化 * * @param style 样式值 * @return 当前样式包含的格式化,不含格式化时返回{@code null} */ public Fill getFill(int style) { return fills.get(style << 14 >>> (INDEX_FILL + 14)); } /** * 获取样式中的字体 * * @param style 样式值 * @return 当前样式包含的字体,样式一定包含字体 */ public Font getFont(int style) { return fonts.get(Math.max(0, style << 8 >>> (INDEX_FONT + 8))); } /** * 获取样式中的边框 * * @param style 样式值 * @return 当前样式包含的边框,不含边框时返回{@code null} */ public Border getBorder(int style) { return borders.get(style << 20 >>> (INDEX_BORDER + 20)); } /** * 获取样式中的垂直对齐,参考范围{@link Verticals} * * @param style 样式值 * @return 当前样式包含的垂直对齐 */ public int getVertical(int style) { return style << 26 >>> (INDEX_VERTICAL + 26) << INDEX_VERTICAL; } /** * 获取样式中的水平对齐,参考范围{@link Horizontals} * * @param style 样式值 * @return 当前样式包含的水平对齐 */ public int getHorizontal(int style) { return style << 28 >>> (INDEX_HORIZONTAL + 28) << INDEX_HORIZONTAL; } /** * 获取样式中自动折行标记,标记为{@code 1}时表示自动折行 * * @param style 样式值 * @return 1: 自动折行 */ public int getWrapText(int style) { return style & 1; } /** * Returns the attribute value from Element * * @param element current element * @param attr the attr name * @return the attr value */ public static String getAttr(Element element, String attr) { return element != null ? element.attributeValue(attr) : null; } /** * Parse color tag * * @param element color tag * @return awt.Color or null */ public static Color parseColor(Element element) { if (element == null) return null; String rgb = getAttr(element, "rgb"), indexed = getAttr(element, "indexed") , auto = getAttr(element, "auto"), theme = getAttr(element, "theme"); Color c = null; // Standard Alpha Red Green Blue color value (ARGB). if (StringUtil.isNotEmpty(rgb)) { c = toColor(rgb); } // Indexed color value. Only used for backwards compatibility. // References a color in indexedColors. else if (StringUtil.isNotEmpty(indexed)) { // if indexed greater than 64 means auto. c = new BuildInColor(Integer.parseInt(indexed)); } // A boolean value indicating the color is automatic and system color dependent. else if ("1".equals(auto) || "true".equalsIgnoreCase(auto)) { c = new BuildInColor(64); } // Theme colors else if (StringUtil.isNotEmpty(theme)) { int t = 0; try { t = Integer.parseInt(theme); } catch (NumberFormatException ex) { } if (t < 0 || t > 11) { LOGGER.warn("Unknown theme color index {}", t); t = 0; } Color themeColor = ColorIndex.themeColors[t]; String tint = getAttr(element, "tint"); c = HlsColor.calculateColor(themeColor, tint); } return c; } /** * Test the style is data format * * @param styleIndex the style index * @return true if the style content data format */ public boolean isDate(int styleIndex) { // Test from cache if (dateFmtCache != null && dateFmtCache.contains(styleIndex)) return true; if (styleIndex > counter.get()) return false; int style = this.styleIndex[styleIndex]; int nf = style >> INDEX_NUMBER_FORMAT & 0xFF; // No number format if (nf == 0) return false; boolean isDate; NumFmt numFmt; // Test by numFmt code if (!(isDate = isBuildInDateFormat(nf)) && (numFmt = findFmtById(nf)) != null && (isNotEmpty(numFmt.getCode()))) { isDate = testCodeIsDate(numFmt.getCode()); } // Put into data/time format cache // Ignore the style code, Uniform use of 'yyyy-mm-dd hh:mm:ss' format output if (isDate) { if (dateFmtCache == null) dateFmtCache = new HashSet<>(); dateFmtCache.add(styleIndex); } return isDate; } // All indexes from 0 to 163 are reserved for built-in formats. // The first user-defined format starts at 164. private static boolean isBuildInDateFormat(int nf) { return nf < 164 && (nf >= 14 && nf <= 22 || nf >= 27 && nf <= 36 || nf >= 45 && nf <= 47 || nf >= 50 && nf <= 58 || nf == 81); } public static boolean testCodeIsDate(String code) { char[] chars = code.toCharArray(); int score = 0; byte[] byteScore = new byte[26]; for (int i = 0, size = chars.length; i < size; ) { char c = chars[i]; // To lower case if (c >= 65 && c <= 90) c += 32; int a = ++i; if (c == '[') { // Found the end char ']' for (; i < size && chars[i] != ']'; i++) ; int len = i - a + 1; // DBNum{n} if (len == 6 && chars[a] == 'D' && chars[a + 1] == 'B' && chars[a + 2] == 'N' && chars[a + 3] == 'u' && chars[a + 4] == 'm') { int n = chars[a + 5] - '0'; // If use "[DBNum{n}]" etc. as the Excel display format, you can use Chinese numerals. if (n != 1 && n != 2 && n != 3) break; } // Maybe is a LCID // [$-xxyyzzzz] // https://stackoverflow.com/questions/54134729/what-does-the-130000-in-excel-locale-code-130000-mean else if (i - a > 2 && chars[a] == '$' && chars[a + 1] == '-') { // Language & Calendar Identifier // String lcid = new String(chars, a + 2, i - a - 2); // Maybe the format is a data score = 50; } } else { switch (c) { case 'y': case 'm': case 'd': case 'h': case 's': for (; i < size && chars[i] == c; i++) ; byteScore[c - 'a'] += i - a + 1; break; case 'a': case 'p': if (a < size && chars[a] == 'm') byteScore[c - 'a'] += 1; break; default: } } } // Exclude case // y > 4 // h > 4 // s > 4 // am > 1 || pm > 1 if (byteScore[24] > 4 || byteScore[7] > 4 || byteScore[18] > 4 || byteScore[0] > 1 || byteScore[15] > 1) { return false; } // Calculating the score // Plus 5 points for each keywords score += byteScore[0] * 5; // am score += byteScore[3] * 5; // d score += byteScore[7] * 5; // h score += byteScore[12] * 5; // m score += byteScore[15] * 5; // pm score += byteScore[18] * 5; // s score += byteScore[24] * 5; // y // Addition calculation if consecutive keywords appear // y + m + d if (byteScore[24] > 0 && byteScore[12] > 0 && byteScore[3] > 0 && byteScore[24] + byteScore[12] + byteScore[3] >= 4) { score += 70; // y + m } else if (byteScore[24] > 0 && byteScore[12] > 0 && byteScore[24] + byteScore[12] >= 3) { score += 60; // m + d } else if (byteScore[12] > 0 && byteScore[3] > 0) { score += 60; // Code is yyyy or yy } else if (byteScore[24] == chars.length) { score += 60; } // h + m + s if (byteScore[7] > 0 && byteScore[12] > 0 && byteScore[18] > 0 && byteScore[7] + byteScore[12] + byteScore[18] > 3) { score += 70; // h + m } else if (byteScore[7] > 0 && byteScore[12] > 0) { score += 60; // m + s } else if (byteScore[12] > 0 && byteScore[18] > 0) { score += 60; } // am + pm if (byteScore[0] + byteScore[15] == 2) { score += 50; } return score >= 70; } // Find the number format in array private NumFmt findFmtById(int id) { if (numFmts == null || numFmts.isEmpty()) return null; NumFmt fmt = numFmts.get(numFmts.size() - 1); if (fmt.getId() > id) { int n = Collections.binarySearch(numFmts, new NumFmt(id, null)); return n >= 0 ? numFmts.get(n) : null; } return fmt.getId() == id ? fmt : null; } /** * Fast test cell value is data/time value * * @param styleIndex the style index * @return true if the style content data format * @deprecated Replace with {@link #isDate(int)} */ @Deprecated public boolean fastTestDateFmt(int styleIndex) { return dateFmtCache != null && dateFmtCache.contains(styleIndex); } /** * Append a date format back into cache * * @param xf the XFRecord id */ public void addDateFmtCache(int xf) { if (dateFmtCache == null) dateFmtCache = new HashSet<>(); dateFmtCache.add(xf); } /** * Converts a String to an integer and returns the * specified opaque Color. This method handles string * formats that are used to represent octal and hexadecimal numbers. * * @param v hexadecimal numbers or color name * @return the new Color object. * @throws IllegalArgumentException if convert failed. */ public static Color toColor(String v) { Color color = null; final String source = v; if (v.charAt(0) != '#') { try { Field field = Color.class.getDeclaredField(v); color = (Color) field.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { if (v.length() > 6) v = v.substring(v.length() - 6); v = '#' + v; } } if (color == null) { try { color = Color.decode(v); } catch (NumberFormatException e) { throw new NumberFormatException("Color \"" + source + "\" not support."); } } return color; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy