HtmlParser.java

1
package com.renomad.minum.htmlparsing;
2
3
import com.renomad.minum.security.ForbiddenUseException;
4
import com.renomad.minum.utils.RingBuffer;
5
6
import java.io.ByteArrayInputStream;
7
import java.nio.charset.StandardCharsets;
8
import java.util.*;
9
10
/**
11
 * Converts HTML strings to object trees.
12
 * <p>
13
 *     Enables a developer to analyze an HTML document by its
14
 *     structure.
15
 * </p>
16
 * <p>
17
 *     Note: HTML parsing is difficult because
18
 *     of its lenient specification.  See Postel's Law.
19
 * </p>
20
 * <p>
21
 *     For our purposes, it is less important
22
 *     to perfectly meet the criteria of the spec, so
23
 *     there will be numerous edge-cases unaccounted-for
24
 *     by this implementation.  Nevertheless, this program
25
 *     should suit many needs for ordinary web applications.
26
 * </p>
27
 */
28
public final class HtmlParser {
29
30
    /**
31
     * Most total chars we'll read.
32
     */
33
    static final int MAX_HTML_SIZE = 2 * 1024 * 1024;
34
35
    /**
36
     * Given any HTML input, scan through and generate a tree
37
     * of HTML nodes.  Return a list of the roots of the tree.
38
     * <p>
39
     * This parser operates with a very particular paradigm in mind. I'll explain
40
     * it through examples.  Let's look at some typical HTML:
41
     * </p>
42
     * <pre>{@code <p>Hello world</p>}</pre>
43
     * <p>
44
     * The way we will model this is as follows:
45
     * </p>
46
     * <pre>{@code <ELEMENT_NAME_AND_DETAILS>content<END_OF_ELEMENT>}</pre>
47
     * <p>
48
     * We will examine the first part, "ELEMENT_NAME_AND_DETAILS", and
49
     * grab the element's name and any attributes.  Then we will descend into the
50
     * content section.  We know we have hit the end of the element by keeping
51
     * track of how far we have descended/ascended and whether we are hitting
52
     * a closing HTML element.
53
     * </p>
54
     * <p>
55
     * Complicating this is that elements may not have content, for example
56
     * any <a href="https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element_xref3">void elements</a>
57
     * or when a user chooses to create an empty tag
58
     * </p>
59
     */
60
    public List<HtmlParseNode> parse(String input) {
61 2 1. parse : changed conditional boundary → KILLED
2. parse : negated conditional → KILLED
        if (input.length() > MAX_HTML_SIZE)
62
            throw new ForbiddenUseException("Input exceeds max allowed HTML text size, " + MAX_HTML_SIZE + " chars");
63
        var is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
64
65
        List<HtmlParseNode> nodes = new ArrayList<>();
66
        State state = State.buildNewState();
67
68
        while (true) {
69
            int value = is.read();
70
            // if the value is -1, there's nothing left to read
71 2 1. parse : replaced return value with Collections.emptyList for com/renomad/minum/htmlparsing/HtmlParser::parse → TIMED_OUT
2. parse : negated conditional → KILLED
            if (value == -1) return nodes;
72
73
            char currentChar = (char) value;
74 1 1. parse : removed call to com/renomad/minum/htmlparsing/HtmlParser::processState → KILLED
            processState(currentChar, state, nodes);
75
        }
76
    }
77
78
    /**
79
     * Use important symbols in the HTML code to indicate
80
     * which mode we are in - reading inside a tag, or between
81
     * tags.
82
     * <p>
83
     * Apologies to future readers.  Hand-written parser code is the suck.
84
     * </p>
85
     * <p>
86
     * That said, there are plenty of tests exercising this, and it is
87
     * easy to test due to having been built using TDD.  Cold comfort, I know.
88
     * </p>
89
     */
90
    private void processState(char currentChar, State state, List<HtmlParseNode> nodes) {
91 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::recordLocation → TIMED_OUT
        recordLocation(currentChar, state);
92
93
        // keep track of previous twelve characters, to check if inside comments and scripts
94 1 1. processState : removed call to com/renomad/minum/utils/RingBuffer::add → KILLED
        state.previousCharacters.add(currentChar);
95 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::determineCommentState → KILLED
        determineCommentState(state);
96 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::determineScriptState → KILLED
        determineScriptState(state);
97 1 1. processState : negated conditional → KILLED
        if (state.isInsideComment) {
98
            return;
99
        }
100 1 1. processState : negated conditional → KILLED
        if (state.isInsideScript) {
101
            state.stringBuilder.append(currentChar);
102
            return;
103
        }
104
105 1 1. processState : negated conditional → KILLED
        if (currentChar == '<') {
106 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::processLessThan → KILLED
            processLessThan(currentChar, state);
107 1 1. processState : negated conditional → KILLED
        } else if (currentChar == '>') {
108 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::processGreaterThan → KILLED
            processGreaterThan(currentChar, state, nodes);
109
        } else {
110 1 1. processState : removed call to com/renomad/minum/htmlparsing/HtmlParser::addingToken → KILLED
            addingToken(state, currentChar);
111
        }
112
    }
113
114
    /**
115
     * handle basic recording of stats, like row and column,
116
     * useful during error messages
117
     */
118
    private static void recordLocation(char currentChar, State state) {
119 1 1. recordLocation : Replaced integer addition with subtraction → KILLED
        state.charsRead += 1;
120 1 1. recordLocation : negated conditional → KILLED
        if (currentChar == '\n') {
121 1 1. recordLocation : Replaced integer addition with subtraction → KILLED
            state.lineRow += 1;
122
            state.lineColumn = 0;
123
        }
124 1 1. recordLocation : Replaced integer addition with subtraction → TIMED_OUT
        state.lineColumn += 1;
125
    }
126
127
    private void processGreaterThan(char currentChar, State state, List<HtmlParseNode> nodes) {
128
        /* It's allowed to use greater-than signs in a lot of places */
129 1 1. processGreaterThan : negated conditional → KILLED
        if (state.isInsideTag) {
130 1 1. processGreaterThan : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleExitingTag → KILLED
            handleExitingTag(currentChar, state, nodes);
131
        } else {
132
            /*
133
            This situation means we're looking at a
134
            free-floating greater-than symbol in
135
            the html text.
136
            */
137
            state.stringBuilder.append(currentChar);
138
        }
139
    }
140
141
    /**
142
     * As we leave the tag, we make some decisions about it.
143
     */
144
    private void handleExitingTag(char currentChar, State state, List<HtmlParseNode> nodes) {
145 1 1. handleExitingTag : negated conditional → KILLED
        if (state.isInsideAttributeValueQuoted) {
146
            /*
147
            Here, we're looking at a greater-than
148
            that is inside a quoted attribute value
149
            */
150
            state.stringBuilder.append(currentChar);
151
        } else {
152 1 1. handleExitingTag : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleTagComponents → KILLED
            handleTagComponents(state, nodes);
153
        }
154
    }
155
156
    private void handleTagComponents(State state, List<HtmlParseNode> nodes) {
157 1 1. handleTagComponents : negated conditional → KILLED
        if (hasFinishedBuildingTagname(state.hasEncounteredTagName, state.tagName, state.stringBuilder)) {
158
            state.tagName = state.stringBuilder.toString();
159 3 1. handleTagComponents : negated conditional → KILLED
2. handleTagComponents : negated conditional → KILLED
3. handleTagComponents : negated conditional → KILLED
        } else if (!state.stringBuilder.isEmpty() && state.currentAttributeKey.isBlank() && state.isReadingAttributeKey) {
160
            state.attributes.put(state.stringBuilder.toString(), "");
161
            state.stringBuilder = new StringBuilder();
162
            state.isReadingAttributeKey = false;
163 1 1. handleTagComponents : negated conditional → KILLED
        } else if (!state.currentAttributeKey.isBlank()) {
164
            // if we were in the midst of reading attribute stuff when we hit the closing bracket...
165 1 1. handleTagComponents : negated conditional → KILLED
            if (!state.stringBuilder.isEmpty()) {
166
                state.attributes.put(state.currentAttributeKey, state.stringBuilder.toString());
167
            } else {
168
                state.attributes.put(state.currentAttributeKey, "");
169
            }
170
            state.isInsideAttributeValueQuoted = false;
171
            state.stringBuilder = new StringBuilder();
172
            state.currentAttributeKey = "";
173
        }
174
175 1 1. handleTagComponents : removed call to com/renomad/minum/htmlparsing/HtmlParser::processTagAndResetState → KILLED
        processTagAndResetState(state, nodes);
176
    }
177
178
    static boolean hasFinishedBuildingTagname(boolean hasEncounteredTagName, String tagName, StringBuilder sb) {
179 4 1. hasFinishedBuildingTagname : negated conditional → KILLED
2. hasFinishedBuildingTagname : negated conditional → KILLED
3. hasFinishedBuildingTagname : replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::hasFinishedBuildingTagname → KILLED
4. hasFinishedBuildingTagname : negated conditional → KILLED
        return hasEncounteredTagName && tagName.isEmpty() && !sb.isEmpty();
180
    }
181
182
    private void processLessThan(char currentChar, State state) {
183
        /* less-than signs are policed strictly */
184 1 1. processLessThan : negated conditional → KILLED
        if (state.isInsideAttributeValueQuoted) {
185
            /*
186
            Here, we're looking at a less-than that
187
            is inside a quoted attribute value
188
            */
189
            state.stringBuilder.append(currentChar);
190
        } else {
191 1 1. processLessThan : removed call to com/renomad/minum/htmlparsing/HtmlParser::enteringTag → KILLED
            enteringTag(state);
192
        }
193
    }
194
195
    /**
196
     * When we've read a less-than sign and are entering an HTML tag.
197
     */
198
    private void enteringTag(State state) {
199 1 1. enteringTag : removed call to com/renomad/minum/htmlparsing/HtmlParser::addText → KILLED
        addText(state);
200
201
        state.isInsideTag = true;
202
        /*
203
        not really sure it's a start tag, but if we
204
        assume it is that's fine, because if we hit
205
        a forward slash at the beginning, it becomes
206
        a non-start-tag.
207
         */
208
        state.isStartTag = true;
209
        state.stringBuilder = new StringBuilder();
210
    }
211
212
    private static void addText(State state) {
213 1 1. addText : negated conditional → KILLED
        if (!state.stringBuilder.isEmpty()) {
214
215
            String textContent = state.stringBuilder.toString();
216
217
            // This is where we add characters if we found any between tags.
218 2 1. addText : negated conditional → TIMED_OUT
2. addText : negated conditional → KILLED
            if (! state.parseStack.isEmpty() && ! textContent.isBlank()) {
219 1 1. addText : removed call to com/renomad/minum/htmlparsing/HtmlParseNode::addToInnerContent → KILLED
                state.parseStack.peek().addToInnerContent(new HtmlParseNode(ParseNodeType.CHARACTERS, TagInfo.EMPTY, new ArrayList<>(), textContent));
220
            }
221
        }
222
    }
223
224
    /**
225
     * Called when we've just hit a greater-than sign and thus
226
     * exited an HTML tag.
227
     */
228
    private void processTagAndResetState(State state, List<HtmlParseNode> nodes) {
229 1 1. processTagAndResetState : removed call to com/renomad/minum/htmlparsing/HtmlParser::processTag → KILLED
        processTag(state, nodes);
230
231
        state.isHalfClosedTag = false;
232
        state.isInsideTag = false;
233
        state.isStartTag = false;
234
        state.isReadingTagName = false;
235
        state.tagName = "";
236
        state.attributes = new HashMap<>();
237
        state.hasEncounteredTagName = false;
238
        state.stringBuilder = new StringBuilder();
239
    }
240
241
    /**
242
     * The commonest case when reading characters.  Buckle up.
243
     */
244
    private void addingToken(State state, char currentChar) {
245 2 1. addingToken : negated conditional → KILLED
2. addingToken : negated conditional → KILLED
        var hasNotBegunReadingTagName = state.isInsideTag && !state.hasEncounteredTagName;
246
247 1 1. addingToken : negated conditional → KILLED
        if (hasNotBegunReadingTagName) {
248 1 1. addingToken : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleBeforeReadingTagName → KILLED
            handleBeforeReadingTagName(state, currentChar);
249 1 1. addingToken : negated conditional → KILLED
        } else if (state.isReadingTagName) {
250 1 1. addingToken : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleReadingTagName → KILLED
            handleReadingTagName(state, currentChar);
251 1 1. addingToken : negated conditional → KILLED
        } else if (isFinishedReadingTag(state.tagName, state.isInsideTag)) {
252 1 1. addingToken : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleAfterReadingTagName → KILLED
                handleAfterReadingTagName(state, currentChar);
253
        } else {
254
            state.stringBuilder.append(currentChar);
255
        }
256
    }
257
258
    static boolean isFinishedReadingTag(String tagName, boolean isInsideTag) {
259 3 1. isFinishedReadingTag : negated conditional → KILLED
2. isFinishedReadingTag : replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::isFinishedReadingTag → KILLED
3. isFinishedReadingTag : negated conditional → KILLED
        return !tagName.isEmpty() && isInsideTag;
260
    }
261
262
    static final List<Character> startOfComment = List.of('<', '!', '-', '-');
263
    static final List<Character> endOfComment = List.of('-', '-', '>');
264
265
    /**
266
     * Returns whether we are inside an HTML comment,
267
     * that is {@code <!-- -->}
268
     */
269
    private void determineCommentState(State state) {
270
        boolean atCommentStart = state.previousCharacters.containsAt(startOfComment, 8);
271
        boolean atCommentEnd = state.previousCharacters.containsAt(endOfComment, 8);
272
        boolean isInsideTag = state.isInsideTag;
273
        boolean hasEncounteredTagName = state.hasEncounteredTagName;
274 3 1. determineCommentState : negated conditional → KILLED
2. determineCommentState : negated conditional → KILLED
3. determineCommentState : negated conditional → KILLED
        if (isInsideTag && !hasEncounteredTagName && atCommentStart) {
275
            state.isInsideComment = true;
276
            state.isInsideTag = false;
277 2 1. determineCommentState : negated conditional → KILLED
2. determineCommentState : negated conditional → KILLED
        } else if (state.isInsideComment && atCommentEnd) {
278
            state.isInsideComment = false;
279
        }
280
    }
281
282
    static final List<Character> scriptElement = List.of('<','/','s','c','r','i','p','t','>');
283
284
    /**
285
     * Determines whether we have hit the end of the script block
286
     * by looking for the closing script tag.
287
     */
288
    private void determineScriptState(State state) {
289
        boolean isScriptFinished = state.previousCharacters.containsAt(scriptElement, 3);
290
        boolean wasInsideScript = state.isInsideScript;
291 2 1. determineScriptState : negated conditional → KILLED
2. determineScriptState : negated conditional → KILLED
        state.isInsideScript = state.isInsideScript && !isScriptFinished;
292 2 1. determineScriptState : negated conditional → KILLED
2. determineScriptState : negated conditional → KILLED
        boolean justClosedScriptTag = wasInsideScript && !state.isInsideScript;
293 1 1. determineScriptState : negated conditional → KILLED
        if (justClosedScriptTag) {
294
            state.tagName = "script";
295
            state.isInsideTag = true;
296
            state.isStartTag = false;
297
            var innerTextLength = state.stringBuilder.length();
298 1 1. determineScriptState : Replaced integer subtraction with addition → KILLED
            state.stringBuilder.delete(innerTextLength - 8, innerTextLength);
299 1 1. determineScriptState : removed call to com/renomad/minum/htmlparsing/HtmlParser::addText → KILLED
            addText(state);
300
        }
301
302
    }
303
304
    /**
305
     * at this point we have a tagname for our tag, and we're still in the tag
306
     */
307
    private static void handleAfterReadingTagName(State state, char currentChar) {
308
309
        boolean isHandlingAttributes = isHandlingAttributes(state, currentChar);
310 1 1. handleAfterReadingTagName : negated conditional → KILLED
        if (isHandlingAttributes) {
311
312 1 1. handleAfterReadingTagName : negated conditional → KILLED
            if (state.currentAttributeKey.isBlank()) {
313
                /*
314
                because the key is blank, we know we haven't read it all. That's
315
                because when we finish reading the key, we'll add it to currentAttributeKey
316
                and be in the mode of reading the value.
317
                 */
318 1 1. handleAfterReadingTagName : removed call to com/renomad/minum/htmlparsing/HtmlParser::handleNotFullyReadAttributeKey → KILLED
                handleNotFullyReadAttributeKey(state, currentChar);
319
            } else {
320
                // reading in the (potential) attribute value
321
322 1 1. handleAfterReadingTagName : removed call to com/renomad/minum/htmlparsing/HtmlParser::handlePotentialAttributeValue → KILLED
                handlePotentialAttributeValue(state, currentChar);
323
            }
324
        }
325
    }
326
327
    /**
328
     * Check whether we're past the whitespace between the tag name and
329
     * the start of the (potential) attribute key.
330
     */
331
    static boolean isHandlingAttributes(State state, char currentChar) {
332 2 1. isHandlingAttributes : replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::isHandlingAttributes → KILLED
2. isHandlingAttributes : negated conditional → KILLED
        return ! (state.currentAttributeKey.isEmpty() &&
333 2 1. isHandlingAttributes : negated conditional → KILLED
2. isHandlingAttributes : negated conditional → KILLED
                        state.stringBuilder.isEmpty()
334
                        && currentChar == ' ');
335
    }
336
337
    private static void handlePotentialAttributeValue(State state, char currentChar) {
338 1 1. handlePotentialAttributeValue : negated conditional → KILLED
        if (state.isInsideAttributeValueQuoted) {
339
            // if we're already inside a quoted area, encountering a
340
            // closing quote will take us out of it.
341 1 1. handlePotentialAttributeValue : negated conditional → KILLED
            if (currentChar == state.quoteType.literal) {
342
                // if we hit the matching end-quote, switch modes
343
                state.isInsideAttributeValueQuoted = false;
344
                state.quoteType = QuoteType.NONE;
345
                state.attributes.put(state.currentAttributeKey, state.stringBuilder.toString());
346
                state.stringBuilder = new StringBuilder();
347
                state.currentAttributeKey = "";
348
                state.isReadingAttributeKey = false;
349
            } else {
350
                // otherwise keep on trucking, adding characters
351
                state.stringBuilder.append(currentChar);
352
            }
353
        } else {
354 2 1. handlePotentialAttributeValue : negated conditional → KILLED
2. handlePotentialAttributeValue : negated conditional → KILLED
            if (currentChar == '"' || currentChar == '\'') {
355
                /*
356
                if we're not currently inside a quoted area but encounter
357
                a quote, switch modes.
358
                 */
359
                state.isInsideAttributeValueQuoted = true;
360
                state.quoteType = QuoteType.byLiteral(currentChar);
361 2 1. handlePotentialAttributeValue : negated conditional → KILLED
2. handlePotentialAttributeValue : negated conditional → KILLED
            } else if (!state.stringBuilder.isEmpty() && currentChar == ' ') {
362
                /*
363
                if we're not in a quoted area and encounter a space, then
364
                we're done reading the attribute value and can add the key-value
365
                pair to the map.
366
                 */
367
                state.attributes.put(state.currentAttributeKey, state.stringBuilder.toString());
368
                state.isReadingAttributeKey = false;
369
                state.stringBuilder = new StringBuilder();
370
                state.currentAttributeKey = "";
371
            } else {
372
                // otherwise keep trucking along adding characters
373
                state.stringBuilder.append(currentChar);
374
            }
375
        }
376
    }
377
378
    private static void handleNotFullyReadAttributeKey(State state, char currentChar) {
379 1 1. handleNotFullyReadAttributeKey : negated conditional → KILLED
        if (state.isHalfClosedTag) {
380
            /*
381
            This situation occurs when we are in a void tag, like <link />,
382
            and are closing the tag with a forward slash + closing bracket.
383
384
            if we got here, it means the previous char was
385
            a forward slash, so the current character *should*
386
            be a closing angle, but if it's not ...
387
             */
388
            throw new ParsingException(String.format("in closing a void tag (e.g. <link />), character after forward slash must be angle bracket.  Char: %s at line %d and at the %d character. %d chars read in total.", currentChar, state.lineRow, state.lineColumn, state.charsRead));
389 2 1. handleNotFullyReadAttributeKey : negated conditional → KILLED
2. handleNotFullyReadAttributeKey : negated conditional → KILLED
        } else if (currentChar == ' ' || currentChar == '=') {
390
            // if we hit whitespace or an equals sign, we're done reading the key
391
            state.currentAttributeKey = state.stringBuilder.toString();
392
            state.isReadingAttributeKey = false;
393
            state.stringBuilder = new StringBuilder();
394 1 1. handleNotFullyReadAttributeKey : negated conditional → KILLED
        } else if (currentChar == '/') {
395
            // a forward-slash cannot be in the attribute key
396
            state.isReadingAttributeKey = false;
397
            state.isHalfClosedTag = true;
398
        } else {
399
            // otherwise keep on reading
400
            state.stringBuilder.append(currentChar);
401
            // and note we are reading the key
402
            state.isReadingAttributeKey = true;
403
        }
404
    }
405
406
    private static void handleReadingTagName(State state, char currentChar) {
407 1 1. handleReadingTagName : negated conditional → KILLED
        if (Character.isWhitespace(currentChar)) {
408
            /*
409
            At this point, we've been reading the tag name, and we've encountered whitespace.
410
            That means we are done reading the tag name
411
             */
412
            state.hasEncounteredTagName = true;
413
            state.isReadingTagName = false;
414
            state.tagName = state.stringBuilder.toString();
415
            state.attributes = new HashMap<>();
416
            state.stringBuilder = new StringBuilder();
417
        } else {
418
            /*
419
            Reading the characters of the tag name
420
             */
421
            state.hasEncounteredTagName = true;
422
            state.tagName = "";
423
            state.stringBuilder.append(currentChar);
424
        }
425
    }
426
427
    /**
428
     * We're just past a starting angle bracket, so we're
429
     * feeling our way around what this element is.
430
     */
431
    private static void handleBeforeReadingTagName(State state, char currentChar) {
432 1 1. handleBeforeReadingTagName : negated conditional → KILLED
        if (currentChar == ' ') {
433
            /*
434
            At this point, we're inside the tag, and we've encountered whitespace.
435
            Seeking the tag name (although we may be inside a closing tag).
436
             */
437
            state.stringBuilder = new StringBuilder();
438 1 1. handleBeforeReadingTagName : negated conditional → KILLED
        } else if (currentChar == '/') {
439
            /*
440
            hitting a forward-slash symbol means we're looking
441
            at the closure of a tag
442
            */
443
            state.isStartTag = false;
444
            state.stringBuilder = new StringBuilder();
445 1 1. handleBeforeReadingTagName : negated conditional → KILLED
        } else if (Character.isAlphabetic(currentChar)) {
446
447
            /*
448
            Here, our input could definitely be the letters of a tag name
449
             */
450
            state.hasEncounteredTagName = true;
451
            state.isReadingTagName = true;
452
            state.stringBuilder.append(currentChar);
453
        }
454
    }
455
456
    /**
457
     * This examines the results of reading a tag - if it's
458
     * a start tag, it pushes it onto a stack for later
459
     * comparison to the end tag.  The stack is a key
460
     * component of how we are able to nest the tags properly.
461
     */
462
    private void processTag(State state, List<HtmlParseNode> nodes) {
463
        String tagNameString = state.tagName;
464
        TagName tagName;
465
466
        tagName = TagName.findMatchingTagname(tagNameString);
467 1 1. processTag : negated conditional → KILLED
        if (tagName.equals(TagName.UNRECOGNIZED)) return;
468
        var tagInfo = new TagInfo(tagName, state.attributes);
469 1 1. processTag : negated conditional → KILLED
        if (state.isStartTag) {
470
            HtmlParseNode newNode = new HtmlParseNode(ParseNodeType.ELEMENT, tagInfo, new ArrayList<>(), "");
471
472 1 1. processTag : negated conditional → KILLED
            if (! state.parseStack.isEmpty()) {
473
                // if we're inside an html element,
474
                // add this to the inner content
475 1 1. processTag : removed call to com/renomad/minum/htmlparsing/HtmlParseNode::addToInnerContent → KILLED
                state.parseStack.peek().addToInnerContent(newNode);
476
            }
477
478 2 1. processTag : negated conditional → KILLED
2. processTag : negated conditional → KILLED
            if (state.parseStack.isEmpty() && tagName.isVoidElement) {
479
                // if we're at the root level and encountering a void element,
480
                // add it to the root-level list of nodes
481
                nodes.add(newNode);
482 1 1. processTag : negated conditional → KILLED
            } else if (!tagName.isVoidElement) {
483 1 1. processTag : removed call to java/util/Deque::push → KILLED
                state.parseStack.push(newNode);
484
            }
485
486 1 1. processTag : negated conditional → KILLED
            if (tagName.equals(TagName.SCRIPT)) {
487
                state.isInsideScript = true;
488
                state.stringBuilder = new StringBuilder();
489
            }
490
        } else {
491
            // if we're leaving an end-tag, it means we have a
492
            // full element with potentially inner content
493
            HtmlParseNode htmlParseNode;
494
            try {
495
                htmlParseNode = state.parseStack.pop();
496
            } catch (NoSuchElementException ex) {
497
                throw new ParsingException("No starting tag found. At line " + state.lineRow + " and at the " + state.lineColumn + "th character. " + state.charsRead + " characters read in total.");
498
            }
499
500
             /*
501
            If the stack is a size of zero at this point, it means we're at the
502
            roots of our HTML code, which means it's the proper time to add the
503
            topmost element we just popped into a list.
504
             */
505 1 1. processTag : negated conditional → KILLED
            if (state.parseStack.isEmpty()) {
506
                nodes.add(htmlParseNode);
507
            }
508
            TagName expectedTagName = htmlParseNode.getTagInfo().getTagName();
509 1 1. processTag : negated conditional → KILLED
            if (expectedTagName != tagName) {
510
                throw new ParsingException("Did not find expected closing-tag type. " + "Expected: " + expectedTagName + " at line " + state.lineRow + " and at the " + state.lineColumn + "th character. " + state.charsRead + " characters read in total.");
511
            }
512
        }
513
    }
514
515
    enum QuoteType {
516
        SINGLE_QUOTED('\''), DOUBLE_QUOTED('"'), NONE(Character.MIN_VALUE);
517
518
        public final char literal;
519
520
        QuoteType(char literal) {
521
            this.literal = literal;
522
        }
523
524
        public static QuoteType byLiteral(char currentChar) {
525 1 1. byLiteral : negated conditional → KILLED
            if (currentChar == '\'') {
526 1 1. byLiteral : replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$QuoteType::byLiteral → KILLED
                return QuoteType.SINGLE_QUOTED;
527
            } else  {
528 1 1. byLiteral : replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$QuoteType::byLiteral → KILLED
                return QuoteType.DOUBLE_QUOTED;
529
            }
530
        }
531
    }
532
533
    static class State {
534
535
        static State buildNewState() {
536
            RingBuffer<Character> previousCharacters = new RingBuffer<>(12, Character.class);
537
            int lineColumn1 = 0;
538
            int lineRow1 = 1;
539
            boolean isHalfClosedTag1 = false;
540
            boolean isInsideAttributeValueQuoted1 = false;
541
            boolean isStartTag1 = true;
542
            boolean isReadingTagName1 = false;
543
            boolean hasEncounteredTagName1 = false;
544
            ArrayDeque<HtmlParseNode> parseStack1 = new ArrayDeque<>();
545
            StringBuilder stringBuilder1 = new StringBuilder();
546
            boolean isInsideTag1 = false;
547
            int charsRead1 = 0;
548
            String tagName1 = "";
549
            String currentAttributeKey1 = "";
550
            HashMap<String, String> attributes1 = new HashMap<>();
551
            boolean isReadingAttributeKey1 = false;
552
            boolean isInsideComment1 = false;
553
            boolean isInsideScript1 = false;
554 1 1. buildNewState : replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$State::buildNewState → KILLED
            return new State(charsRead1, isInsideTag1, stringBuilder1, parseStack1, hasEncounteredTagName1,
555
                    isReadingTagName1, isStartTag1, isInsideAttributeValueQuoted1,
556
                    tagName1, currentAttributeKey1, attributes1, QuoteType.NONE, isReadingAttributeKey1,
557
                    isHalfClosedTag1, lineRow1, lineColumn1, previousCharacters, isInsideComment1, isInsideScript1);
558
        }
559
560
        /**
561
         * If we encounter a forward-slash in a tag, and we're
562
         * not in the midst of reading an attribute value, then
563
         * we expect the next character to be a greater-than symbol.
564
         */
565
        boolean isHalfClosedTag;
566
        /**
567
         * total number of chars read of this HTML file
568
         */
569
        int charsRead;
570
        /**
571
         * True if we are inside angle brackets (may be a closing tag)
572
         */
573
        boolean isInsideTag;
574
        /**
575
         * Where we build up tokens a character at a time
576
         */
577
        StringBuilder stringBuilder;
578
        /**
579
         * A stack of HtmlParseNodes, used to see how far deep in the tree we are
580
         */
581
        final Deque<HtmlParseNode> parseStack;
582
        /**
583
         * True if we have successfully encountered the first letter of the tag
584
         */
585
        boolean hasEncounteredTagName;
586
        /**
587
         * True if we are in the process of reading the tag (e.g. p, a, h1, etc)
588
         */
589
        boolean isReadingTagName;
590
591
        /**
592
         * if we determine we are in the midst of reading an attribute key
593
         */
594
        boolean isReadingAttributeKey;
595
596
        /**
597
         * True if we determine we are probably in the start tag (rather than the closing tag)
598
         */
599
        boolean isStartTag;
600
        /**
601
         * True if we're inside the quoted area inside an attribute value in an element
602
         * tag - this could be where we encounter some symbols that may not be allowed elsewhere.
603
         */
604
        boolean isInsideAttributeValueQuoted;
605
        /**
606
         * If we're in a quoted area, it's either single or double-quoted.
607
         * These quotes need to be paired properly, so we need to keep track.
608
         */
609
        QuoteType quoteType;
610
        /**
611
         * The string value of the tag name
612
         */
613
        String tagName;
614
        /**
615
         * The attribute key we just read
616
         */
617
        String currentAttributeKey;
618
        /**
619
         * a map of string to values (in some cases there won't be an equals
620
         * sign, meaning the value is null.  In other cases there will be an
621
         * equals sign but no value, meaning the value is empty string)
622
         */
623
        Map<String, String> attributes;
624
625
        /**
626
         * indicate which line we're on in debugging
627
         */
628
        int lineRow;
629
630
        /**
631
         * How far we are from the last newline character, including
632
         * all whitespace as well.
633
         */
634
        int lineColumn;
635
636
        /**
637
         * This is used to check for comments and script tags, like:
638
         *     {@code <!-- -->} and {@code <script>}
639
         */
640
        final RingBuffer<Character> previousCharacters;
641
642
        /**
643
         * Indicates whether we are inside a comment
644
         */
645
        boolean isInsideComment;
646
647
        boolean isInsideScript;
648
649
        /**
650
         * Holds the state so we can remember where we are as we examine the HTML
651
         * a character at a time.
652
         */
653
        public State(int charsRead, boolean isInsideTag, StringBuilder stringBuilder,
654
                     Deque<HtmlParseNode> parseStack, boolean hasEncounteredTagName, boolean isReadingTagName,
655
                     boolean isStartTag, boolean isInsideAttributeValueQuoted, String tagName,
656
                     String currentAttributeKey, Map<String, String> attributes, QuoteType quoteType,
657
                     boolean isReadingAttributeKey, boolean isHalfClosedTag, int lineRow, int lineColumn,
658
                     RingBuffer<Character> previousCharacters, boolean isInsideComment, boolean isInsideScript) {
659
660
            this.charsRead = charsRead;
661
            this.isInsideTag = isInsideTag;
662
            this.stringBuilder = stringBuilder;
663
            this.parseStack = parseStack;
664
            this.hasEncounteredTagName = hasEncounteredTagName;
665
            this.isReadingTagName = isReadingTagName;
666
            this.isStartTag = isStartTag;
667
            this.isInsideAttributeValueQuoted = isInsideAttributeValueQuoted;
668
            this.tagName = tagName;
669
            this.currentAttributeKey = currentAttributeKey;
670
            this.attributes = attributes;
671
            this.quoteType = quoteType;
672
            this.isReadingAttributeKey = isReadingAttributeKey;
673
            this.isHalfClosedTag = isHalfClosedTag;
674
            this.lineRow = lineRow;
675
            this.lineColumn = lineColumn;
676
            this.previousCharacters = previousCharacters;
677
            this.isInsideComment = isInsideComment;
678
            this.isInsideScript = isInsideScript;
679
        }
680
    }
681
682
    /**
683
     * Search the node tree for matching elements.
684
     * <p>
685
     * If zero nodes are found, returns an empty list.
686
     * </p>
687
     */
688
    public List<HtmlParseNode> search(List<HtmlParseNode> nodes, TagName tagName, Map<String, String> attributes) {
689
        List<HtmlParseNode> foundNodes = new ArrayList<>();
690
        for (var node : nodes) {
691
            var result = node.search(tagName, attributes);
692
            foundNodes.addAll(result);
693
        }
694 1 1. search : replaced return value with Collections.emptyList for com/renomad/minum/htmlparsing/HtmlParser::search → KILLED
        return foundNodes;
695
    }
696
697
}

