• 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

75.89
/src/main/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 * Copyright 2011-2026 Hazendaz
4
 */
5
package com.opensymphony.module.sitemesh.parser;
6

7
import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
8
import com.opensymphony.module.sitemesh.Page;
9
import com.opensymphony.module.sitemesh.PageParser;
10
import com.opensymphony.module.sitemesh.SitemeshBuffer;
11
import com.opensymphony.module.sitemesh.SitemeshBufferFragment;
12

13
import java.io.IOException;
14
import java.util.HashMap;
15
import java.util.Map;
16
import java.util.TreeMap;
17

18
/**
19
 * Page parser that doesn't parse the full page, but rather just parses the head section of the page.
20
 *
21
 * @since v2.5
22
 */
23
public class PartialPageParser implements PageParser {
1✔
24
    @Override
25
    public Page parse(char[] buffer) throws IOException {
26
        return parse(new DefaultSitemeshBuffer(buffer));
×
27
    }
28

29
    @Override
30
    public Page parse(SitemeshBuffer buffer) throws IOException {
31
        char[] data = buffer.getCharArray();
1✔
32
        int length = buffer.getBufferLength();
1✔
33
        int position = 0;
1✔
34
        while (position < data.length) {
1!
35
            if (data[position++] == '<') {
1✔
36
                if (position < data.length && data[position] == '!') {
1!
37
                    // Ignore doctype
38
                    continue;
×
39
                }
40
                if (compareLowerCase(data, length, position, "html")) {
1✔
41
                    // It's an HTML page, handle HTML pages
42
                    return parseHtmlPage(buffer, position);
1✔
43
                }
44
                // The whole thing is the body.
45
                return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, 0, length), null);
1✔
46
            }
47
        }
48
        // If we're here, we mustn't have found a tag
49
        return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, 0, length), null);
×
50
    }
51

52
    /**
53
     * Parses the html page.
54
     *
55
     * @param buffer
56
     *            the buffer
57
     * @param position
58
     *            the position
59
     *
60
     * @return the page
61
     */
62
    private Page parseHtmlPage(SitemeshBuffer buffer, int position) {
63
        char[] data = buffer.getCharArray();
1✔
64
        int length = buffer.getBufferLength();
1✔
65
        int bodyStart = -1;
1✔
66
        int bodyLength = -1;
1✔
67
        int headStart = -1;
1✔
68
        int headLength = -1;
1✔
69
        // Find head end and start, and body start
70
        Map<String, String> bodyProperties = null;
1✔
71
        while (position < length) {
1!
72
            if (data[position++] == '<') {
1✔
73
                if (compareLowerCase(data, length, position, "head")) {
1✔
74
                    position = findEndOf(data, length, position + 4, ">");
1✔
75
                    headStart = position;
1✔
76
                    // Find end of head
77
                    position = findStartOf(data, length, position, "</head>");
1✔
78
                    headLength = position - headStart;
1✔
79
                    position += 7;
1✔
80
                } else if (compareLowerCase(data, length, position, "body")) {
1!
81
                    HashSimpleMap map = new HashSimpleMap();
1✔
82
                    bodyStart = parseProperties(data, length, position + 4, map);
1✔
83
                    bodyProperties = map.getMap();
1✔
84
                    break;
1✔
85
                }
86
            }
87
        }
88

89
        if (bodyStart < 0) {
1!
90
            // No body found
91
            bodyStart = length;
×
92
            bodyLength = 0;
×
93
        } else {
94
            for (int i = length - 8; i > bodyStart; i--) {
1✔
95
                if (compareLowerCase(data, length, i, "</body>")) {
1✔
96
                    bodyLength = i - bodyStart;
1✔
97
                    break;
1✔
98
                }
99
            }
100
            if (bodyLength == -1) {
1✔
101
                bodyLength = length - bodyStart;
1✔
102
            }
103
        }
104

105
        if (headLength <= 0) {
1✔
106
            return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, bodyStart, bodyLength),
1✔
107
                    bodyProperties);
108
        }
109
        int idx = headStart;
1✔
110
        int headEnd = headStart + headLength;
1✔
111
        String title = null;
1✔
112

113
        TreeMap<Integer, Integer> deletions = new TreeMap<>();
1✔
114

115
        // Extract meta attributes out of head
116
        Map<String, String> metaAttributes = new HashMap<>();
1✔
117
        while (idx < headEnd) {
1✔
118
            if ((data[idx++] == '<') && compareLowerCase(data, headEnd, idx, "meta")) {
1✔
119
                MetaTagSimpleMap map = new MetaTagSimpleMap();
1✔
120
                idx = parseProperties(data, headEnd, idx + 4, map);
1✔
121
                if (map.getName() != null && map.getContent() != null) {
1!
122
                    metaAttributes.put(map.getName(), map.getContent());
1✔
123
                }
124
            }
1✔
125
        }
