• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / sitemesh2 / 59

22 Mar 2026 02:30AM UTC coverage: 40.347%. Remained the same
59

push

github

hazendaz
[mvn] Update maven wrapper

698 of 1891 branches covered (36.91%)

Branch coverage included in aggregate %.

1555 of 3693 relevant lines covered (42.11%)

0.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.82
/src/main/java/com/opensymphony/module/sitemesh/html/tokenizer/Parser.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 * Copyright 2011-2026 Hazendaz
4
 */
5
/*
6
 * IF YOU ARE HAVING TROUBLE COMPILING THIS CLASS, IT IS PROBABLY BECAUSE Lexer.java IS MISSING.
7
 *
8
 * Use 'ant jflex' to generate the file, which will reside in build/java
9
 */
10

11
package com.opensymphony.module.sitemesh.html.tokenizer;
12

13
import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
14
import com.opensymphony.module.sitemesh.SitemeshBufferFragment;
15
import com.opensymphony.module.sitemesh.html.Tag;
16
import com.opensymphony.module.sitemesh.html.Text;
17
import com.opensymphony.module.sitemesh.html.util.CharArray;
18
import com.opensymphony.module.sitemesh.util.CharArrayReader;
19

20
import java.io.IOException;
21

22
/**
23
 * Looks for patterns of tokens in the Lexer and translates these to calls to pass to the TokenHandler.
24
 *
25
 * @author Joe Walnes
26
 *
27
 * @see TagTokenizer
28
 */
