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