Mutations

61

1.1
Location : parse
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
changed conditional boundary → KILLED

2.2
Location : parse
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

71

1.1
Location : parse
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : parse
Killed by : none
replaced return value with Collections.emptyList for com/renomad/minum/htmlparsing/HtmlParser::parse → TIMED_OUT

74

1.1
Location : parse
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::processState → KILLED

91

1.1
Location : processState
Killed by : none
removed call to com/renomad/minum/htmlparsing/HtmlParser::recordLocation → TIMED_OUT

94

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/utils/RingBuffer::add → KILLED

95

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::determineCommentState → KILLED

96

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::determineScriptState → KILLED

97

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

100

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

105

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

106

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::processLessThan → KILLED

107

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

108

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::processGreaterThan → KILLED

110

1.1
Location : processState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::addingToken → KILLED

119

1.1
Location : recordLocation
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
Replaced integer addition with subtraction → KILLED

120

1.1
Location : recordLocation
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

121

1.1
Location : recordLocation
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
Replaced integer addition with subtraction → KILLED

124

1.1
Location : recordLocation
Killed by : none
Replaced integer addition with subtraction → TIMED_OUT

129

1.1
Location : processGreaterThan
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

130

1.1
Location : processGreaterThan
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleExitingTag → KILLED

145

1.1
Location : handleExitingTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