29
public class Parser extends Lexer {
30

31
    /** The attribute buffer. */
32
    private final CharArray attributeBuffer = new CharArray(64);
1✔
33

34
    /** The reusable token. */
35
    private final ReusableToken reusableToken = new ReusableToken();
1✔
36

37
    /** The pushback token. */
38
    private int pushbackToken = -1;
1✔
39

40
    /** The pushback text. */
41
    private String pushbackText;
42

43
    /** The Constant SLASH. */
44
    public static final short SLASH = 257;
45

46
    /** The Constant WHITESPACE. */
47
    public static final short WHITESPACE = 258;
48

49
    /** The Constant EQUALS. */
50
    public static final short EQUALS = 259;
51

52
    /** The Constant QUOTE. */
53
    public static final short QUOTE = 260;
54

55
    /** The Constant WORD. */
56
    public static final short WORD = 261;
57

58
    /** The Constant TEXT. */
59
    public static final short TEXT = 262;
60

61
    /** The Constant QUOTED. */
62
    public static final short QUOTED = 263;
63

64
    /** The Constant LT. */
65
    public static final short LT = 264;
66

67
    /** The Constant GT. */
68
    public static final short GT = 265;
69

70
    /** The Constant LT_OPEN_MAGIC_COMMENT. */
71
    public static final short LT_OPEN_MAGIC_COMMENT = 266;
72

73
    /** The Constant LT_CLOSE_MAGIC_COMMENT. */
74
    public static final short LT_CLOSE_MAGIC_COMMENT = 267;
75

76
    /** The input. */
77
    private final char[] input;
78

79
    /** The handler. */
80
    private TokenHandler handler;
81

82
    /** The position. */
83
    private int position;
84

85
    /** The length. */
86
    private int length;
87

88
    /** The name. */
89
    private String name;
90

91
    /** The type. */
92
    private int type;
93

94
    /**
95
     * Instantiates a new parser.
96
     *
97
     * @param input
98
     *            the input
99
     * @param length
100
     *            the length
101
     * @param handler
102
     *            the handler
103
     */
104
    public Parser(char[] input, int length, TokenHandler handler) {
105
        super(new CharArrayReader(input, 0, length));
1✔
106
        this.input = input;
1✔
107
        this.handler = handler;
1✔
108
    }
1✔
109

110
    /**
111
     * Text.
112
     *
113
     * @return the string
114
     */
115
    private String text() {
116
        if (pushbackToken == -1) {
1!
117
            return yytext();
1✔
118
        }
119
        return pushbackText;
×
120
    }
121

122
    /**
123
     * Skip white space.
124
     *
125
     * @throws IOException
126
     *             Signals that an I/O exception has occurred.
127
     */
128
    private void skipWhiteSpace() throws IOException {
129
        while (true) {
130
            int next;
131
            if (pushbackToken == -1) {
1✔
132
                next = yylex();
1✔
133
            } else {
134
                next = pushbackToken;
1✔
135
                pushbackToken = -1;
1✔
136
            }
137
            if (next != Parser.WHITESPACE) {
1✔
138
                pushBack(next);
1✔
139
                break;
1✔
140
            }
141
        }
1✔
142
    }
1✔
143

144
    /**
145
     * Push back.
146
     *
147
     * @param next
148
     *            the next
149
     */
150
    private void pushBack(int next) {
151
        if (pushbackToken != -1) {
1!
152
            reportError("Cannot pushback more than once", line(), column());
×
153
        }
154
        pushbackToken = next;
1✔
155
        if (next == Parser.WORD || next == Parser.QUOTED || next == Parser.SLASH || next == Parser.EQUALS) {
1✔
156
            pushbackText = yytext();
1✔
157
        } else {
158
            pushbackText = null;
1✔
159
        }
160
    }
1✔
161

162
    /**
163
     * Start.
164
     */
165
    public void start() {
166
        try {
167
            while (true) {
168
                int token;
169
                if (pushbackToken == -1) {
1✔
170
                    token = yylex();
1✔
171
                } else {
172
                    token = pushbackToken;
1✔
173
                    pushbackToken = -1;
1✔
174
                }
175
                switch (token) {
1✔
176
                    case 0:
177
                        // EOF
178
                        return;
1✔
179
                    case Parser.TEXT:
180
                        // Got some text
181
                        parsedText(position(), length());
1✔
182
                        break;
1✔
183
                    case Parser.LT:
184
                        // Token "<" - start of tag
185
                        parseTag(Tag.OPEN);
1✔
186
                        break;
1✔
187
                    case Parser.LT_OPEN_MAGIC_COMMENT:
188
                        // Token "<!--[" - start of open magic comment
189
                        parseTag(Tag.OPEN_MAGIC_COMMENT);
1✔
190
                        break;
1✔
191
                    case Parser.LT_CLOSE_MAGIC_COMMENT:
192
                        // Token "<![" - start of close magic comment
193
                        parseTag(Tag.CLOSE_MAGIC_COMMENT);
1✔
194
                        break;
1✔
195
                    default:
196
                        reportError("Unexpected token from lexer, was expecting TEXT or LT", line(), column());
1✔
197
                        break;
198
                }
199
            }
1✔
200
        } catch (IOException e) {
×
201
            throw new RuntimeException(e);
×
202
        }
203
    }
204

205
    /**
206
     * Parses the tag.
207
     *
208
     * @param type
209
     *            the type
210
     *
211
     * @throws IOException
212
     *             Signals that an I/O exception has occurred.
213
     */
214
    private void parseTag(int type) throws IOException {
215
        // Start parsing a TAG
216

217
        int start = position();
1✔
218
        skipWhiteSpace();
1✔
219
        int token;
220
        if (pushbackToken == -1) {
1!
221
            token = yylex();
×
222
        } else {
223
            token = pushbackToken;
1✔
224
            pushbackToken = -1;
1✔
225
        }
226

227
        if (token == Parser.SLASH) {
1✔
228
            // Token "/" - it's a closing tag
229
            type = Tag.CLOSE;
1✔
230
            if (pushbackToken == -1) {
1!
231
                token = yylex();
1✔
232
            } else {
233
                token = pushbackToken;
×
234
                pushbackToken = -1;
×
235
            }
236
        }
237

238
        switch (token) {
1!
239
            case Parser.WORD: {
240
                // Token WORD - name of tag
241
                String name = text();
1✔
242
                if (handler.shouldProcessTag(name)) {
1✔
243
                    parseFullTag(type, name, start);
1✔
244
                } else {
245
                    resetLexerState();
1✔
246
                    pushBack(yylex()); // take and replace the next token, so the position is correct
1✔
247
                    parsedText(start, position() - start);
1✔
248
                }
249
                break;
1✔
250
            }
251
            case Parser.GT:
252
                break;
1✔
253
            case 0:
254
                parsedText(start, position() - start); // eof
1✔
255
                break;
1✔
256
            default:
257
                reportError("Could not recognise tag", line(), column());
×
258
                break;
259
        }
260
    }
1✔
261

262
    /**
263
     * Parses the full tag.
264
     *
265
     * @param type
266
     *            the type
267
     * @param name
268
     *            the name
269
     * @param start
270
     *            the start
271
     *
272
     * @throws IOException
273
     *             Signals that an I/O exception has occurred.
274
     */
275
    private void parseFullTag(int type, String name, int start) throws IOException {
276
        int token;
277
        while (true) {
278
            skipWhiteSpace();
1✔
279
            if (pushbackToken == -1) {
1!
280
                token = yylex();
×
281
            } else {
282
                token = pushbackToken;
1✔
283
                pushbackToken = -1;
1✔
284
            }
285
            pushBack(token);
1✔
286

287
            if (token == Parser.SLASH || token == Parser.GT) {
1✔
288
                break; // no more attributes here
1✔
289
            }
290
            if (token == Parser.WORD) {
1✔
291
                parseAttribute(); // start of an attribute
1✔
292
            } else if (token == 0) {
1✔
293
                parsedText(start, position() - start); // eof
1✔
294
                return;
1✔
295
            } else {
296
                reportError("Illegal tag", line(), column());
1✔
297
                break;
1✔
298
            }
299
        }
300

301
        if (pushbackToken == -1) {
1!
302
            token = yylex();
×
303
        } else {
304
            token = pushbackToken;
1✔
305
            pushbackToken = -1;
1✔
306
        }
307
        if (token == Parser.SLASH) {
1✔
308
            // Token "/" - it's an empty tag
309
            type = Tag.EMPTY;
1✔
310
            if (pushbackToken == -1) {
1!
311
                token = yylex();
1✔
312
            } else {
313
                token = pushbackToken;
×
314
                pushbackToken = -1;
×
315
            }
316
        }
317

318
        if (token == Parser.GT) {
1✔
319
            // Token ">" - YAY! end of tag.. process it!
320
            parsedTag(type, name, start, position() - start + 1);
1✔
321
        } else if (token == 0) {
1✔
322
            parsedText(start, position() - start); // eof
1✔
323
        } else {
324
            reportError("Expected end of tag", line(), column());
1✔
325
            parsedTag(type, name, start, position() - start + 1);
1✔
326
        }
327
    }
1✔
328

329
    /**
330
     * Parses the attribute.
331
     *
332
     * @throws IOException
333
     *             Signals that an I/O exception has occurred.
334
     */
335
    private void parseAttribute() throws IOException {
336
        int token;
337
        if (pushbackToken == -1) {
1!
338
            token = yylex();
×
339
        } else {
340
            token = pushbackToken;
1✔
341
            pushbackToken = -1;
1✔
342
        }
343
        // Token WORD - start of an attribute
344
        String attributeName = text();
1✔
345
        skipWhiteSpace();
1✔
346
        if (pushbackToken == -1) {
1!
347
            token = yylex();
×
348
        } else {
349
            token = pushbackToken;
1✔
350
            pushbackToken = -1;
1✔
351
        }
352
        switch (token) {
1!
353
            case Parser.EQUALS:
354
                // Token "=" - the attribute has a value
355
                skipWhiteSpace();
1✔
356
                if (pushbackToken == -1) {
1!
357
                    token = yylex();
×
358
                } else {
359
                    token = pushbackToken;
1✔
360
                    pushbackToken = -1;
1✔
361
                }
362
                if (token == Parser.QUOTED) {
1✔
363
                    // token QUOTED - a quoted literal as the attribute value
364
                    parsedAttribute(attributeName, text(), true);
1✔
365
                } else if (token == Parser.WORD || token == Parser.SLASH) {
1✔
366
                    // unquoted word
367
                    attributeBuffer.clear();
1✔
368
                    attributeBuffer.append(text());
1✔
369
                    while (true) {
370
                        int next;
371
                        if (pushbackToken == -1) {
1!
372
                            next = yylex();
1✔
373
                        } else {
374
                            next = pushbackToken;
×
375
                            pushbackToken = -1;
×
376
                        }
377
                        if ((next != Parser.WORD) && (next != Parser.EQUALS) && (next != Parser.SLASH)) {
1✔
378
                            pushBack(next);
1✔
379
                            break;
1✔
380
                        }
381
                        attributeBuffer.append(text());
1✔
382
                    }
1✔
383
                    parsedAttribute(attributeName, attributeBuffer.toString(), false);
1✔
384
                } else if (token == Parser.SLASH || token == Parser.GT) {
1!
385
                    // no more attributes
386
                    pushBack(token);
×
387
                } else if (token == 0) {
1!
388
                    return;
1✔
389
                } else {
390
                    reportError("Illegal attribute value", line(), column());
×
391
                }
392
                break;
×
393
            case Parser.SLASH:
394
            case Parser.GT:
395
            case Parser.WORD:
396
                // it was a value-less HTML style attribute
397
                parsedAttribute(attributeName, null, false);
1✔
398
                pushBack(token);
1✔
399
                break;
1✔
400
            case 0:
401
                return;
1✔
402
            default:
403
                reportError("Illegal attribute name", line(), column());
×
404
                break;
405
        }
406
    }
1✔
407

408
    /**
409
     * Parsed text.
410
     *
411
     * @param position
412
     *            the position
413
     * @param length
414
     *            the length
415
     */
416
    protected void parsedText(int position, int length) {
417
        this.position = position;
1✔
418
        this.length = length;
1✔
419
        handler.text(reusableToken);
1✔
420
    }
1✔
421

422
    /**
423
     * Parsed tag.
424
     *
425
     * @param type
426
     *            the type
427
     * @param name
428
     *            the name
429
     * @param start
430
     *            the start
431
     * @param length
432
     *            the length
433
     */
434
    protected void parsedTag(int type, String name, int start, int length) {
435
        this.type = type;
1✔
436
        this.name = name;
1✔
437
        this.position = start;
1✔
438
        this.length = length;
1✔
439
        handler.tag(reusableToken);
1✔
440
        reusableToken.attributeCount = 0;
1✔
441
    }
1✔
442

443
    /**
444
     * Parsed attribute.
445
     *
446
     * @param name
447
     *            the name
448
     * @param value
449
     *            the value
450
     * @param quoted
451
     *            the quoted
452
     */
453
    protected void parsedAttribute(String name, String value, boolean quoted) {
454
        if (reusableToken.attributeCount + 2 >= reusableToken.attributes.length) {
1✔
455
            String[] newAttributes = new String[reusableToken.attributeCount * 2];
1✔
456
            System.arraycopy(reusableToken.attributes, 0, newAttributes, 0, reusableToken.attributeCount);
1✔
457
            reusableToken.attributes = newAttributes;
1✔
458
        }
459
        reusableToken.attributes[reusableToken.attributeCount++] = name;
1✔
460
        if (quoted) {
1✔
461
            reusableToken.attributes[reusableToken.attributeCount++] = value.substring(1, value.length() - 1);
1✔
462
        } else {
463
            reusableToken.attributes[reusableToken.attributeCount++] = value;
1✔
464
        }
465
    }
1✔
466

467
    @Override
468
    protected void reportError(String message, int line, int column) {
469
        handler.warning(message, line, column);
1✔
470
    }
1✔
471

472
    /**
473
     * The Class ReusableToken.
474
     */
475
    public class ReusableToken implements Tag, Text {
1✔
476

477
        /** The attribute count. */
478
        public int attributeCount = 0;
1✔
479

480
        /** The attributes. */
481
        public String[] attributes = new String[10]; // name1, value1, name2, value2...
1✔
482

483
        @Override
484
        public String getName() {
485
            return name;
1✔
486
        }
487

488
        @Override
489
        public int getType() {
490
            return type;
1✔
491
        }
492

493
        @Override
494
        public String getContents() {
495
            return new String(input, position, length);
1✔
496
        }
497

498
        @Override
499
        public void writeTo(SitemeshBufferFragment.Builder buffer, int position) {
500
            buffer.insert(position, SitemeshBufferFragment.builder().setBuffer(new DefaultSitemeshBuffer(input))
×
501
                    .setStart(position).setLength(length).build());
×
502
        }
×
503

504
        @Override
505
        public int getAttributeCount() {
506
            return attributeCount / 2;
1✔
507
        }
508

509
        @Override
510
        public int getAttributeIndex(String name, boolean caseSensitive) {
511
            if (attributeCount == 0) {
1!
512
                return -1;
×
513
            }
514
            final int len = attributeCount;
1✔
515
            for (int i = 0; i < len; i += 2) {
1✔
516
                final String current = attributes[i];
1✔
517
                if (caseSensitive ? name.equals(current) : name.equalsIgnoreCase(current)) {
1!
518
                    return i / 2;
1✔
519
                }
520
            }
521
            return -1;
1✔
522
        }
523

524
        @Override
525
        public String getAttributeName(int index) {
526
            return attributes[index * 2];
1✔
527
        }
528

529
        @Override
530
        public String getAttributeValue(int index) {
531
            return attributes[index * 2 + 1];
1✔
532
        }
533

534
        @Override
535
        public String getAttributeValue(String name, boolean caseSensitive) {
536
            if (attributeCount == 0) {
1✔
537
                return null;
1✔
538
            }
539
            final int len = attributeCount;
1✔
540
            for (int i = 0; i < len; i += 2) {
1!
541
                final String current = attributes[i];
1✔
542
                if (caseSensitive ? name.equals(current) : name.equalsIgnoreCase(current)) {
1!
543
                    return attributes[i + 1];
1✔
544
                }
545
            }
546
            return null;
×
547
        }
548

549
        @Override
550
        public boolean hasAttribute(String name, boolean caseSensitive) {
551
            return getAttributeIndex(name, caseSensitive) > -1;
1✔
552
        }
553

554
        @Override
555
        public int getPosition() {
556
            return position;
1✔
557
        }
558

559
        @Override
560
        public int getLength() {
561
            return length;
1✔
562
        }
563

564
    }
565
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc