Browse Source

Pull request #471: REPORT-33031 无插件PDF导出-HTML解析:块元素(如:div、p、不含table)支持 border 属性“全家桶”

Merge in CORE/base-third from ~HUGH.C/base-third:feature/10.0 to feature/10.0

* commit '252c889448d5082248441ed11a3a7830d5706c3e':
  REPORT-33031 review 改进
  REPORT-33031 无插件PDF导出-HTML解析:块元素(如:div、p)支持 border 属性“全家桶”
research/11.0
Hugh.C 4 years ago
parent
commit
7f63ba364e
  1. 11
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java
  2. 39
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Border.java
  3. 106
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderAttribute.java
  4. 79
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderEnum.java
  5. 105
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java
  6. 16
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java
  7. 17
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java
  8. 7
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java
  9. 34
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java
  10. 83
      fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BorderUtils.java

11
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java

@ -49,6 +49,7 @@
package com.fr.third.v2.lowagie.text;
import com.fr.third.v2.lowagie.text.html.BorderAttribute;
import java.util.HashMap;
/**
@ -110,6 +111,8 @@ public class Paragraph extends Phrase {
protected HashMap attributes = new HashMap();
private BorderAttribute borderAttr;
public HashMap getAttributes() {
return attributes;
}
@ -527,4 +530,12 @@ public class Paragraph extends Phrase {
return this.background;
}
public BorderAttribute getBorderAttr() {
return borderAttr;
}
public void setBorderAttrs(BorderAttribute borderAttrs) {
this.borderAttr = borderAttrs;
}
}

39
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Border.java

@ -0,0 +1,39 @@
package com.fr.third.v2.lowagie.text.html;
import java.awt.Color;
/**
* @author Hugh.C
* @version 1.0
* Created by Hugh.C on 2020/6/15
*/
public class Border {
private float width;
private String style;
private Color color = Color.BLACK;
public float getWidth() {
return width;
}
public void setWidth(float width) {
this.width = width;
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}

106
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderAttribute.java

@ -0,0 +1,106 @@
package com.fr.third.v2.lowagie.text.html;
import java.awt.Color;
import java.util.Map;
/**
* @author Hugh.C
* @version 1.0
* Created by Hugh.C on 2020/6/15
*/
public class BorderAttribute {
protected Border top = new Border();
protected Border right = new Border();
protected Border bottom = new Border();
protected Border left = new Border();
public Border getTop() { return top; }
public Border getRight() {
return right;
}
public Border getBottom() {
return bottom;
}
public Border getLeft() {
return left;
}
/**
* 将解析好的边框属性转化为 BorderAttribute
*
* @param map
* @return
*/
public BorderAttribute applyBorderAttr(Map<String, String> map) {
if (null == map || map.isEmpty()) {
return this;
}
String topWidth = map.get(Markup.CSS_KEY_BORDERWIDTHTOP);
String style = map.get(Markup.CSS_KEY_BORDERSTYLETOP);
if (verify(topWidth, style)) {
Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORTOP));
top.setWidth(CSSUtils.parseFloat(topWidth));
top.setStyle(style);
top.setColor(null == color ? Color.BLACK : color);
}
String rightWidth = map.get(Markup.CSS_KEY_BORDERWIDTHRIGHT);
style = map.get(Markup.CSS_KEY_BORDERSTYLERIGHT);
if (verify(rightWidth, style)) {
Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORRIGHT));
right.setWidth(CSSUtils.parseFloat(rightWidth));
right.setStyle(style);
right.setColor(null == color ? Color.BLACK : color);
}
String bottomWidth = map.get(Markup.CSS_KEY_BORDERWIDTHBOTTOM);
style = map.get(Markup.CSS_KEY_BORDERSTYLEBOTTOM);
if (verify(bottomWidth, style)) {
Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORBOTTOM));
bottom.setWidth(CSSUtils.parseFloat(bottomWidth));
bottom.setStyle(style);
bottom.setColor(null == color ? Color.BLACK : color);
}
String leftWidth = map.get(Markup.CSS_KEY_BORDERWIDTHLEFT);
style = map.get(Markup.CSS_KEY_BORDERSTYLELEFT);
if (verify(leftWidth, style)) {
Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORLEFT));
left.setWidth(CSSUtils.parseFloat(leftWidth));
left.setStyle(style);
left.setColor(null == color ? Color.BLACK : color);
}
return this;
}
/**
* 将边框属性 拼接成 html style 中的值
*
* @return
*/
public String toStyleAttr() {
StringBuffer sb = new StringBuffer();
sb.append(border2StyleAttr(Markup.CSS_KEY_BORDERTOP, top)).append(border2StyleAttr(Markup.CSS_KEY_BORDERRIGHT, right))
.append(border2StyleAttr(Markup.CSS_KEY_BORDERBOTTOM, bottom)).append(border2StyleAttr(Markup.CSS_KEY_BORDERLEFT, left));
return sb.toString();
}
private String border2StyleAttr(String key, Border border) {
if (!verify(border)) {
return "";
}
Color color = null == border.getColor() ? Color.BLACK : border.getColor();
StringBuilder sb = new StringBuilder();
sb.append(key).append(":").append(border.getWidth()).append("px ")
.append(border.getStyle()).append(" ").append(null == color ? "" : "rgb(" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ");");
return sb.toString();
}
private boolean verify(String width, String style) {
return null != width && width.length() > 0 && null != style && style.length() > 0;
}
private boolean verify(Border border) {
return null != border && border.getWidth() > 0 && null != border.getStyle();
}
}

79
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderEnum.java

@ -0,0 +1,79 @@
package com.fr.third.v2.lowagie.text.html;
import java.util.List;
import java.util.Map;
/**
* @author Hugh.C
* @version 1.0
* Created by Hugh.C on 2020/6/15
*/
public enum BorderEnum {
TOP {
@Override
public void fillAttr(Map<String, String> map, List<String> borderAttr) {
map.put(Markup.CSS_KEY_BORDERWIDTHTOP, borderAttr.get(0));
map.put(Markup.CSS_KEY_BORDERSTYLETOP, borderAttr.get(1));
map.put(Markup.CSS_KEY_BORDERCOLORTOP, borderAttr.get(2));
}
},
RIGHT {
@Override
public void fillAttr(Map<String, String> map, List<String> borderAttr) {
map.put(Markup.CSS_KEY_BORDERWIDTHRIGHT, borderAttr.get(0));
map.put(Markup.CSS_KEY_BORDERSTYLERIGHT, borderAttr.get(1));
map.put(Markup.CSS_KEY_BORDERCOLORRIGHT, borderAttr.get(2));
}
},
BOTTOM {
@Override
public void fillAttr(Map<String, String> map, List<String> borderAttr) {
map.put(Markup.CSS_KEY_BORDERWIDTHBOTTOM, borderAttr.get(0));
map.put(Markup.CSS_KEY_BORDERSTYLEBOTTOM, borderAttr.get(1));
map.put(Markup.CSS_KEY_BORDERCOLORBOTTOM, borderAttr.get(2));
}
},
LEFT {
public void fillAttr(Map<String, String> map, List<String> borderAttr) {
map.put(Markup.CSS_KEY_BORDERWIDTHLEFT, borderAttr.get(0));
map.put(Markup.CSS_KEY_BORDERSTYLELEFT, borderAttr.get(1));
map.put(Markup.CSS_KEY_BORDERCOLORLEFT, borderAttr.get(2));
}
},
DEFAULT {
@Override
public void fillAttr(Map<String, String> map, List<String> borderAttr) {
}
};
public static BorderEnum get(String str) {
switch (str) {
case Markup.CSS_KEY_BORDERTOP:
return TOP;
case Markup.CSS_KEY_BORDERRIGHT:
return RIGHT;
case Markup.CSS_KEY_BORDERBOTTOM:
return BOTTOM;
case Markup.CSS_KEY_BORDERLEFT:
return LEFT;
default:
return DEFAULT;
}
}
public abstract void fillAttr(Map<String, String> map, List<String> borderAttr);
public void dealAttr(Map<String, String> map, List<String> borderAttr) {
if (null == map || null == borderAttr || borderAttr.size() > 3 || borderAttr.size() < 2) {
return;
}
//保证 borderAttr 大小为3,即 线条粗细、样式、颜色都需要具备
if (2 == borderAttr.size()) {
borderAttr.add("black");
}
fillAttr(map,borderAttr);
}
}

105
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java

@ -53,7 +53,10 @@
package com.fr.third.v2.lowagie.text.html;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* A class that contains all the possible tagnames and their attributes.
@ -173,12 +176,40 @@ public class Markup {
/** the CSS tag for the margin of an object */
public static final String CSS_KEY_PADDINGBOTTOM = "padding-bottom";
public static final String CSS_KEY_BORDERTOP = "border-top";
public static final String CSS_KEY_BORDERRIGHT= "border-right";
public static final String CSS_KEY_BORDERBOTTOM= "border-bottom";
public static final String CSS_KEY_BORDERLEFT= "border-left";
public static final String CSS_KEY_BORDERSTYLETOP = "border-top-style";
public static final String CSS_KEY_BORDERSTYLERIGHT= "border-right-style";
public static final String CSS_KEY_BORDERSTYLEBOTTOM= "border-bottom-style";
public static final String CSS_KEY_BORDERSTYLELEFT= "border-left-style";
public static final String CSS_KEY_BORDERCOLORTOP = "border-top-color";
public static final String CSS_KEY_BORDERCOLORRIGHT= "border-right-color";
public static final String CSS_KEY_BORDERCOLORBOTTOM= "border-bottom-color";
public static final String CSS_KEY_BORDERCOLORLEFT= "border-left-color";
public static final String CSS_KEY_BORDER = "border";
/** the CSS tag for the margin of an object */
public static final String CSS_KEY_BORDERCOLOR = "border-color";
/** the CSS tag for the margin of an object */
public static final String CSS_KEY_BORDERWIDTH = "border-width";
public static final String CSS_KEY_BORDERSTYLE = "border-style";
/** the CSS tag for the margin of an object */
public static final String CSS_KEY_BORDERWIDTHLEFT = "border-left-width";
@ -507,4 +538,78 @@ public class Markup {
return result.toString();
}
/**
* 解析类似于 border:1px solid #000 中的值
*
* @param attr 1px solid #000 or 1px solid
* @return list: 按粗细样式颜色的顺序返回 没有则为null
*/
public static List<String> parseBorderAttr(String attr) {
if (null == attr) {
return null;
}
List<String> list = new ArrayList<String>(3);
StringTokenizer st = new StringTokenizer(attr);
while (st.hasMoreElements()) {
list.add(st.nextToken());
}
switch (list.size()) {
case 2:
// 如 border:1px solid ,则默认颜色为黑色
list.add("black");
break;
case 3:
// 如 border:1px solid black ,已经写全了,不用做额外的处理
break;
default:
// 如 border:1px ,此时浏览器是无法识别的
list = null;
break;
}
return list;
}
/**
* 解析类似于 margin: 10px 5px 15px 20px中的值
* NESW:北东南西上右下左
*
* @param attr 如10px 5px 15px 20px
* @return list: 按上左下右的顺序存放 要么四个方位的值都存在要么都不存在返回null
*/
public static List<String> parseNESW(String attr) {
if (null == attr) {
return null;
}
List<String> list = new ArrayList<String>(4);
StringTokenizer st = new StringTokenizer(attr);
while (st.hasMoreElements()) {
list.add(st.nextToken());
}
switch (list.size()) {
case 1:
//如 margin:1px ,代表上下左右 的margin值都是 1px
for (int i = 0; i < 3; i++) {
list.add(list.get(0));
}
break;
case 2:
//如 margin:1px 2px ,代表上下方位的margin值为1px, 左右方位的margin值为 2px
list.add(list.get(0));
list.add(list.get(1));
break;
case 3:
//如 margin:1px 2px 3px ,代表上下方位的margin值分别为1px、3px, 左右方位的margin值为 2px
list.add(list.get(1));
break;
case 4:
//如 margin:1px 2px 3px 4px,就不用管了
break;
default:
//如 margin:1px 2px 3px 4px 5px,超了就违反了规则,在浏览器中该属性失效
list = null;
break;
}
return list;
}
}

16
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java