152

1.1
Location : handleExitingTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleTagComponents → KILLED

157

1.1
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

159

1.1
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

3.3
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

163

1.1
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

165

1.1
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

175

1.1
Location : handleTagComponents
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::processTagAndResetState → KILLED

179

1.1
Location : hasFinishedBuildingTagname
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : hasFinishedBuildingTagname
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

3.3
Location : hasFinishedBuildingTagname
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::hasFinishedBuildingTagname → KILLED

4.4
Location : hasFinishedBuildingTagname
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

184

1.1
Location : processLessThan
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

191

1.1
Location : processLessThan
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::enteringTag → KILLED

199

1.1
Location : enteringTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::addText → KILLED

213

1.1
Location : addText
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

218

1.1
Location : addText
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : addText
Killed by : none
negated conditional → TIMED_OUT

219

1.1
Location : addText
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParseNode::addToInnerContent → KILLED

229

1.1
Location : processTagAndResetState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::processTag → KILLED

245

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

247

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

248

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleBeforeReadingTagName → KILLED

249

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

250

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleReadingTagName → KILLED

251

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

252

1.1
Location : addingToken
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleAfterReadingTagName → KILLED

259

1.1
Location : isFinishedReadingTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : isFinishedReadingTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::isFinishedReadingTag → KILLED

