View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.css;
16  
17  import static org.htmlunit.BrowserVersionFeatures.CSS_BACKGROUND_INITIAL;
18  import static org.htmlunit.BrowserVersionFeatures.CSS_BACKGROUND_RGBA;
19  import static org.htmlunit.css.CssStyleSheet.FIXED;
20  import static org.htmlunit.css.CssStyleSheet.INITIAL;
21  import static org.htmlunit.css.CssStyleSheet.NONE;
22  import static org.htmlunit.css.CssStyleSheet.REPEAT;
23  import static org.htmlunit.css.CssStyleSheet.SCROLL;
24  
25  import java.io.Serializable;
26  import java.util.LinkedHashMap;
27  import java.util.Map;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.htmlunit.BrowserVersion;
32  import org.htmlunit.BrowserVersionFeatures;
33  import org.htmlunit.css.StyleAttributes.Definition;
34  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
35  import org.htmlunit.html.DomElement;
36  import org.htmlunit.html.impl.Color;
37  import org.htmlunit.util.StringUtils;
38  
39  /**
40   * A css StyleDeclaration.
41   *
42   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
43   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
44   * @author Daniel Gredler
45   * @author Chris Erskine
46   * @author Ahmed Ashour
47   * @author Rodney Gitzel
48   * @author Sudhan Moghe
49   * @author Ronald Brill
50   * @author Frank Danek
51   * @author Dennis Duysak
52   * @author cd alexndr
53   */
54  public abstract class AbstractCssStyleDeclaration implements Serializable {
55  
56      private static final Pattern URL_PATTERN =
57              Pattern.compile("url\\(\\s*[\"']?(.*?)[\"']?\\s*\\)");
58  
59      private static final Pattern POSITION_PATTERN =
60              Pattern.compile("(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex))\\s*"
61                      + "(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|top|bottom|center)");
62      private static final Pattern POSITION_PATTERN2 =
63              Pattern.compile("(left|right|center)\\s*(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|top|bottom|center)");
64      private static final Pattern POSITION_PATTERN3 =
65              Pattern.compile("(top|bottom|center)\\s*(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|left|right|center)");
66  
67      /**
68       * Returns the priority of the named style attribute, or an empty string if it is not found.
69       *
70       * @param name the name of the style attribute whose value is to be retrieved
71       * @return the named style attribute value, or an empty string if it is not found
72       */
73      public abstract String getStylePriority(String name);
74  
75      /**
76       * Returns the actual text of the style.
77       * @return the actual text of the style
78       */
79      public abstract String getCssText();
80  
81      /**
82       * Get the value for the style attribute.
83       * @param name the name
84       * @return the value
85       */
86      public abstract String getStyleAttribute(String name);
87  
88      /**
89       * Get the value for the style attribute.
90       * This impl ignores the default getDefaultValueIfEmpty flag, but there is a overload
91       * in {@link ComputedCssStyleDeclaration}.
92       * @param definition the definition
93       * @param getDefaultValueIfEmpty whether to get the default value if empty or not
94       * @return the value
95       */
96      public abstract String getStyleAttribute(Definition definition, boolean getDefaultValueIfEmpty);
97  
98      /**
99       * Indicates if the browser this is associated with has the feature.
100      * @param property the property name
101      * @return {@code false} if this browser doesn't have this feature
102      */
103     public abstract boolean hasFeature(BrowserVersionFeatures property);
104 
105     /**
106      * @return the {@link BrowserVersion}
107      */
108     public abstract BrowserVersion getBrowserVersion();
109 
110     /**
111      * <p>Returns the value of one of the two named style attributes. If both attributes exist,
112      * the value of the attribute that was declared last is returned. If only one of the
113      * attributes exists, its value is returned. If neither attribute exists, an empty string
114      * is returned.</p>
115      *
116      * <p>The second named attribute may be shorthand for a the actual desired property.
117      * The following formats are possible:</p>
118      * <ol>
119      *   <li><code>top right bottom left</code>: All values are explicit.</li>
120      *   <li><code>top right bottom</code>: Left is implicitly the same as right.</li>
121      *   <li><code>top right</code>: Left is implicitly the same as right, bottom is implicitly the same as top.</li>
122      *   <li><code>top</code>: Left, bottom and right are implicitly the same as top.</li>
123      * </ol>
124      *
125      * @param definition1 the name of the first style attribute
126      * @param definition2 the name of the second style attribute
127      * @return the value of one of the two named style attributes
128      */
129     public String getStyleAttribute(final Definition definition1, final Definition definition2) {
130         final StyleElement element1 = getStyleElement(definition1.getAttributeName());
131         final StyleElement element2 = getStyleElement(definition2.getAttributeName());
132 
133         if (element2 == null) {
134             if (element1 == null) {
135                 return "";
136             }
137             return element1.getValue();
138         }
139         if (element1 != null) {
140             if (StyleElement.compareToByImportanceAndSpecificity(element1, element2) > 0) {
141                 return element1.getValue();
142             }
143         }
144 
145         final String[] values = StringUtils.splitAtJavaWhitespace(element2.getValue());
146         if (definition1.name().contains("TOP")) {
147             if (values.length > 0) {
148                 return values[0];
149             }
150             return "";
151         }
152         else if (definition1.name().contains("RIGHT")) {
153             if (values.length > 1) {
154                 return values[1];
155             }
156             else if (values.length > 0) {
157                 return values[0];
158             }
159             return "";
160         }
161         else if (definition1.name().contains("BOTTOM")) {
162             if (values.length > 2) {
163                 return values[2];
164             }
165             else if (values.length > 0) {
166                 return values[0];
167             }
168             return "";
169         }
170         else if (definition1.name().contains("LEFT")) {
171             if (values.length > 3) {
172                 return values[3];
173             }
174             else if (values.length > 1) {
175                 return values[1];
176             }
177             else if (values.length > 0) {
178                 return values[0];
179             }
180             else {
181                 return "";
182             }
183         }
184         else {
185             throw new IllegalStateException("Unsupported definition: " + definition1);
186         }
187     }
188 
189     /**
190      * Sets the actual text of the style.
191      * @param value the new text
192      */
193     public abstract void setCssText(String value);
194 
195     /**
196      * Sets the specified style attribute.
197      * @param name the attribute name (camel-cased)
198      * @param newValue the attribute value
199      * @param important important value
200      */
201     public abstract void setStyleAttribute(String name, String newValue, String important);
202 
203     /**
204      * Removes the specified style attribute, returning the value of the removed attribute.
205      * @param name the attribute name (delimiter-separated, not camel-cased)
206      * @return the removed value
207      */
208     public abstract String removeStyleAttribute(String name);
209 
210     /**
211      * Returns the {@code length} property.
212      * @return the {@code length} property
213      */
214     public abstract int getLength();
215 
216     /**
217      * @param index the index
218      * @return the name of the CSS property at the specified index
219      */
220     public abstract String item(int index);
221 
222     /**
223      * Returns the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
224      * not attached to a CSSRule.
225      * @return the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
226      *      not attached to a CSSRule
227      */
228     public abstract AbstractCSSRuleImpl getParentRule();
229 
230     /**
231      * Determines the StyleElement for the given name.
232      *
233      * @param name the name of the requested StyleElement
234      * @return the StyleElement or null if not found
235      */
236     public abstract StyleElement getStyleElement(String name);
237 
238     /**
239      * Determines the StyleElement for the given name.
240      * This ignores the case of the name.
241      *
242      * @param name the name of the requested StyleElement
243      * @return the StyleElement or null if not found
244      */
245     public abstract StyleElement getStyleElementCaseInSensitive(String name);
246 
247     /**
248      * Returns a sorted map containing style elements, keyed on style element name. We use a
249      * {@link LinkedHashMap} map so that results are deterministic and are thus testable.
250      *
251      * @return a sorted map containing style elements, keyed on style element name
252      */
253     public abstract Map<String, StyleElement> getStyleMap();
254 
255     /**
256      * @return true if this is a computed style declaration
257      */
258     public boolean isComputed() {
259         return false;
260     }
261 
262     /**
263      * Gets the {@code backgroundAttachment} style attribute.
264      * @return the style attribute
265      */
266     public String getBackgroundAttachment() {
267         String value = getStyleAttribute(Definition.BACKGROUND_ATTACHMENT, false);
268         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
269             final String bg = getStyleAttribute(Definition.BACKGROUND, true);
270             if (org.apache.commons.lang3.StringUtils.isNotBlank(bg)) {
271                 value = findAttachment(bg);
272                 if (value == null) {
273                     if (hasFeature(CSS_BACKGROUND_INITIAL) && !isComputed()) {
274                         return INITIAL;
275                     }
276                     return SCROLL; // default if shorthand is used
277                 }
278                 return value;
279             }
280             return "";
281         }
282 
283         return value;
284     }
285 
286     /**
287      * Gets the {@code backgroundColor} style attribute.
288      * @return the style attribute
289      */
290     public String getBackgroundColor() {
291         String value = getStyleAttribute(Definition.BACKGROUND_COLOR, false);
292         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
293             final String bg = getStyleAttribute(Definition.BACKGROUND, false);
294             if (org.apache.commons.lang3.StringUtils.isBlank(bg)) {
295                 return "";
296             }
297             value = findColor(bg);
298             if (value == null) {
299                 if (hasFeature(CSS_BACKGROUND_INITIAL)) {
300                     if (!isComputed()) {
301                         return INITIAL;
302                     }
303                     return "rgba(0, 0, 0, 0)";
304                 }
305                 if (hasFeature(CSS_BACKGROUND_RGBA)) {
306                     return "rgba(0, 0, 0, 0)";
307                 }
308                 return "transparent"; // default if shorthand is used
309             }
310             return value;
311         }
312         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
313             return "";
314         }
315         return value;
316     }
317 
318     /**
319      * Gets the {@code backgroundImage} style attribute.
320      * @return the style attribute
321      */
322     public String getBackgroundImage() {
323         String value = getStyleAttribute(Definition.BACKGROUND_IMAGE, false);
324         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
325             final String bg = getStyleAttribute(Definition.BACKGROUND, false);
326             if (org.apache.commons.lang3.StringUtils.isNotBlank(bg)) {
327                 value = findImageUrl(bg);
328                 final boolean backgroundInitial = hasFeature(CSS_BACKGROUND_INITIAL);
329                 if (value == null) {
330                     return backgroundInitial && !isComputed() ? INITIAL : NONE;
331                 }
332                 if (isComputed()) {
333                     try {
334                         value = value.substring(5, value.length() - 2);
335                         final DomElement domElement = ((ComputedCssStyleDeclaration) this).getDomElement();
336                         return "url(\"" + domElement.getHtmlPageOrNull()
337                             .getFullyQualifiedUrl(value) + "\")";
338                     }
339                     catch (final Exception ignored) {
340                         // ignore
341                     }
342                 }
343                 return value;
344             }
345             return "";
346         }
347 
348         return value;
349     }
350 
351     /**
352      * Gets the {@code backgroundPosition} style attribute.
353      * @return the style attribute
354      */
355     public String getBackgroundPosition() {
356         String value = getStyleAttribute(Definition.BACKGROUND_POSITION, false);
357         if (value == null) {
358             return null;
359         }
360         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
361             final String bg = getStyleAttribute(Definition.BACKGROUND, false);
362             if (bg == null) {
363                 return null;
364             }
365             if (org.apache.commons.lang3.StringUtils.isNotBlank(bg)) {
366                 value = findPosition(bg);
367                 final boolean isInitial = hasFeature(CSS_BACKGROUND_INITIAL);
368                 if (value == null) {
369                     if (isInitial) {
370                         return isComputed() ? "" : INITIAL;
371                     }
372                     return "0% 0%";
373                 }
374                 if (isComputed()) {
375                     final String[] values = StringUtils.splitAtBlank(value);
376                     switch (values[0]) {
377                         case "left":
378                             values[0] = "0%";
379                             break;
380 
381                         case "center":
382                             values[0] = "50%";
383                             break;
384 
385                         case "right":
386                             values[0] = "100%";
387                             break;
388 
389                         default:
390                     }
391                     switch (values[1]) {
392                         case "top":
393                             values[1] = "0%";
394                             break;
395 
396                         case "center":
397                             values[1] = "50%";
398                             break;
399 
400                         case "bottom":
401                             values[1] = "100%";
402                             break;
403 
404                         default:
405                     }
406                     value = values[0] + ' ' + values[1];
407                 }
408                 return value;
409             }
410             return "";
411         }
412 
413         return value;
414     }
415 
416     /**
417      * Gets the {@code backgroundRepeat} style attribute.
418      * @return the style attribute
419      */
420     public String getBackgroundRepeat() {
421         String value = getStyleAttribute(Definition.BACKGROUND_REPEAT, false);
422         if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
423             final String bg = getStyleAttribute(Definition.BACKGROUND, false);
424             if (org.apache.commons.lang3.StringUtils.isNotBlank(bg)) {
425                 value = findRepeat(bg);
426                 if (value == null) {
427                     if (hasFeature(CSS_BACKGROUND_INITIAL) && !isComputed()) {
428                         return INITIAL;
429                     }
430                     return REPEAT; // default if shorthand is used
431                 }
432                 return value;
433             }
434             return "";
435         }
436 
437         return value;
438     }
439 
440     /**
441      * Gets the {@code blockSize} style attribute.
442      * @return the style attribute
443      */
444     public String getBlockSize() {
445         return getStyleAttribute(Definition.BLOCK_SIZE, true);
446     }
447 
448     /**
449      * Gets the {@code borderBottomColor} style attribute.
450      * @return the style attribute
451      */
452     public String getBorderBottomColor() {
453         String value = getStyleAttribute(Definition.BORDER_BOTTOM_COLOR, false);
454         if (value.isEmpty()) {
455             value = findColor(getStyleAttribute(Definition.BORDER_BOTTOM, false));
456             if (value == null) {
457                 value = findColor(getStyleAttribute(Definition.BORDER, false));
458             }
459             if (value == null) {
460                 value = "";
461             }
462         }
463         return value;
464     }
465 
466     /**
467      * Gets the {@code borderBottomStyle} style attribute.
468      * @return the style attribute
469      */
470     public String getBorderBottomStyle() {
471         String value = getStyleAttribute(Definition.BORDER_BOTTOM_STYLE, false);
472         if (value.isEmpty()) {
473             value = findBorderStyle(getStyleAttribute(Definition.BORDER_BOTTOM, false));
474             if (value == null) {
475                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
476             }
477             if (value == null) {
478                 value = "";
479             }
480         }
481         return value;
482     }
483 
484     /**
485      * Gets the {@code borderBottomWidth} style attribute.
486      * @return the style attribute
487      */
488     public String getBorderBottomWidth() {
489         return getBorderWidth(Definition.BORDER_BOTTOM_WIDTH, Definition.BORDER_BOTTOM);
490     }
491 
492     /**
493      * Gets the {@code borderLeftColor} style attribute.
494      * @return the style attribute
495      */
496     public String getBorderLeftColor() {
497         String value = getStyleAttribute(Definition.BORDER_LEFT_COLOR, false);
498         if (value.isEmpty()) {
499             value = findColor(getStyleAttribute(Definition.BORDER_LEFT, false));
500             if (value == null) {
501                 value = findColor(getStyleAttribute(Definition.BORDER, false));
502             }
503             if (value == null) {
504                 value = "";
505             }
506         }
507         return value;
508     }
509 
510     /**
511      * Gets the {@code borderLeftStyle} style attribute.
512      * @return the style attribute
513      */
514     public String getBorderLeftStyle() {
515         String value = getStyleAttribute(Definition.BORDER_LEFT_STYLE, false);
516         if (value.isEmpty()) {
517             value = findBorderStyle(getStyleAttribute(Definition.BORDER_LEFT, false));
518             if (value == null) {
519                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
520             }
521             if (value == null) {
522                 value = "";
523             }
524         }
525         return value;
526     }
527 
528     /**
529      * Gets the {@code borderLeftWidth} style attribute.
530      * @return the style attribute
531      */
532     public String getBorderLeftWidth() {
533         return getBorderWidth(Definition.BORDER_LEFT_WIDTH, Definition.BORDER_LEFT);
534     }
535 
536     /**
537      * Gets the border width for the specified side
538      * @param borderSideWidth the border side width Definition
539      * @param borderSide the border side Definition
540      * @return the width, "" if not defined
541      */
542     private String getBorderWidth(final Definition borderSideWidth, final Definition borderSide) {
543         String value = getStyleAttribute(borderSideWidth, false);
544         if (value.isEmpty()) {
545             value = findBorderWidth(getStyleAttribute(borderSide, false));
546             if (value == null) {
547                 final String borderWidth = getStyleAttribute(Definition.BORDER_WIDTH, false);
548                 if (!org.apache.commons.lang3.StringUtils.isEmpty(borderWidth)) {
549                     final String[] values = StringUtils.splitAtJavaWhitespace(borderWidth);
550                     int index = values.length;
551                     if (borderSideWidth.name().contains("TOP")) {
552                         index = 0;
553                     }
554                     else if (borderSideWidth.name().contains("RIGHT")) {
555                         index = 1;
556                     }
557                     else if (borderSideWidth.name().contains("BOTTOM")) {
558                         index = 2;
559                     }
560                     else if (borderSideWidth.name().contains("LEFT")) {
561                         index = 3;
562                     }
563                     if (index < values.length) {
564                         value = values[index];
565                     }
566                 }
567             }
568             if (value == null) {
569                 value = findBorderWidth(getStyleAttribute(Definition.BORDER, false));
570             }
571             if (value == null) {
572                 value = "";
573             }
574         }
575         return value;
576     }
577 
578     /**
579      * Gets the {@code borderRightColor} style attribute.
580      * @return the style attribute
581      */
582     public String getBorderRightColor() {
583         String value = getStyleAttribute(Definition.BORDER_RIGHT_COLOR, false);
584         if (value.isEmpty()) {
585             value = findColor(getStyleAttribute(Definition.BORDER_RIGHT, false));
586             if (value == null) {
587                 value = findColor(getStyleAttribute(Definition.BORDER, false));
588             }
589             if (value == null) {
590                 value = "";
591             }
592         }
593         return value;
594     }
595 
596     /**
597      * Gets the {@code borderRightStyle} style attribute.
598      * @return the style attribute
599      */
600     public String getBorderRightStyle() {
601         String value = getStyleAttribute(Definition.BORDER_RIGHT_STYLE, false);
602         if (value.isEmpty()) {
603             value = findBorderStyle(getStyleAttribute(Definition.BORDER_RIGHT, false));
604             if (value == null) {
605                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
606             }
607             if (value == null) {
608                 value = "";
609             }
610         }
611         return value;
612     }
613 
614     /**
615      * Gets the {@code borderRightWidth} style attribute.
616      * @return the style attribute
617      */
618     public String getBorderRightWidth() {
619         return getBorderWidth(Definition.BORDER_RIGHT_WIDTH, Definition.BORDER_RIGHT);
620     }
621 
622     /**
623      * Gets the {@code borderTop} style attribute.
624      * @return the style attribute
625      */
626     public String getBorderTop() {
627         return getStyleAttribute(Definition.BORDER_TOP, true);
628     }
629 
630     /**
631      * Gets the {@code borderTopColor} style attribute.
632      * @return the style attribute
633      */
634     public String getBorderTopColor() {
635         String value = getStyleAttribute(Definition.BORDER_TOP_COLOR, false);
636         if (value.isEmpty()) {
637             value = findColor(getStyleAttribute(Definition.BORDER_TOP, false));
638             if (value == null) {
639                 value = findColor(getStyleAttribute(Definition.BORDER, false));
640             }
641             if (value == null) {
642                 value = "";
643             }
644         }
645         return value;
646     }
647 
648     /**
649      * Gets the {@code borderTopStyle} style attribute.
650      * @return the style attribute
651      */
652     public String getBorderTopStyle() {
653         String value = getStyleAttribute(Definition.BORDER_TOP_STYLE, false);
654         if (value.isEmpty()) {
655             value = findBorderStyle(getStyleAttribute(Definition.BORDER_TOP, false));
656             if (value == null) {
657                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
658             }
659             if (value == null) {
660                 value = "";
661             }
662         }
663         return value;
664     }
665 
666     /**
667      * Gets the {@code borderTopWidth} style attribute.
668      * @return the style attribute
669      */
670     public String getBorderTopWidth() {
671         return getBorderWidth(Definition.BORDER_TOP_WIDTH, Definition.BORDER_TOP);
672     }
673 
674     /**
675      * Gets the {@code bottom} style attribute.
676      * @return the style attribute
677      */
678     public String getBottom() {
679         return getStyleAttribute(Definition.BOTTOM, true);
680     }
681 
682     /**
683      * Gets the {@code color} style attribute.
684      * @return the style attribute
685      */
686     public String getColor() {
687         return getStyleAttribute(Definition.COLOR, true);
688     }
689 
690     /**
691      * Gets the {@code cssFloat} style attribute.
692      * @return the style attribute
693      */
694     public String getCssFloat() {
695         return getStyleAttribute(Definition.FLOAT, true);
696     }
697 
698     /**
699      * Gets the {@code display} style attribute.
700      * @return the style attribute
701      */
702     public String getDisplay() {
703         return getStyleAttribute(Definition.DISPLAY, true);
704     }
705 
706     /**
707      * Gets the {@code font} style attribute.
708      * @return the style attribute
709      */
710     public String getFont() {
711         return getStyleAttribute(Definition.FONT, true);
712     }
713 
714     /**
715      * Gets the {@code fontFamily} style attribute.
716      * @return the style attribute
717      */
718     public String getFontFamily() {
719         return getStyleAttribute(Definition.FONT_FAMILY, true);
720     }
721 
722     /**
723      * Gets the {@code fontSize} style attribute.
724      * @return the style attribute
725      */
726     public String getFontSize() {
727         return getStyleAttribute(Definition.FONT_SIZE, true);
728     }
729 
730     /**
731      * Gets the {@code height} style attribute.
732      * @return the style attribute
733      */
734     public String getHeight() {
735         return getStyleAttribute(Definition.HEIGHT, true);
736     }
737 
738     /**
739      * @return the style attribute {@code left}
740      */
741     public String getLeft() {
742         return getStyleAttribute(Definition.LEFT, true);
743     }
744 
745     /**
746      * @return the style attribute {@code letterSpacing}
747      */
748     public String getLetterSpacing() {
749         return getStyleAttribute(Definition.LETTER_SPACING, true);
750     }
751 
752     /**
753      * @return the style attribute {@code lineHeight}
754      */
755     public String getLineHeight() {
756         return getStyleAttribute(Definition.LINE_HEIGHT, true);
757     }
758 
759     /**
760      * @return the style attribute {@code margin}
761      */
762     public String getMargin() {
763         return getStyleAttribute(Definition.MARGIN, true);
764     }
765 
766     /**
767      * Gets the {@code marginBottom} style attribute.
768      * @return the style attribute
769      */
770     public String getMarginBottom() {
771         return getStyleAttribute(Definition.MARGIN_BOTTOM, Definition.MARGIN);
772     }
773 
774     /**
775      * Gets the {@code marginLeft} style attribute.
776      * @return the style attribute
777      */
778     public String getMarginLeft() {
779         return getStyleAttribute(Definition.MARGIN_LEFT, Definition.MARGIN);
780     }
781 
782     /**
783      * Gets the {@code marginRight} style attribute.
784      * @return the style attribute
785      */
786     public String getMarginRight() {
787         return getStyleAttribute(Definition.MARGIN_RIGHT, Definition.MARGIN);
788     }
789 
790     /**
791      * Gets the {@code marginTop} style attribute.
792      * @return the style attribute
793      */
794     public String getMarginTop() {
795         return getStyleAttribute(Definition.MARGIN_TOP, Definition.MARGIN);
796     }
797 
798     /**
799      * @return the style attribute {@code maxHeight}
800      */
801     public String getMaxHeight() {
802         return getStyleAttribute(Definition.MAX_HEIGHT, true);
803     }
804 
805     /**
806      * @return the style attribute {@code maxWidth}
807      */
808     public String getMaxWidth() {
809         return getStyleAttribute(Definition.MAX_WIDTH, true);
810     }
811 
812     /**
813      * @return the style attribute {@code minHeight}
814      */
815     public String getMinHeight() {
816         return getStyleAttribute(Definition.MIN_HEIGHT, true);
817     }
818 
819     /**
820      * @return the style attribute {@code minWidth}
821      */
822     public String getMinWidth() {
823         return getStyleAttribute(Definition.MIN_WIDTH, true);
824     }
825 
826     /**
827      * Gets the {@code opacity} style attribute.
828      * @return the style attribute
829      */
830     public String getOpacity() {
831         final String opacity = getStyleAttribute(Definition.OPACITY, false);
832         if (opacity == null || opacity.isEmpty()) {
833             return "";
834         }
835 
836         final String trimedOpacity = opacity.trim();
837         try {
838             final double value = Double.parseDouble(trimedOpacity);
839             if (value % 1 == 0) {
840                 return Integer.toString((int) value);
841             }
842             return Double.toString(value);
843         }
844         catch (final NumberFormatException ignored) {
845             // ignore wrong values
846         }
847         return "";
848     }
849 
850     /**
851      * @return the style attribute {@code orphans}
852      */
853     public String getOrphans() {
854         return getStyleAttribute(Definition.ORPHANS, true);
855     }
856 
857     /**
858      * @return the style attribute {@code outline}
859      */
860     public String getOutline() {
861         return getStyleAttribute(Definition.OUTLINE, true);
862     }
863 
864     /**
865      * @return the style attribute {@code outlineWidth}
866      */
867     public String getOutlineWidth() {
868         return getStyleAttribute(Definition.OUTLINE_WIDTH, true);
869     }
870 
871     /**
872      * @return the style attribute {@code padding}
873      */
874     public String getPadding() {
875         return getStyleAttribute(Definition.PADDING, true);
876     }
877 
878     /**
879      * @return the style attribute {@code paddingBottom}
880      */
881     public String getPaddingBottom() {
882         return getStyleAttribute(Definition.PADDING_BOTTOM, Definition.PADDING);
883     }
884 
885     /**
886      * @return the style attribute {@code paddingLeft}
887      */
888     public String getPaddingLeft() {
889         return getStyleAttribute(Definition.PADDING_LEFT, Definition.PADDING);
890     }
891 
892     /**
893      * @return the style attribute {@code paddingRight}
894      */
895     public String getPaddingRight() {
896         return getStyleAttribute(Definition.PADDING_RIGHT, Definition.PADDING);
897     }
898 
899     /**
900      * @return the style attribute {@code paddingTop}
901      */
902     public String getPaddingTop() {
903         return getStyleAttribute(Definition.PADDING_TOP, Definition.PADDING);
904     }
905 
906     /**
907      * @return the style attribute {@code position}
908      */
909     public String getPosition() {
910         return getStyleAttribute(Definition.POSITION, true);
911     }
912 
913     /**
914      * @return the style attribute {@code right}
915      */
916     public String getRight() {
917         return getStyleAttribute(Definition.RIGHT, true);
918     }
919 
920     /**
921      * @return the style attribute {@code rubyAlign}
922      */
923     public String getRubyAlign() {
924         return getStyleAttribute(Definition.RUBY_ALIGN, true);
925     }
926 
927     /**
928      * @return the style attribute {@code size}
929      */
930     public String getSize() {
931         return getStyleAttribute(Definition.SIZE, true);
932     }
933 
934     /**
935      * @return the style attribute {@code textIndent}
936      */
937     public String getTextIndent() {
938         return getStyleAttribute(Definition.TEXT_INDENT, true);
939     }
940 
941     /**
942      * @return the style attribute {@code top}
943      */
944     public String getTop() {
945         return getStyleAttribute(Definition.TOP, true);
946     }
947 
948     /**
949      * @return the style attribute {@code verticalAlign}
950      */
951     public String getVerticalAlign() {
952         return getStyleAttribute(Definition.VERTICAL_ALIGN, true);
953     }
954 
955     /**
956      * @return the style attribute {@code widows}
957      */
958     public String getWidows() {
959         return getStyleAttribute(Definition.WIDOWS, true);
960     }
961 
962     /**
963      * @return the style attribute {@code width}
964      */
965     public String getWidth() {
966         return getStyleAttribute(Definition.WIDTH, true);
967     }
968 
969     /**
970      * @return the style attribute {@code wordSpacing}
971      */
972     public String getWordSpacing() {
973         return getStyleAttribute(Definition.WORD_SPACING, true);
974     }
975 
976     /**
977      * Gets the {@code zIndex} style attribute.
978      * @return the style attribute
979      */
980     public String getZIndex() {
981         final String value = getStyleAttribute(Definition.Z_INDEX_, true);
982         try {
983             Integer.parseInt(value);
984             return value;
985         }
986         catch (final NumberFormatException e) {
987             return "";
988         }
989     }
990 
991     /**
992      * Searches for any attachment notation in the specified text.
993      * @param text the string to search in
994      * @return the string of the attachment if found, null otherwise
995      */
996     private static String findAttachment(final String text) {
997         if (text.contains(SCROLL)) {
998             return SCROLL;
999         }
1000         if (text.contains(FIXED)) {
1001             return FIXED;
1002         }
1003         return null;
1004     }
1005 
1006     /**
1007      * Searches for any color notation in the specified text.
1008      * @param text the string to search in
1009      * @return the string of the color if found, null otherwise
1010      */
1011     private static String findColor(final String text) {
1012         Color tmpColor = StringUtils.findColorRGB(text);
1013         if (tmpColor != null) {
1014             return StringUtils.formatColor(tmpColor);
1015         }
1016 
1017         final String[] tokens = StringUtils.splitAtBlank(text);
1018         for (final String token : tokens) {
1019             if (CssColors.isColorKeyword(token)) {
1020                 return token;
1021             }
1022 
1023             tmpColor = StringUtils.asColorHexadecimal(token);
1024             if (tmpColor != null) {
1025                 return StringUtils.formatColor(tmpColor);
1026             }
1027         }
1028         return null;
1029     }
1030 
1031     /**
1032      * Searches for any URL notation in the specified text.
1033      * @param text the string to search in
1034      * @return the string of the URL if found, null otherwise
1035      */
1036     private static String findImageUrl(final String text) {
1037         final Matcher m = URL_PATTERN.matcher(text);
1038         if (m.find()) {
1039             return "url(\"" + m.group(1) + "\")";
1040         }
1041         return null;
1042     }
1043 
1044     /**
1045      * Searches for any position notation in the specified text.
1046      * @param text the string to search in
1047      * @return the string of the position if found, null otherwise
1048      */
1049     private static String findPosition(final String text) {
1050         Matcher m = POSITION_PATTERN.matcher(text);
1051         if (m.find()) {
1052             return m.group(1) + " " + m.group(3);
1053         }
1054         m = POSITION_PATTERN2.matcher(text);
1055         if (m.find()) {
1056             return m.group(1) + " " + m.group(2);
1057         }
1058         m = POSITION_PATTERN3.matcher(text);
1059         if (m.find()) {
1060             return m.group(2) + " " + m.group(1);
1061         }
1062         return null;
1063     }
1064 
1065     /**
1066      * Searches for any repeat notation in the specified text.
1067      * @param text the string to search in
1068      * @return the string of the repeat if found, null otherwise
1069      */
1070     private static String findRepeat(final String text) {
1071         if (text.contains("repeat-x")) {
1072             return "repeat-x";
1073         }
1074         if (text.contains("repeat-y")) {
1075             return "repeat-y";
1076         }
1077         if (text.contains("no-repeat")) {
1078             return "no-repeat";
1079         }
1080         if (text.contains(REPEAT)) {
1081             return REPEAT;
1082         }
1083         return null;
1084     }
1085 
1086     /**
1087      * Searches for a border style in the specified text.
1088      * @param text the string to search in
1089      * @return the border style if found, null otherwise
1090      */
1091     private static String findBorderStyle(final String text) {
1092         for (final String token : StringUtils.splitAtBlank(text)) {
1093             if (isBorderStyle(token)) {
1094                 return token;
1095             }
1096         }
1097         return null;
1098     }
1099 
1100     /**
1101      * Returns if the specified token is a border style.
1102      * @param token the token to check
1103      * @return whether the token is a border style or not
1104      */
1105     private static boolean isBorderStyle(final String token) {
1106         return NONE.equalsIgnoreCase(token) || "hidden".equalsIgnoreCase(token)
1107             || "dotted".equalsIgnoreCase(token) || "dashed".equalsIgnoreCase(token)
1108             || "solid".equalsIgnoreCase(token) || "double".equalsIgnoreCase(token)
1109             || "groove".equalsIgnoreCase(token) || "ridge".equalsIgnoreCase(token)
1110             || "inset".equalsIgnoreCase(token) || "outset".equalsIgnoreCase(token);
1111     }
1112 
1113     /**
1114      * Searches for a border width in the specified text.
1115      * @param text the string to search in
1116      * @return the border width if found, null otherwise
1117      */
1118     private static String findBorderWidth(final String text) {
1119         for (final String token : StringUtils.splitAtBlank(text)) {
1120             if (isBorderWidth(token)) {
1121                 return token;
1122             }
1123         }
1124         return null;
1125     }
1126 
1127     /**
1128      * Returns if the specified token is a border width.
1129      * @param token the token to check
1130      * @return whether the token is a border width or not
1131      */
1132     private static boolean isBorderWidth(final String token) {
1133         return "thin".equalsIgnoreCase(token) || "medium".equalsIgnoreCase(token)
1134             || "thick".equalsIgnoreCase(token) || isLength(token);
1135     }
1136 
1137     /**
1138      * Returns if the specified token is a length.
1139      * @param token the token to check
1140      * @return whether the token is a length or not
1141      */
1142     static boolean isLength(final String token) {
1143         if (token.endsWith("%")) {
1144             try {
1145                 Double.parseDouble(token.substring(0, token.length() - 1));
1146                 return true;
1147             }
1148             catch (final NumberFormatException ignored) {
1149                 // ignore wrong values
1150             }
1151             return false;
1152         }
1153 
1154         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
1155             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")) {
1156 
1157             try {
1158                 Double.parseDouble(token.substring(0, token.length() - 2));
1159                 return true;
1160             }
1161             catch (final NumberFormatException ignored) {
1162                 // ignore wrong values
1163             }
1164             return false;
1165         }
1166 
1167         return false;
1168     }
1169 }