1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package Torello.HTML;

import Torello.HTML.helper.AttrRegEx;

import Torello.HTML.NodeSearch.CSSStrException;

import Torello.Java.StrCmpr;
import static Torello.Java.StringParse.WHITE_SPACE_REGEX;

import java.util.Properties;
import java.util.regex.Matcher;
import java.util.stream.Stream;

class ClassIDStyle
{
    static Stream<String> cssClasses(final TagNode tn)
    {
        // The CSS Class is just an attribute/inner-tag within an HTML Element.
        String classes = GetSetAttr.AV(tn, "class", false); 


        // IF the "class" attribute was not present, OR (after trimming) was empty, return
        // "empty stream"

        if ((classes == null) || ((classes = classes.trim()).length() == 0))
            return Stream.empty();


        // STEP 1: Split the string on white-space
        // STEP 2: Check each element of the output Stream using the "CSSStrException Checker"

        return CSSStrException.check(WHITE_SPACE_REGEX.splitAsStream(classes));
    }

    static TagNode setCSSClasses(
            final TagNode   tn,
            final SD        quote,
            final boolean   appendOrClobber,
            final String... cssClasses
        )
    {
        // Throw CSSStrException if any of the input strings are invalid CSS Class-Names.
        CSSStrException.check(cssClasses);

        // Build the CSS 'class' Attribute String.  This will be inserted into the TagNode Element
        StringBuilder   sb      = new StringBuilder();
        boolean         first   = true;

        for (String c : cssClasses) 
            { sb.append((first ? "" : " ") + c.trim()); first=false; }

        /* TagNode ret */ return appendOrClobber
            ? GetSetAttr.appendToAV(tn, "class", " " + sb.toString(), false, quote)
            : GetSetAttr.setAV(tn, "class", sb.toString(), quote);

        /*
        System.out.println(
            "ClassIDStyle.setCSSClasses.sb:     [" + sb.toString() + "]\n" +
            "ClassIDStyle.setCSSClasses.quote   [" + quote + "]\n" +
            "ClassIDStyle.setCSSClasses.Before: [" + tn + "]\n" +
            "ClassIDStyle.setCSSClasses.After:  [" + ret + "]"
        );

        return ret;
        */
    }

    static TagNode setCSSStyle(
            final TagNode       tn,
            final Properties    p,
            final SD            quote,
            final boolean       appendOrClobber
        )
    {
        // this is used in the "exception constructor" below (which means, it might not be used).
        int counter = 0;


        // Follows the "FAIL-FAST" philosophy, and does not allow invalid CSS declaration-name
        // tokens.  Use TagNode.setAV("style", ...), or TagNode.appendToAV("style", ...), to
        // bypass exception-check.

        for (String key : p.stringPropertyNames())

            if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(key))
            {
                String[] keyArr = new String[p.size()];

                throw new CSSStrException(

                    "CSS Style Definition Property: [" + key + "] does not conform to the " +
                    "valid, HTML 5, regular-expression for CSS Style Definitions Properties:\n[" +
                    CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "].",


                    // One minor "PRESUMPTION" that is the Iterator will return the elements of 
                    // Properties in the EXACT SAME ORDER on both creations / instantiations of the
                    // iterator.  Specifically: two invocations of method .iterator(), will return
                    // the same-list of property-keys, in the same order, BOTH TIMES.  Once for the
                    // for-loop, and once for the exception message.  This only matters if there is
                    // an exception.

                    p.stringPropertyNames().toArray(keyArr),
                    ++counter
                );
            }

            else ++counter; 


        // Follows the "FAIL-FAST" philosophy, and does not allow "quotes-within-quote" problems
        // to occur.  An attribute-value surrounded by single-quotes, cannot contain a
        // single-quote inside, and double-within-double.

        counter = 0;

        for (String key : p.stringPropertyNames())