3.3
Location : isFinishedReadingTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

274

1.1
Location : determineCommentState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : determineCommentState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

3.3
Location : determineCommentState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

277

1.1
Location : determineCommentState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : determineCommentState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

291

1.1
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

292

1.1
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

293

1.1
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

298

1.1
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
Replaced integer subtraction with addition → KILLED

299

1.1
Location : determineScriptState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::addText → KILLED

310

1.1
Location : handleAfterReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

312

1.1
Location : handleAfterReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

318

1.1
Location : handleAfterReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handleNotFullyReadAttributeKey → KILLED

322

1.1
Location : handleAfterReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParser::handlePotentialAttributeValue → KILLED

332

1.1
Location : isHandlingAttributes
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced boolean return with true for com/renomad/minum/htmlparsing/HtmlParser::isHandlingAttributes → KILLED

2.2
Location : isHandlingAttributes
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

333

1.1
Location : isHandlingAttributes
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : isHandlingAttributes
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

338

1.1
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

341

1.1
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

354

1.1
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

361

1.1
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : handlePotentialAttributeValue
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

379

1.1
Location : handleNotFullyReadAttributeKey
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

389

1.1
Location : handleNotFullyReadAttributeKey
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : handleNotFullyReadAttributeKey
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

394

1.1
Location : handleNotFullyReadAttributeKey
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

407

1.1
Location : handleReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

432

1.1
Location : handleBeforeReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

438

1.1
Location : handleBeforeReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

445

1.1
Location : handleBeforeReadingTagName
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

467

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

469

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

472

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

475

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to com/renomad/minum/htmlparsing/HtmlParseNode::addToInnerContent → KILLED

478

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

2.2
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

482

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

483

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
removed call to java/util/Deque::push → KILLED

486

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

505

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

509

1.1
Location : processTag
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

525

1.1
Location : byLiteral
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
negated conditional → KILLED

526

1.1
Location : byLiteral
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$QuoteType::byLiteral → KILLED

528

1.1
Location : byLiteral
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$QuoteType::byLiteral → KILLED

554

1.1
Location : buildNewState
Killed by : com.renomad.minum.htmlparsing.HtmlParserTests
replaced return value with null for com/renomad/minum/htmlparsing/HtmlParser$State::buildNewState → KILLED

694

1.1
Location : search
Killed by : com.renomad.minum.FunctionalTests
replaced return value with Collections.emptyList for com/renomad/minum/htmlparsing/HtmlParser::search → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0