@ -71,6 +71,22 @@ public class ParseIndentAttrUtils {
return indentAttribute;
}
public static IndentAttribute parseBorderAttribute(BorderAttribute borderAttr) {
IndentAttribute widthAttr = new IndentAttribute();
if (null == borderAttr) {
return widthAttr;
}
widthAttr.setTop(verify(borderAttr.getTop()) ? borderAttr.getTop().getWidth() : 0);
widthAttr.setRight(verify(borderAttr.getRight()) ? borderAttr.getRight().getWidth() : 0);
widthAttr.setBottom(verify(borderAttr.getBottom()) ? borderAttr.getBottom().getWidth() : 0);
widthAttr.setBottom(verify(borderAttr.getLeft()) ? borderAttr.getLeft().getWidth() : 0);
return widthAttr;
}
private static boolean verify(Border border) {
return null != border && 0 < border.getWidth() && null != border.getStyle();
}
public static void createIndent(StringBuffer stringBuffer, String attr, IndentAttribute indentAttribute){
stringBuffer.append(attr).append("-").append("left").append(":").append(indentAttribute.getLeft()).append("px;")
.append(attr).append("-").append("right").append(":").append(indentAttribute.getRight()).append("px;")

17
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java

@ -77,6 +77,9 @@ public class ChainedProperties {
}
public String getLastChainProperty(String key){
if (0 == chain.size()) {
return null;
}
Object obj[] = (Object[]) chain.get(chain.size()-1);
HashMap prop = (HashMap) obj[1];
return (String) prop.get(key);
@ -199,5 +202,19 @@ public class ChainedProperties {
return false;
}
/**
* 获取位于最后的块标签
*
* @return
*/
public String getLastBlockTag() {
for (int i = chain.size() - 1; i >= 0; --i) {
Object obj[] = (Object[]) chain.get(i);
if (HtmlConstants.BLOCK_ELEMENTS.contains(obj[0])) {
return (String) obj[0];
}
}
return null;
}
}

7
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java

@ -50,7 +50,9 @@
package com.fr.third.v2.lowagie.text.html.simpleparser;
import com.fr.third.v2.lowagie.text.html.BorderAttribute;
import com.fr.third.v2.lowagie.text.html.utils.BackgroundUtil;
import com.fr.third.v2.lowagie.text.html.utils.BorderUtils;
import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
@ -161,6 +163,11 @@ public class FactoryProperties {
}
}
String lastBlockTag = props.getLastBlockTag();
//这里解析的背景属性不能是table标签的
if (null != lastBlockTag && !HtmlTags.TABLE.equals(lastBlockTag)) {
p.setBorderAttrs(new BorderAttribute().applyBorderAttr(BorderUtils.parse2RulesMap(props, false)));
}
if(props.hasPropertyInChain("div", "text-indent")){
String ss = props.getPropertyFromChain("div", "text-indent");
p.setFirstLineIndent(Markup.parseLength(ss));

34
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java

@ -11,6 +11,8 @@ import java.util.List;
* @date 2018/5/2
*/
public class HtmlConstants {
//表格相关标签集合
public static final List<String> TABLE_LABEL_GROUP = new ArrayList<String>();
//块级元素集合
public static final List<String> BLOCK_ELEMENTS = new ArrayList<String>();
//行内元素集合
@ -18,7 +20,17 @@ public class HtmlConstants {
//支持解析的属性
public static final List<String> PADDING = new ArrayList<String>();
public static final List<String> MARGIN = new ArrayList<String>();
//边框属性
public static final List<String> BORDER = new ArrayList<String>();
public static final List<String> BORDER_WIDTH = new ArrayList<String>();
public static final List<String> BORDER_STYLE = new ArrayList<String>();
public static final List<String> BORDER_COLOR = new ArrayList<String>();
static {
TABLE_LABEL_GROUP.add(HtmlTags.TABLE);
TABLE_LABEL_GROUP.add(HtmlTags.ROW);
TABLE_LABEL_GROUP.add(HtmlTags.CELL);
TABLE_LABEL_GROUP.add(HtmlTags.HEADERCELL);
BLOCK_ELEMENTS.add(HtmlTags.DIV);
BLOCK_ELEMENTS.add(HtmlTags.UNORDEREDLIST);
BLOCK_ELEMENTS.add(HtmlTags.PARAGRAPH);
@ -41,5 +53,27 @@ public class HtmlConstants {
MARGIN.add(Markup.CSS_KEY_MARGINRIGHT);
MARGIN.add(Markup.CSS_KEY_MARGINTOP);
MARGIN.add(Markup.CSS_KEY_MARGINBOTTOM);
//按照上 右 下 左 的顺序排
BORDER.add(Markup.CSS_KEY_BORDERTOP);
BORDER.add(Markup.CSS_KEY_BORDERRIGHT);
BORDER.add(Markup.CSS_KEY_BORDERBOTTOM);
BORDER.add(Markup.CSS_KEY_BORDERLEFT);
BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHTOP);
BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHRIGHT);
BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHBOTTOM);
BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHLEFT);
BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLETOP);
BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLERIGHT);
BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLEBOTTOM);
BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLELEFT);
BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORTOP);
BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORRIGHT);
BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORBOTTOM);
BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORLEFT);
}
}

83
fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BorderUtils.java

@ -0,0 +1,83 @@
package com.fr.third.v2.lowagie.text.html.utils;
import com.fr.third.v2.lowagie.text.html.BorderEnum;
import com.fr.third.v2.lowagie.text.html.HtmlTags;
import com.fr.third.v2.lowagie.text.html.Markup;
import com.fr.third.v2.lowagie.text.html.simpleparser.ChainedProperties;
import com.fr.third.v2.lowagie.text.html.simpleparser.HtmlConstants;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Hugh.C
* @version 1.0
* Created by Hugh.C on 2020/5/27
*/
public class BorderUtils {
/**
* 解析border属性
*
* @param props
* @param lastTag true解析最后一个tag 的边框属性
*/
public static Map<String, String> parse2RulesMap(ChainedProperties props, boolean lastTag) {
Map<String, String> map = new HashMap<String, String>();
String value = lastTag ? props.getLastChainProperty(Markup.CSS_KEY_BORDER) : props.getPropertyFromChain(HtmlTags.DIV, Markup.CSS_KEY_BORDER);
//解析 border
if (null != value) {
List<String> list = Markup.parseBorderAttr(value);
for (BorderEnum border : BorderEnum.values()) {
border.dealAttr(map, list);
}
}
//解析 border-width
parseBorderAttr(Markup.CSS_KEY_BORDERWIDTH, HtmlConstants.BORDER_WIDTH, map, props, lastTag);
//解析 border-style
parseBorderAttr(Markup.CSS_KEY_BORDERSTYLE, HtmlConstants.BORDER_STYLE, map, props, lastTag);
//解析 border-color
parseBorderAttr(Markup.CSS_KEY_BORDERCOLOR, HtmlConstants.BORDER_COLOR, map, props, lastTag);
//解析 border-top|right|bottom|left
for (String border : HtmlConstants.BORDER) {
value = lastTag ? props.getLastChainProperty(border) : props.getPropertyFromChain(HtmlTags.DIV, border);
if (null != value) {
BorderEnum.get(border).dealAttr(map, Markup.parseBorderAttr(value));
}
}
//解析 border-top|right|bottom|left-width
parseBorderAttr(HtmlConstants.BORDER_WIDTH, map, props, lastTag);
//解析 border-top|right|bottom|left-style
parseBorderAttr(HtmlConstants.BORDER_STYLE, map, props, lastTag);
//解析 border-top|right|bottom|left-color
parseBorderAttr(HtmlConstants.BORDER_COLOR, map, props, lastTag);
return map;
}
private static void parseBorderAttr(List<String> attrKeys, Map<String, String> map, ChainedProperties props, boolean lastTag) {
String value = null;
for (String key : attrKeys) {
value = lastTag ? props.getLastChainProperty(key) : props.getPropertyFromChain(HtmlTags.DIV, key);
if (null != value) {
map.put(key, value);
}
}
}
private static void parseBorderAttr(String key, List<String> attrKeys, Map<String, String> map, ChainedProperties props, boolean lastTag) {
String value = lastTag ? props.getLastChainProperty(key) : props.getPropertyFromChain(HtmlTags.DIV, key);
if (null != value) {
List<String> list = Markup.parseNESW(value);
if (null != list) {
for (int i = 0; i < attrKeys.size() && i < list.size(); i++) {
map.put(attrKeys.get(i), list.get(i));
}
}
}
}
}
Loading…
Cancel
Save