126

127
        // We need a new head buffer because we have to remove the title and content tags from it
128
        Map<String, String> pageProperties = new HashMap<>();
1✔
129
        for (int i = headStart; i < headEnd; i++) {
1✔
130
            char c = data[i];
1✔
131
            if (c == '<') {
1✔
132
                if (compareLowerCase(data, headEnd, i + 1, "title")) {
1✔
133
                    int titleStart = findEndOf(data, headEnd, i + 6, ">");
1✔
134
                    int titleEnd = findStartOf(data, headEnd, titleStart, "<");
1✔
135
                    title = new String(data, titleStart, titleEnd - titleStart);
1✔
136
                    int titleTagEnd = titleEnd + "</title>".length();
1✔
137
                    deletions.put(i, titleTagEnd - i);
1✔
138
                    i = titleTagEnd - 1;
1✔
139
                } else if (compareLowerCase(data, headEnd, i + 1, "content")) {
1✔
140
                    ContentTagSimpleMap map = new ContentTagSimpleMap();
1✔
141
                    int contentStart = parseProperties(data, headEnd, i + 8, map);
1✔
142
                    int contentEnd = findStartOf(data, headEnd, contentStart, "</content>");
1✔
143
                    pageProperties.put(map.getTag(), new String(data, contentStart, contentEnd - contentStart));
1✔
144
                    int contentTagEnd = contentEnd + "</content>".length();
1✔
145
                    deletions.put(i, contentTagEnd - i);
1✔
146
                    i = contentTagEnd - 1;
1✔
147
                }
148
            }
149
        }
150

151
        return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, bodyStart, bodyLength),
1✔
152
                bodyProperties, new SitemeshBufferFragment(buffer, headStart, headEnd - headStart, deletions), title,
153
                metaAttributes, pageProperties);
154
    }
155

156
    /**
157
     * Compare lower case.
158
     *
159
     * @param data
160
     *            the data
161
     * @param dataEnd
162
     *            the data end
163
     * @param position
164
     *            the position
165
     * @param token
166
     *            the token
167
     *
168
     * @return true, if successful
169
     */
170
    private static boolean compareLowerCase(final char[] data, final int dataEnd, int position, String token) {
171
        int l = position + token.length();
1✔
172
        if (l > dataEnd) {
1!
173
            return false;
×
174
        }
175
        for (int i = 0; i < token.length(); i++) {
1✔
176
            // | 32 converts from ASCII uppercase to ASCII lowercase
177
            char potential = data[position + i];
1✔
178
            char needed = token.charAt(i);
1✔
179
            if (Character.isLetter(potential) && (potential | 32) != needed || potential != needed) {
1✔
180
                return false;
1✔
181
            }
182
        }
183
        return true;
1✔
184
    }
185

186
    /**
187
     * Find end of.
188
     *
189
     * @param data
190
     *            the data
191
     * @param dataEnd
192
     *            the data end
193
     * @param position
194
     *            the position
195
     * @param token
196
     *            the token
197
     *
198
     * @return the int
199
     */
200
    private static int findEndOf(final char[] data, final int dataEnd, int position, String token) {
201
        for (int i = position; i < dataEnd - token.length(); i++) {
1!
202
            if (compareLowerCase(data, dataEnd, i, token)) {
1!
203
                return i + token.length();
1✔
204
            }
205
        }
206
        return dataEnd;
×
207
    }
208

209
    /**
210
     * Find start of.
211
     *
212
     * @param data
213
     *            the data
214
     * @param dataEnd
215
     *            the data end
216
     * @param position
217
     *            the position
218
     * @param token
219
     *            the token
220
     *
221
     * @return the int
222
     */
223
    private static int findStartOf(final char[] data, final int dataEnd, int position, String token) {
224
        for (int i = position; i < dataEnd - token.length(); i++) {
1!
225
            if (compareLowerCase(data, dataEnd, i, token)) {
1✔
226
                return i;
1✔
227
            }
228
        }
229
        return dataEnd;
×
230
    }
231

232
    /**
233
     * Parse the properties of the current tag.
234
     *
235
     * @param data
236
     *            the data
237
     * @param dataEnd
238
     *            the end index of the data
239
     * @param position
240
     *            our position in the data, this should be the first character after the tag name
241
     * @param map
242
     *            to the map to parse the properties into
243
     *
244
     * @return The position of the first character after the tag
245
     */
