package org.lobobrowser.html.style; import java.util.Vector; import org.lobobrowser.html.domimpl.HTMLElementImpl; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import cz.vutbr.web.css.StyleSheet; /** * Borrowed from CSSBox HTMLNorm.java This class provides a mechanism of * converting some HTML presentation atributes to the CSS styles and other * methods related to HTML specifics. */ public class StyleElements { public static StyleSheet convertAttributesToStyles(final Node n) { if (n.getNodeType() == Node.ELEMENT_NODE) { final HTMLElementImpl el = (HTMLElementImpl) n; //Analyze HTML attributes String attrs = ""; final String tagName = el.getTagName(); if ("TABLE".equalsIgnoreCase(tagName)) { //setting table and cell borders attrs = getTableElementStyle(el, attrs); } else if ("FONT".equalsIgnoreCase(tagName)) { //Text properties attrs = getFontElementStyle(el, attrs); } else if ("CANVAS".equalsIgnoreCase(tagName)) { attrs = getCanvasElementStyle(el, attrs); } else if ("IMG".equalsIgnoreCase(tagName)) { attrs = getElementDimensionStyle(el, attrs); } if (attrs.length() > 0) { return CSSUtilities.jParseInlineStyle(attrs, null, el, false); } } return null; } private static String getCanvasElementStyle(HTMLElementImpl el, String attrs) { final Node widthNode = el.getAttributes().getNamedItem("width"); if (widthNode != null) { attrs += "width: " + pixelise(widthNode.getNodeValue()) + ";"; } else { attrs += "width: 300px;"; } final Node heightNode = el.getAttributes().getNamedItem("height"); if (heightNode != null) { attrs += "height: " + pixelise(heightNode.getNodeValue()) + ";"; } else { attrs += "height: 150px;"; } return attrs; } private static String getElementDimensionStyle(HTMLElementImpl el, String attrs) { final Node widthNode = el.getAttributes().getNamedItem("width"); if (widthNode != null) { attrs += "width: " + pixelise(widthNode.getNodeValue()) + ";"; } final Node heightNode = el.getAttributes().getNamedItem("height"); if (heightNode != null) { attrs += "height: " + pixelise(heightNode.getNodeValue()) + ";"; } return attrs; } private static String pixelise(final String value) { try { @SuppressWarnings("unused") final int ignored = Integer.parseInt(value); return value + "px"; } catch (NumberFormatException e) { return value; } } private static String getTableElementStyle(final Element el, String attrs) { String border = "0"; String frame = "void"; //borders if (el.getAttributes().getNamedItem("border") != null) { border = el.getAttribute("border"); if (!border.equals("0")) { frame = "border"; } } if (el.getAttributes().getNamedItem("frame") != null) { frame = el.getAttribute("frame").toLowerCase(); } if (!border.equals("0")) { final String fstyle = "border-@-style:solid;border-@-width:" + border + "px;"; if (frame.equals("above")) { attrs = attrs + applyBorders(fstyle, "top"); } if (frame.equals("below")) { attrs = attrs + applyBorders(fstyle, "bottom"); } if (frame.equals("hsides")) { attrs = attrs + applyBorders(fstyle, "left"); attrs = attrs + applyBorders(fstyle, "right"); } if (frame.equals("lhs")) { attrs = attrs + applyBorders(fstyle, "left"); } if (frame.equals("rhs")) { attrs = attrs + applyBorders(fstyle, "right"); } if (frame.equals("vsides")) { attrs = attrs + applyBorders(fstyle, "top"); attrs = attrs + applyBorders(fstyle, "bottom"); } if (frame.equals("box")) { attrs = appAllBorders(attrs, fstyle); } if (frame.equals("border")) { attrs = appAllBorders(attrs, fstyle); } } return attrs; } private static String appAllBorders(String attrs, final String fstyle) { attrs = attrs + applyBorders(fstyle, "left"); attrs = attrs + applyBorders(fstyle, "right"); attrs = attrs + applyBorders(fstyle, "top"); attrs = attrs + applyBorders(fstyle, "bottom"); return attrs; } private static String getFontElementStyle(final Element el, String attrs) { if (el.getAttributes().getNamedItem("color") != null) { attrs = attrs + "color: " + el.getAttribute("color") + ";"; } if (el.getAttributes().getNamedItem("face") != null) { attrs = attrs + "font-family: " + el.getAttribute("face") + ";"; } if (el.getAttributes().getNamedItem("size") != null) { final String sz = el.getAttribute("size"); String ret = "normal"; if (sz.equals("1")) { ret = "xx-small"; } else if (sz.equals("2")) { ret = "x-small"; } else if (sz.equals("3")) { ret = "small"; } else if (sz.equals("4")) { ret = "normal"; } else if (sz.equals("5")) { ret = "large"; } else if (sz.equals("6")) { ret = "x-large"; } else if (sz.equals("7")) { ret = "xx-large"; } else if (sz.startsWith("+")) { final int sn = Integer.parseInt(sz.substring(1)); if ((sn > 0) && (sn <= 7)) { ret = (100 + (sn * 20)) + "%"; } } else if (sz.startsWith("-")) { final int sn = Integer.parseInt(sz.substring(1)); if ((sn > 0) && (sn <= 7)) { ret = (100 - (sn * 10)) + "%"; } } attrs = attrs + "font-size: " + ret; } return attrs; } private static String applyBorders(final String template, final String dir) { return template.replaceAll("@", dir); } //======== Normalize the dom ============================= /** * Provides a cleanup of a HTML DOM tree according to the HTML syntax * restrictions. Currently, following actions are implemented: * <ul> * <li>Table cleanup * <ul> * <li>elements that are not acceptable within a table are moved before the table</li> * </ul> * </li> * </ul> * * @param doc * the processed DOM Document. */ public static void normalizeHTMLTree(final Document doc) { //normalize tables final NodeList tables = doc.getElementsByTagName("table"); for (int i = 0; i < tables.getLength(); i++) { final Vector<Node> nodes = new Vector<>(); recursiveFindBadNodesInTable(tables.item(i), null, nodes); for (final Node n : nodes) { moveSubtreeBefore(n, tables.item(i)); } } } /** * Finds all the nodes in a table that cannot be contained in the table * according to the HTML syntax. * * @param n * table root * @param cellroot * last cell root * @param nodes * resulting list of nodes */ private static void recursiveFindBadNodesInTable(final Node n, final Node cellroot, final Vector<Node> nodes) { Node cell = cellroot; if (n.getNodeType() == Node.ELEMENT_NODE) { final String tag = n.getNodeName().toLowerCase(); if (tag.equals("table")) { if (cell != null) { //do not enter nested tables return; } } else if (tag.equals("tbody") || tag.equals("thead") || tag.equals("tfoot") || tag.equals("tr") || tag.equals("col") || tag.equals("colgroup")) { } else if (tag.equals("td") || tag.equals("th") || tag.equals("caption")) { cell = n; } else { //other elements if (cell == null) { nodes.add(n); return; } } } else if (n.getNodeType() == Node.TEXT_NODE) { //other nodes if ((cell == null) && (n.getNodeValue().trim().length() > 0)) { nodes.add(n); return; } } final NodeList child = n.getChildNodes(); for (int i = 0; i < child.getLength(); i++) { recursiveFindBadNodesInTable(child.item(i), cell, nodes); } } private static void moveSubtreeBefore(final Node root, final Node ref) { root.getParentNode().removeChild(root); ref.getParentNode().insertBefore(root, ref); } }