001package com.hfg.css; 002 003import java.awt.Color; 004import java.io.IOException; 005import java.io.Reader; 006import java.util.*; 007 008import com.hfg.graphics.ColorUtil; 009import com.hfg.html.HTMLTag; 010import com.hfg.html.attribute.Align; 011import com.hfg.html.attribute.HTMLColor; 012import com.hfg.html.attribute.VAlign; 013import com.hfg.util.*; 014import com.hfg.util.collection.CollectionUtil; 015 016//------------------------------------------------------------------------------ 017/** 018 Class to define some css style shortcuts. 019 020 Use example: 021 <pre> 022 span.setStyle(CSS.BOLD + CSS.color(Color.red) + CSS.ITALIC); 023 </pre> 024 <div> 025 @author J. Alex Taylor, hairyfatguy.com 026 </div> 027 */ 028//------------------------------------------------------------------------------ 029// com.hfg XML/HTML Coding Library 030// 031// This library is free software; you can redistribute it and/or 032// modify it under the terms of the GNU Lesser General Public 033// License as published by the Free Software Foundation; either 034// version 2.1 of the License, or (at your option) any later version. 035// 036// This library is distributed in the hope that it will be useful, 037// but WITHOUT ANY WARRANTY; without even the implied warranty of 038// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 039// Lesser General Public License for more details. 040// 041// You should have received a copy of the GNU Lesser General Public 042// License along with this library; if not, write to the Free Software 043// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 044// 045// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 046// jataylor@hairyfatguy.com 047//------------------------------------------------------------------------------ 048 049public class CSS 050{ 051 //########################################################################### 052 // PUBLIC FIELDS 053 //########################################################################### 054 055 public static final String STYLE = "style"; 056 057 /** 058 Shortcut for 'font-weight:bold;' 059 */ 060 public static final String BOLD = "font-weight:bold;"; 061 /** 062 Shortcut for 'font-style:italic;' 063 */ 064 public static final String ITALIC = "font-style:italic;"; 065 /** 066 Shortcut for 'text-decoration:underline;' 067 */ 068 public static final String UNDERLINE = "text-decoration:underline;"; 069 /** 070 Shortcut for 'font-size:small;' 071 */ 072 public static final String SMALL_FONT = "font-size:small;"; 073 /** 074 Shortcut for 'font-family:monospace;' 075 */ 076 public static final String MONOSPACE = "font-family:monospace;"; 077 /** 078 Shortcut for 'vertical-align:super;' 079 */ 080 public static final String SUPER = "vertical-align:super;"; 081 082 //########################################################################### 083 // PRIVATE FIELDS 084 //########################################################################### 085 086 private List<CSSRule> mCSSRules; 087 088 //########################################################################### 089 // CONSTRUCTORS 090 //########################################################################### 091 092 //-------------------------------------------------------------------------- 093 public CSS() 094 { 095 } 096 097 //-------------------------------------------------------------------------- 098 /** 099 Parses CSS text into rules. 100 @param inReader CSS text 101 @throws IOException if a problem is encountered while processing the CSS text from the specified Reader 102 */ 103 public CSS(Reader inReader) 104 throws IOException 105 { 106 mCSSRules = parse(inReader); 107 } 108 109 //########################################################################### 110 // PUBLIC METHODS 111 //########################################################################### 112 113 //-------------------------------------------------------------------------- 114 @Override 115 public String toString() 116 { 117 StringBuilderPlus buffer = new StringBuilderPlus(); 118 if (CollectionUtil.hasValues(getCSSRules())) 119 { 120 for (CSSRule rule : getCSSRules()) 121 { 122 buffer.appendln(rule.toString()); 123 } 124 } 125 126 return buffer.toString(); 127 } 128 129 //-------------------------------------------------------------------------- 130 public CSSRule addRule() 131 { 132 CSSRule rule = new CSSRule(); 133 addRule(rule); 134 135 return rule; 136 } 137 138 //-------------------------------------------------------------------------- 139 public CSS addRule(CSSRule inRule) 140 { 141 if (null == mCSSRules) 142 { 143 mCSSRules = new ArrayList<>(); 144 } 145 146 mCSSRules.add(inRule); 147 148 return this; 149 } 150 151 //-------------------------------------------------------------------------- 152 public List<CSSRule> getCSSRules() 153 { 154 return mCSSRules; 155 } 156 157 //-------------------------------------------------------------------------- 158 /** 159 Returns CSS declarations that apply to the specified HTML tag. 160 Useful for fusing HTML and CSS before conversion (to WordprocessingML for example). 161 NOTE: This initial implementation does not handle complex selectors. 162 @param inHTML top tag in a chunk of HTML DOM 163 @param inMediaType CSS media type 164 @return List of CSS declarations 165 */ 166 //TODO: improve selector matching 167 public List<CSSDeclaration> getCSSDeclarationsForHTMLTag(HTMLTag inHTML, CSSMediaType inMediaType) 168 { 169 List<CSSDeclaration> declarations = new ArrayList<>(5); 170 171 for (CSSRule cssRule : mCSSRules) 172 { 173 // Does this CSS rule apply to this tag? 174 if (cssRule.appliesTo(inMediaType)) 175 { 176 for (CSSSelector selector : cssRule.getSelectors()) 177 { 178 if (selector.appliesTo(inHTML)) 179 { 180 if (CollectionUtil.hasValues(cssRule.getDeclarations())) 181 { 182 declarations.addAll(cssRule.getDeclarations()); 183 } 184 } 185 } 186 } 187 } 188 189 return declarations; 190 } 191 192 //-------------------------------------------------------------------------- 193 /** 194 Where specified CSS rules apply, the styling is placed in the tag's style attribute. 195 Useful for fusing HTML and CSS before conversion (to WordprocessingML for example). 196 NOTE: This initial implementation does not handle complex selectors. 197 @param inHTML top tag in a chunk of HTML DOM 198 @param inMediaType CSS media type 199 */ 200 //TODO: improve selector matching 201 public void localizeStyles(HTMLTag inHTML, CSSMediaType inMediaType) 202 { 203 for (CSSRule cssRule : mCSSRules) 204 { 205 // Does this CSS rule apply to this tag? 206 if (cssRule.appliesTo(inMediaType)) 207 { 208 for (CSSSelector selector : cssRule.getSelectors()) 209 { 210 if (selector.appliesTo(inHTML)) 211 { 212 // Since the local style css should override the stylesheet css we need to (re)add it last. 213 String tagStyle = inHTML.getStyle(); 214 inHTML.setStyle(cssRule.getDeclarationString()); 215 if (StringUtil.isSet(tagStyle)) 216 { 217 inHTML.addStyle(tagStyle); 218 } 219 } 220 } 221 } 222 } 223 224 // Recurse thru subtags 225 List<HTMLTag> subtags = inHTML.getSubtags(); 226 if (CollectionUtil.hasValues(subtags)) 227 { 228 for (HTMLTag subtag : subtags) 229 { 230 localizeStyles(subtag, inMediaType); 231 } 232 } 233 } 234 235 //-------------------------------------------------------------------------- 236 public static String color(String inColor) 237 { 238 return CSSProperty.color + ":" + (HexUtil.isHex(inColor) && ! inColor.startsWith("#") ? "#" : "") + inColor + ";"; 239 } 240 241 //-------------------------------------------------------------------------- 242 public static String color(Color inColor) 243 { 244 return CSSProperty.color + ":" + CssUtil.colorToCssValue(inColor) + ";"; 245 } 246 247 248 //-------------------------------------------------------------------------- 249 public static String bgColor(String inColor) 250 { 251 return CSSProperty.background_color + ":" + (HexUtil.isHex(inColor) && ! inColor.startsWith("#") ? "#" : "") + inColor + ";"; 252 } 253 254 //-------------------------------------------------------------------------- 255 public static String bgColor(Color inColor) 256 { 257 return CSSProperty.background_color + ":" + CssUtil.colorToCssValue(inColor) + ";"; 258 } 259 260 261 //-------------------------------------------------------------------------- 262 public static String fontSize(int inPtSize) 263 { 264 return CSSProperty.font_size + ":" + inPtSize + "pt;"; 265 } 266 267 //-------------------------------------------------------------------------- 268 public static String fontSizeEm(int inValue) 269 { 270 return CSSProperty.font_size + ":" + inValue + "em;"; 271 } 272 273 //-------------------------------------------------------------------------- 274 public static String width(String inValue) 275 { 276 return CSSProperty.width + ":" + inValue + ";"; 277 } 278 279 //-------------------------------------------------------------------------- 280 public static String height(String inValue) 281 { 282 return CSSProperty.height + ":" + inValue + ";"; 283 } 284 285 //-------------------------------------------------------------------------- 286 public static String textAlign(Align inValue) 287 { 288 return CSSProperty.text_align + ":" + inValue + ";"; 289 } 290 291 //-------------------------------------------------------------------------- 292 public static String verticalAlign(VAlign inValue) 293 { 294 return CSSProperty.vertical_align + ":" + inValue + ";"; 295 } 296 297 //-------------------------------------------------------------------------- 298 public static String display(CSSDisplayValue inValue) 299 { 300 return CSSProperty.display + ":" + inValue.getDisplayString() + ";"; 301 } 302 303 //########################################################################### 304 // PRIVATE METHODS 305 //########################################################################### 306 307 //-------------------------------------------------------------------------- 308 private List<CSSRule> parse(Reader inReader) 309 throws IOException 310 { 311 List<CSSRule> cssRules = new ArrayList<>(); 312 313 CSSRule currentCSSRule = null; 314 Set<CSSMediaQuery> currentMediaQueries = null; 315 boolean parsingMediaQueryList = false; 316 boolean parsingSelector = true; 317 boolean parsingDeclaration = false; 318 boolean inBlockComment = false; 319 320 StringBuilder mediaQueryListBuffer = new StringBuilder(); 321 StringBuilder selectorBuffer = new StringBuilder(); 322 StringBuilder declarationBuffer = new StringBuilder(); 323 Character prevChar = null; 324 325 int currentChar; 326 while ((currentChar = inReader.read()) != -1) 327 { 328 if (inBlockComment) 329 { 330 if (currentChar == '/' 331 && prevChar == '*') 332 { 333 inBlockComment = false; 334 } 335 } 336 else if (parsingMediaQueryList) 337 { 338 if (currentChar == '{') 339 { 340 parsingSelector = true; 341 parsingMediaQueryList = false; 342 343 String[] mediaQueries = mediaQueryListBuffer.toString().split(","); 344 currentMediaQueries = new HashSet<CSSMediaQuery>(mediaQueries.length); 345 for (String mediaQueryString : mediaQueries) 346 { 347 currentMediaQueries.add(new CSSMediaQuery(mediaQueryString)); 348 } 349 } 350 else 351 { 352 mediaQueryListBuffer.append((char)currentChar); 353 } 354 } 355 else if (parsingSelector 356 && currentChar == 'a' 357 && selectorBuffer.toString().trim().equals("@medi")) 358 { 359 parsingSelector = false; 360 parsingMediaQueryList = true; 361 selectorBuffer.setLength(0); 362 } 363 else if (currentChar == '*' 364 && prevChar == '/') 365 { 366 inBlockComment = true; 367 // Back out the slash from the buffer 368 if (parsingSelector) 369 { 370 selectorBuffer.setLength(selectorBuffer.length() - 1); 371 } 372 else if (parsingDeclaration) 373 { 374 declarationBuffer.setLength(declarationBuffer.length() - 1); 375 } 376 } 377 else if (parsingSelector) 378 { 379 if (currentChar == ',' 380 || currentChar == '{') 381 { 382 if (null == currentCSSRule) 383 { 384 currentCSSRule = new CSSRule().setMediaQueries(currentMediaQueries); 385 } 386 387 currentCSSRule.addSelector(new CSSSelector(selectorBuffer.toString().trim())); 388 selectorBuffer.setLength(0); 389 390 if (currentChar == '{') 391 { 392 parsingSelector = false; 393 parsingDeclaration = true; 394 } 395 } 396 else if (currentChar == '}' 397 && CollectionUtil.hasValues(currentMediaQueries)) 398 { 399 currentMediaQueries = null; 400 mediaQueryListBuffer.setLength(0); 401 selectorBuffer.setLength(0); 402 } 403 else 404 { 405 selectorBuffer.append((char)currentChar); 406 } 407 } 408 else if (parsingDeclaration) 409 { 410 if (currentChar == ';' 411 || currentChar == '}') 412 { 413 String declaration = declarationBuffer.toString().trim(); 414 if (StringUtil.isSet(declaration)) 415 { 416 String[] pieces = declaration.split(":"); 417 if (2 == pieces.length) 418 { 419 CSSProperty cssProperty = CSSProperty.valueOf(pieces[0]); 420 currentCSSRule.addDeclaration(new CSSDeclaration(cssProperty, pieces[1])); 421 } 422 declarationBuffer.setLength(0); 423 } 424 425 if (currentChar == '}') 426 { 427 parsingSelector = true; 428 parsingDeclaration = false; 429 cssRules.add(currentCSSRule); 430 currentCSSRule = null; 431 } 432 } 433 else 434 { 435 declarationBuffer.append((char)currentChar); 436 } 437 } 438 439 prevChar = (char) currentChar; 440 } 441 442 return cssRules; 443 } 444 445}