246
    private static int parseProperties(char[] data, int dataEnd, int position, SimpleMap map) {
247
        int idx = position;
1✔
248

249
        while (idx < dataEnd) {
1!
250
            // Skip forward to the next non-whitespace character
251
            while (idx < dataEnd && Character.isWhitespace(data[idx])) {
1!
252
                idx++;
1✔
253
            }
254

255
            // Make sure its not the end of the data or the end of the tag
256
            if (idx == dataEnd || data[idx] == '>' || data[idx] == '/') {
1!
257
                break;
1✔
258
            }
259

260
            int startAttr = idx;
1✔
261

262
            // Find the next equals
263
            while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '=' && data[idx] != '>') {
1!
264
                idx++;
1✔
265
            }
266

267
            if (idx == dataEnd || data[idx] != '=') {
1!
268
                continue;
×
269
            }
270

271
            String attrName = new String(data, startAttr, idx - startAttr);
1✔
272

273
            idx++;
1✔
274
            if (idx == dataEnd) {
1!
275
                break;
×
276
            }
277

278
            int startValue = idx;
1✔
279
            int endValue;
280
            if (data[idx] == '"') {
1!
281
                idx++;
1✔
282
                startValue = idx;
1✔
283
                while (idx < dataEnd && data[idx] != '"') {
1!
284
                    idx++;
1✔
285
                }
286
                if (idx == dataEnd) {
1!
287
                    break;
×
288
                }
289
                endValue = idx;
1✔
290
                idx++;
1✔
291
            } else if (data[idx] == '\'') {
×
292
                idx++;
×
293
                startValue = idx;
×
294
                while (idx < dataEnd && data[idx] != '\'') {
×
295
                    idx++;
×
296
                }
297
                if (idx == dataEnd) {
×
298
                    break;
×
299
                }
300
                endValue = idx;
×
301
                idx++;
×
302
            } else {
303
                while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '/' && data[idx] != '>') {
×
304
                    idx++;
×
305
                }
306
                endValue = idx;
×
307
            }
308
            String attrValue = new String(data, startValue, endValue - startValue);
1✔
309
            map.put(attrName, attrValue);
1✔
310
        }
1✔
311
        // Find the end of the tag
312
        while (idx < dataEnd && data[idx] != '>') {
1!
313
            idx++;
1✔
314
        }
315
        if (idx == dataEnd) {
1!
316
            return idx;
×
317
        }
318
        // Return the first character after the end of the tag
319
        return idx + 1;
1✔
320
    }
321

322
    /**
323
     * The Interface SimpleMap.
324
     */
325
    public interface SimpleMap {
326

327
        /**
328
         * Put.
329
         *
330
         * @param key
331
         *            the key
332
         * @param value
333
         *            the value
334
         */
335
        void put(String key, String value);
336
    }
337

338
    /**
339
     * The Class MetaTagSimpleMap.
340
     */
341
    public static class MetaTagSimpleMap implements SimpleMap {
1✔
342

343
        /** The name. */
344
        private String name;
345

346
        /** The content. */
347
        private String content;
348

349
        @Override
350
        public void put(String key, String value) {
351
            if (key.equals("name")) {
1✔
352
                name = value;
1✔
353
            } else if (key.equals("content")) {
1!
354
                content = value;
1✔
355
            }
356
        }
1✔
357

358
        /**
359
         * Gets the name.
360
         *
361
         * @return the name
362
         */
363
        public String getName() {
364
            return name;
1✔
365
        }
366

367
        /**
368
         * Gets the content.
369
         *
370
         * @return the content
371
         */
372
        public String getContent() {
373
            return content;
1✔
374
        }
375
    }
376

377
    /**
378
     * The Class ContentTagSimpleMap.
379
     */
380
    public static class ContentTagSimpleMap implements SimpleMap {
1✔
381

382
        /** The tag. */
383
        private String tag;
384

385
        @Override
386
        public void put(String key, String value) {
387
            if (key.equals("tag")) {
1!
388
                tag = value;
1✔
389
            }
390
        }
1✔
391

392
        /**
393
         * Gets the tag.
394
         *
395
         * @return the tag
396
         */
397
        public String getTag() {
398
            return tag;
1✔
399
        }
400
    }
401

402
    /**
403
     * The Class HashSimpleMap.
404
     */
405
    public static class HashSimpleMap implements SimpleMap {
1✔
406

407
        /** The map. */
408
        private final Map<String, String> map = new HashMap<>();
1✔
409

410
        @Override
411
        public void put(String key, String value) {
412
            map.put(key, value);
1✔
413
        }
1✔
414

415
        /**
416
         * Gets the map.
417
         *
418
         * @return the map
419
         */
420
        public Map<String, String> getMap() {
421
            return map;
1✔
422
        }
423
    }
424
}
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