            if (StrCmpr.containsOR(p.get(key).toString(), ("" + quote.quote), ";"))
            {
                String[] keyArr = new String[p.size()];

                throw new CSSStrException(
                    "CSS Style Definition Property: [" + key + "], which maps to style-" +
                    "definition property-value:\n[" + p.get(key) + "], contains either a " +
                    "semi-colon ';' character, or the same quotation-mark specified: [" + 
                    quote.quote + "], and is therefore not a valid assignment for a CSS " +
                    "Definition Property.",

                    p   .stringPropertyNames()
                        .stream()
                        .map((String propertyName) -> p.get(propertyName))
                        .toArray((int i) -> new String[i]),

                    ++counter
                );
            }

            else ++counter;


        // ERROR-CHECKING: FINISHED, NOW JUST BUILD THE ATTRIBUTE-VALUE STRING
        // (using StringBuilder), AND INSERT IT.

        StringBuilder sb = new StringBuilder();

        for (String key : p.stringPropertyNames()) sb.append(key + ": " + p.get(key) + ";");

        /* TagNode ret = */ return appendOrClobber
            ? GetSetAttr.appendToAV(tn, "style", " " + sb.toString(), false, quote)
            : GetSetAttr.setAV(tn, "style", sb.toString(), quote);

        /*
        System.out.println(
            "ClassIDStyle.setCSSStyle.sb:     [" + sb.toString() + "]\n" +
            "ClassIDStyle.setCSSStyle.Before: [" + tn + "]\n" +
            "ClassIDStyle.setCSSStyle.After:  [" + ret + "]"
        );

        return ret;
        */
    }

    static TagNode appendCSSClass(
            final TagNode   tn,
            final String    cssClass,
            final SD        quote
        )
    {
        // Do a validity check on the class.  If this is "problematic" due to use of specialized / 
        // pre-processed CSS Class directives, use the general purpose "setAV(...)" method

        CSSStrException.check(cssClass);

        final String curCSSClassSetting = GetSetAttr.AV(tn, "class", false);


        // If there wasn't a CSS Class already defined, use "setAV(...)", 
        // otherwise use "appendToAV(...)"

        TagNode ret;
        boolean firstOrSecond;

        if ((curCSSClassSetting == null) || (curCSSClassSetting.length() == 0))
        {
            /*return ret = */ return GetSetAttr.setAV(tn, "class", cssClass, quote);
            // firstOrSecond = true;
        }

        else
        {
            /*return ret = */ return GetSetAttr.appendToAV(tn, "class", cssClass + ' ', true, quote);
            // firstOrSecond = false;
        }

        /*
        System.out.println(
            "ClassIDStyle.appendCSSClass.curCSSClassSetting: [" + curCSSClassSetting + "]\n" +
            "ClassIDStyle.appendCSSClass.Before:             [" + tn + "]\n" +
            "ClassIDStyle.appendCSSClass.After:              [" + ret + "]\n" +
            "ClassIDStyle.appendCSSClass.firstOrSecond:      [" + firstOrSecond + "]"
        );

        return ret;
        */
    }

    static Properties cssStyle(final TagNode tn)
    {
        Properties  p           = new Properties();
        String      styleStr    = GetSetAttr.AV(tn, "style", false);
            // Returns the complete attribute-value of "style" in the TagNode

        // There was no STYLE='...' attribute found, return empty Properties.
        if (styleStr == null) return p;

        // Standard String-trim routine
        styleStr = styleStr.trim();

        if (styleStr.length() == 0) return p;


        // This reg-ex "iterates" over matches of strings that follow the (very basic) form:
        // declaration-name: declaration-string;
        //
        // Where the ":" (color), and ";" (semi-colon) are the only watched/monitored tokens.
        // See the reg-ex definition in "See Also" tag.

        Matcher m = AttrRegEx.CSS_INLINE_STYLE_REGEX.matcher(styleStr);


        // m.group(1): declaration-name     (stuff before the ":")
        // m.group(2): declaration-string   (stuff before the next ";", or end-of-string)
        // 
        // For Example, if the style attribute-value was specified as:
        // STYLE="font-weight: bold;   margin: 1em 1em 1em 2em;   color: #0000FF"
        //
        // The returned Properties object would contain the string key-value pair elements:
        // "font-weight"    -> "bold"
        // "margin"         -> "1em 1em 1em 2em"
        // "color"          -> "#0000FF"

        while (m.find()) p.put(m.group(1).toLowerCase(), m.group(2));

        return p;
    }
}