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 borderBottomColor} style attribute.
442      * @return the style attribute
443      */
444     public String getBorderBottomColor() {
445         String value = getStyleAttribute(Definition.BORDER_BOTTOM_COLOR, false);
446         if (value.isEmpty()) {
447             value = findColor(getStyleAttribute(Definition.BORDER_BOTTOM, false));
448             if (value == null) {
449                 value = findColor(getStyleAttribute(Definition.BORDER, false));
450             }
451             if (value == null) {
452                 value = "";
453             }
454         }
455         return value;
456     }
457 
458     /**
459      * Gets the {@code borderBottomStyle} style attribute.
460      * @return the style attribute
461      */
462     public String getBorderBottomStyle() {
463         String value = getStyleAttribute(Definition.BORDER_BOTTOM_STYLE, false);
464         if (value.isEmpty()) {
465             value = findBorderStyle(getStyleAttribute(Definition.BORDER_BOTTOM, false));
466             if (value == null) {
467                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
468             }
469             if (value == null) {
470                 value = "";
471             }
472         }
473         return value;
474     }
475 
476     /**
477      * Gets the {@code borderBottomWidth} style attribute.
478      * @return the style attribute
479      */
480     public String getBorderBottomWidth() {
481         return getBorderWidth(Definition.BORDER_BOTTOM_WIDTH, Definition.BORDER_BOTTOM);
482     }
483 
484     /**
485      * Gets the {@code borderLeftColor} style attribute.
486      * @return the style attribute
487      */
488     public String getBorderLeftColor() {
489         String value = getStyleAttribute(Definition.BORDER_LEFT_COLOR, false);
490         if (value.isEmpty()) {
491             value = findColor(getStyleAttribute(Definition.BORDER_LEFT, false));
492             if (value == null) {
493                 value = findColor(getStyleAttribute(Definition.BORDER, false));
494             }
495             if (value == null) {
496                 value = "";
497             }
498         }
499         return value;
500     }
501 
502     /**
503      * Gets the {@code borderLeftStyle} style attribute.
504      * @return the style attribute
505      */
506     public String getBorderLeftStyle() {
507         String value = getStyleAttribute(Definition.BORDER_LEFT_STYLE, false);
508         if (value.isEmpty()) {
509             value = findBorderStyle(getStyleAttribute(Definition.BORDER_LEFT, false));
510             if (value == null) {
511                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
512             }
513             if (value == null) {
514                 value = "";
515             }
516         }
517         return value;
518     }
519 
520     /**
521      * Gets the {@code borderLeftWidth} style attribute.
522      * @return the style attribute
523      */
524     public String getBorderLeftWidth() {
525         return getBorderWidth(Definition.BORDER_LEFT_WIDTH, Definition.BORDER_LEFT);
526     }
527 
528     /**
529      * Gets the border width for the specified side
530      * @param borderSideWidth the border side width Definition
531      * @param borderSide the border side Definition
532      * @return the width, "" if not defined
533      */
534     private String getBorderWidth(final Definition borderSideWidth, final Definition borderSide) {
535         String value = getStyleAttribute(borderSideWidth, false);
536         if (value.isEmpty()) {
537             value = findBorderWidth(getStyleAttribute(borderSide, false));
538             if (value == null) {
539                 final String borderWidth = getStyleAttribute(Definition.BORDER_WIDTH, false);
540                 if (!org.apache.commons.lang3.StringUtils.isEmpty(borderWidth)) {
541                     final String[] values = StringUtils.splitAtJavaWhitespace(borderWidth);
542                     int index = values.length;
543                     if (borderSideWidth.name().contains("TOP")) {
544                         index = 0;
545                     }
546                     else if (borderSideWidth.name().contains("RIGHT")) {
547                         index = 1;
548                     }
549                     else if (borderSideWidth.name().contains("BOTTOM")) {
550                         index = 2;
551                     }
552                     else if (borderSideWidth.name().contains("LEFT")) {
553                         index = 3;
554                     }
555                     if (index < values.length) {
556                         value = values[index];
557                     }
558                 }
559             }
560             if (value == null) {
561                 value = findBorderWidth(getStyleAttribute(Definition.BORDER, false));
562             }
563             if (value == null) {
564                 value = "";
565             }
566         }
567         return value;
568     }
569 
570     /**
571      * Gets the {@code borderRightColor} style attribute.
572      * @return the style attribute
573      */
574     public String getBorderRightColor() {
575         String value = getStyleAttribute(Definition.BORDER_RIGHT_COLOR, false);
576         if (value.isEmpty()) {
577             value = findColor(getStyleAttribute(Definition.BORDER_RIGHT, false));
578             if (value == null) {
579                 value = findColor(getStyleAttribute(Definition.BORDER, false));
580             }
581             if (value == null) {
582                 value = "";
583             }
584         }
585         return value;
586     }
587 
588     /**
589      * Gets the {@code borderRightStyle} style attribute.
590      * @return the style attribute
591      */
592     public String getBorderRightStyle() {
593         String value = getStyleAttribute(Definition.BORDER_RIGHT_STYLE, false);
594         if (value.isEmpty()) {
595             value = findBorderStyle(getStyleAttribute(Definition.BORDER_RIGHT, false));
596             if (value == null) {
597                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
598             }
599             if (value == null) {
600                 value = "";
601             }
602         }
603         return value;
604     }
605 
606     /**
607      * Gets the {@code borderRightWidth} style attribute.
608      * @return the style attribute
609      */
610     public String getBorderRightWidth() {
611         return getBorderWidth(Definition.BORDER_RIGHT_WIDTH, Definition.BORDER_RIGHT);
612     }
613 
614     /**
615      * Gets the {@code borderTop} style attribute.
616      * @return the style attribute
617      */
618     public String getBorderTop() {
619         return getStyleAttribute(Definition.BORDER_TOP, true);
620     }
621 
622     /**
623      * Gets the {@code borderTopColor} style attribute.
624      * @return the style attribute
625      */
626     public String getBorderTopColor() {
627         String value = getStyleAttribute(Definition.BORDER_TOP_COLOR, false);
628         if (value.isEmpty()) {
629             value = findColor(getStyleAttribute(Definition.BORDER_TOP, false));
630             if (value == null) {
631                 value = findColor(getStyleAttribute(Definition.BORDER, false));
632             }
633             if (value == null) {
634                 value = "";
635             }
636         }
637         return value;
638     }
639 
640     /**
641      * Gets the {@code borderTopStyle} style attribute.
642      * @return the style attribute
643      */
644     public String getBorderTopStyle() {
645         String value = getStyleAttribute(Definition.BORDER_TOP_STYLE, false);
646         if (value.isEmpty()) {
647             value = findBorderStyle(getStyleAttribute(Definition.BORDER_TOP, false));
648             if (value == null) {
649                 value = findBorderStyle(getStyleAttribute(Definition.BORDER, false));
650             }
651             if (value == null) {
652                 value = "";
653             }
654         }
655         return value;
656     }
657 
658     /**
659      * Gets the {@code borderTopWidth} style attribute.
660      * @return the style attribute
661      */
662     public String getBorderTopWidth() {
663         return getBorderWidth(Definition.BORDER_TOP_WIDTH, Definition.BORDER_TOP);
664     }
665 
666     /**
667      * Gets the {@code bottom} style attribute.
668      * @return the style attribute
669      */
670     public String getBottom() {
671         return getStyleAttribute(Definition.BOTTOM, true);
672     }
673 
674     /**
675      * Gets the {@code color} style attribute.
676      * @return the style attribute
677      */
678     public String getColor() {
679         return getStyleAttribute(Definition.COLOR, true);
680     }
681 
682     /**
683      * Gets the {@code cssFloat} style attribute.
684      * @return the style attribute
685      */
686     public String getCssFloat() {
687         return getStyleAttribute(Definition.FLOAT, true);
688     }
689 
690     /**
691      * Gets the {@code display} style attribute.
692      * @return the style attribute
693      */
694     public String getDisplay() {
695         return getStyleAttribute(Definition.DISPLAY, true);
696     }
697 
698     /**
699      * Gets the {@code font} style attribute.
700      * @return the style attribute
701      */
702     public String getFont() {
703         return getStyleAttribute(Definition.FONT, true);
704     }
705 
706     /**
707      * Gets the {@code fontFamily} style attribute.
708      * @return the style attribute
709      */
710     public String getFontFamily() {
711         return getStyleAttribute(Definition.FONT_FAMILY, true);
712     }
713 
714     /**
715      * Gets the {@code fontSize} style attribute.
716      * @return the style attribute
717      */
718     public String getFontSize() {
719         return getStyleAttribute(Definition.FONT_SIZE, true);
720     }
721 
722     /**
723      * Gets the {@code height} style attribute.
724      * @return the style attribute
725      */
726     public String getHeight() {
727         return getStyleAttribute(Definition.HEIGHT, true);
728     }
729 
730     /**
731      * @return the style attribute {@code left}
732      */
733     public String getLeft() {
734         return getStyleAttribute(Definition.LEFT, true);
735     }
736 
737     /**
738      * @return the style attribute {@code letterSpacing}
739      */
740     public String getLetterSpacing() {
741         return getStyleAttribute(Definition.LETTER_SPACING, true);
742     }
743 
744     /**
745      * @return the style attribute {@code lineHeight}
746      */
747     public String getLineHeight() {
748         return getStyleAttribute(Definition.LINE_HEIGHT, true);
749     }
750 
751     /**
752      * @return the style attribute {@code margin}
753      */
754     public String getMargin() {
755         return getStyleAttribute(Definition.MARGIN, true);
756     }
757 
758     /**
759      * Gets the {@code marginBottom} style attribute.
760      * @return the style attribute
761      */
762     public String getMarginBottom() {
763         return getStyleAttribute(Definition.MARGIN_BOTTOM, Definition.MARGIN);
764     }
765 
766     /**
767      * Gets the {@code marginLeft} style attribute.
768      * @return the style attribute
769      */
770     public String getMarginLeft() {
771         return getStyleAttribute(Definition.MARGIN_LEFT, Definition.MARGIN);
772     }
773 
774     /**
775      * Gets the {@code marginRight} style attribute.
776      * @return the style attribute
777      */
778     public String getMarginRight() {
779         return getStyleAttribute(Definition.MARGIN_RIGHT, Definition.MARGIN);
780     }
781 
782     /**
783      * Gets the {@code marginTop} style attribute.
784      * @return the style attribute
785      */
786     public String getMarginTop() {
787         return getStyleAttribute(Definition.MARGIN_TOP, Definition.MARGIN);
788     }
789 
790     /**
791      * @return the style attribute {@code maxHeight}
792      */
793     public String getMaxHeight() {
794         return getStyleAttribute(Definition.MAX_HEIGHT, true);
795     }
796 
797     /**
798      * @return the style attribute {@code maxWidth}
799      */
800     public String getMaxWidth() {
801         return getStyleAttribute(Definition.MAX_WIDTH, true);
802     }
803 
804     /**
805      * @return the style attribute {@code minHeight}
806      */
807     public String getMinHeight() {
808         return getStyleAttribute(Definition.MIN_HEIGHT, true);
809     }
810 
811     /**
812      * @return the style attribute {@code minWidth}
813      */
814     public String getMinWidth() {
815         return getStyleAttribute(Definition.MIN_WIDTH, true);
816     }
817 
818     /**
819      * Gets the {@code opacity} style attribute.
820      * @return the style attribute
821      */
822     public String getOpacity() {
823         final String opacity = getStyleAttribute(Definition.OPACITY, false);
824         if (opacity == null || opacity.isEmpty()) {
825             return "";
826         }
827 
828         final String trimedOpacity = opacity.trim();
829         try {
830             final double value = Double.parseDouble(trimedOpacity);
831             if (value % 1 == 0) {
832                 return Integer.toString((int) value);
833             }
834             return Double.toString(value);
835         }
836         catch (final NumberFormatException ignored) {
837             // ignore wrong values
838         }
839         return "";
840     }
841 
842     /**
843      * @return the style attribute {@code orphans}
844      */
845     public String getOrphans() {
846         return getStyleAttribute(Definition.ORPHANS, true);
847     }
848 
849     /**
850      * @return the style attribute {@code outline}
851      */
852     public String getOutline() {
853         return getStyleAttribute(Definition.OUTLINE, true);
854     }
855 
856     /**
857      * @return the style attribute {@code outlineWidth}
858      */
859     public String getOutlineWidth() {
860         return getStyleAttribute(Definition.OUTLINE_WIDTH, true);
861     }
862 
863     /**
864      * @return the style attribute {@code padding}
865      */
866     public String getPadding() {
867         return getStyleAttribute(Definition.PADDING, true);
868     }
869 
870     /**
871      * @return the style attribute {@code paddingBottom}
872      */
873     public String getPaddingBottom() {
874         return getStyleAttribute(Definition.PADDING_BOTTOM, Definition.PADDING);
875     }
876 
877     /**
878      * @return the style attribute {@code paddingLeft}
879      */
880     public String getPaddingLeft() {
881         return getStyleAttribute(Definition.PADDING_LEFT, Definition.PADDING);
882     }
883 
884     /**
885      * @return the style attribute {@code paddingRight}
886      */
887     public String getPaddingRight() {
888         return getStyleAttribute(Definition.PADDING_RIGHT, Definition.PADDING);
889     }
890 
891     /**
892      * @return the style attribute {@code paddingTop}
893      */
894     public String getPaddingTop() {
895         return getStyleAttribute(Definition.PADDING_TOP, Definition.PADDING);
896     }
897 
898     /**
899      * @return the style attribute {@code position}
900      */
901     public String getPosition() {
902         return getStyleAttribute(Definition.POSITION, true);
903     }
904 
905     /**
906      * @return the style attribute {@code right}
907      */
908     public String getRight() {
909         return getStyleAttribute(Definition.RIGHT, true);
910     }
911 
912     /**
913      * @return the style attribute {@code rubyAlign}
914      */
915     public String getRubyAlign() {
916         return getStyleAttribute(Definition.RUBY_ALIGN, true);
917     }
918 
919     /**
920      * @return the style attribute {@code size}
921      */
922     public String getSize() {
923         return getStyleAttribute(Definition.SIZE, true);
924     }
925 
926     /**
927      * @return the style attribute {@code textIndent}
928      */
929     public String getTextIndent() {
930         return getStyleAttribute(Definition.TEXT_INDENT, true);
931     }
932 
933     /**
934      * @return the style attribute {@code top}
935      */
936     public String getTop() {
937         return getStyleAttribute(Definition.TOP, true);
938     }
939 
940     /**
941      * @return the style attribute {@code verticalAlign}
942      */
943     public String getVerticalAlign() {
944         return getStyleAttribute(Definition.VERTICAL_ALIGN, true);
945     }
946 
947     /**
948      * @return the style attribute {@code widows}
949      */
950     public String getWidows() {
951         return getStyleAttribute(Definition.WIDOWS, true);
952     }
953 
954     /**
955      * @return the style attribute {@code width}
956      */
957     public String getWidth() {
958         return getStyleAttribute(Definition.WIDTH, true);
959     }
960 
961     /**
962      * @return the style attribute {@code wordSpacing}
963      */
964     public String getWordSpacing() {
965         return getStyleAttribute(Definition.WORD_SPACING, true);
966     }
967 
968     /**
969      * Gets the {@code zIndex} style attribute.
970      * @return the style attribute
971      */
972     public String getZIndex() {
973         final String value = getStyleAttribute(Definition.Z_INDEX_, true);
974         try {
975             Integer.parseInt(value);
976             return value;
977         }
978         catch (final NumberFormatException e) {
979             return "";
980         }
981     }
982 
983     /**
984      * Searches for any attachment notation in the specified text.
985      * @param text the string to search in
986      * @return the string of the attachment if found, null otherwise
987      */
988     private static String findAttachment(final String text) {
989         if (text.contains(SCROLL)) {
990             return SCROLL;
991         }
992         if (text.contains(FIXED)) {
993             return FIXED;
994         }
995         return null;
996     }
997 
998     /**
999      * Searches for any color notation in the specified text.
1000      * @param text the string to search in
1001      * @return the string of the color if found, null otherwise
1002      */
1003     private static String findColor(final String text) {
1004         Color tmpColor = StringUtils.findColorRGB(text);
1005         if (tmpColor != null) {
1006             return StringUtils.formatColor(tmpColor);
1007         }
1008 
1009         final String[] tokens = StringUtils.splitAtBlank(text);
1010         for (final String token : tokens) {
1011             if (CssColors.isColorKeyword(token)) {
1012                 return token;
1013             }
1014 
1015             tmpColor = StringUtils.asColorHexadecimal(token);
1016             if (tmpColor != null) {
1017                 return StringUtils.formatColor(tmpColor);
1018             }
1019         }
1020         return null;
1021     }
1022 
1023     /**
1024      * Searches for any URL notation in the specified text.
1025      * @param text the string to search in
1026      * @return the string of the URL if found, null otherwise
1027      */
1028     private static String findImageUrl(final String text) {
1029         final Matcher m = URL_PATTERN.matcher(text);
1030         if (m.find()) {
1031             return "url(\"" + m.group(1) + "\")";
1032         }
1033         return null;
1034     }
1035 
1036     /**
1037      * Searches for any position notation in the specified text.
1038      * @param text the string to search in
1039      * @return the string of the position if found, null otherwise
1040      */
1041     private static String findPosition(final String text) {
1042         Matcher m = POSITION_PATTERN.matcher(text);
1043         if (m.find()) {
1044             return m.group(1) + " " + m.group(3);
1045         }
1046         m = POSITION_PATTERN2.matcher(text);
1047         if (m.find()) {
1048             return m.group(1) + " " + m.group(2);
1049         }
1050         m = POSITION_PATTERN3.matcher(text);
1051         if (m.find()) {
1052             return m.group(2) + " " + m.group(1);
1053         }
1054         return null;
1055     }
1056 
1057     /**
1058      * Searches for any repeat notation in the specified text.
1059      * @param text the string to search in
1060      * @return the string of the repeat if found, null otherwise
1061      */
1062     private static String findRepeat(final String text) {
1063         if (text.contains("repeat-x")) {
1064             return "repeat-x";
1065         }
1066         if (text.contains("repeat-y")) {
1067             return "repeat-y";
1068         }
1069         if (text.contains("no-repeat")) {
1070             return "no-repeat";
1071         }
1072         if (text.contains(REPEAT)) {
1073             return REPEAT;
1074         }
1075         return null;
1076     }
1077 
1078     /**
1079      * Searches for a border style in the specified text.
1080      * @param text the string to search in
1081      * @return the border style if found, null otherwise
1082      */
1083     private static String findBorderStyle(final String text) {
1084         for (final String token : StringUtils.splitAtBlank(text)) {
1085             if (isBorderStyle(token)) {
1086                 return token;
1087             }
1088         }
1089         return null;
1090     }
1091 
1092     /**
1093      * Returns if the specified token is a border style.
1094      * @param token the token to check
1095      * @return whether the token is a border style or not
1096      */
1097     private static boolean isBorderStyle(final String token) {
1098         return NONE.equalsIgnoreCase(token) || "hidden".equalsIgnoreCase(token)
1099             || "dotted".equalsIgnoreCase(token) || "dashed".equalsIgnoreCase(token)
1100             || "solid".equalsIgnoreCase(token) || "double".equalsIgnoreCase(token)
1101             || "groove".equalsIgnoreCase(token) || "ridge".equalsIgnoreCase(token)
1102             || "inset".equalsIgnoreCase(token) || "outset".equalsIgnoreCase(token);
1103     }
1104 
1105     /**
1106      * Searches for a border width in the specified text.
1107      * @param text the string to search in
1108      * @return the border width if found, null otherwise
1109      */
1110     private static String findBorderWidth(final String text) {
1111         for (final String token : StringUtils.splitAtBlank(text)) {
1112             if (isBorderWidth(token)) {
1113                 return token;
1114             }
1115         }
1116         return null;
1117     }
1118 
1119     /**
1120      * Returns if the specified token is a border width.
1121      * @param token the token to check
1122      * @return whether the token is a border width or not
1123      */
1124     private static boolean isBorderWidth(final String token) {
1125         return "thin".equalsIgnoreCase(token) || "medium".equalsIgnoreCase(token)
1126             || "thick".equalsIgnoreCase(token) || isLength(token);
1127     }
1128 
1129     /**
1130      * Returns if the specified token is a length.
1131      * @param token the token to check
1132      * @return whether the token is a length or not
1133      */
1134     static boolean isLength(final String token) {
1135         if (token.endsWith("%")) {
1136             try {
1137                 Double.parseDouble(token.substring(0, token.length() - 1));
1138                 return true;
1139             }
1140             catch (final NumberFormatException ignored) {
1141                 // ignore wrong values
1142             }
1143             return false;
1144         }
1145 
1146         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
1147             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")) {
1148 
1149             try {
1150                 Double.parseDouble(token.substring(0, token.length() - 2));
1151                 return true;
1152             }
1153             catch (final NumberFormatException ignored) {
1154                 // ignore wrong values
1155             }
1156             return false;
1157         }
1158 
1159         return false;
1160     }
1161 }