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

Mutations

73

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.HtmlParseNodeTests
negated conditional → KILLED

80

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

82

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

98

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

101

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

102

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

103

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

104

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

107

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

112

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

113

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

114

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

115

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

117

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

126

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

127

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

128

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

131

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

136

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

137

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

152

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

159

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

164

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

166

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

170

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

172

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

182

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

186

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

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

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

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

191

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

198

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

206

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

220

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

225

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

2.2
Location : addText
Killed by : com.renomad.minum.htmlparsing.HtmlParseNodeTests
negated conditional → KILLED

226

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

236

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

252

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

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

254

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

255

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

256

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

257

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

258

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

259

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

266

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

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

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

281

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.HtmlParseNodeTests
negated conditional → KILLED

284

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

298

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

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

299

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

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

300

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

305

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

306

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

317

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

319

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

325

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

329

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

339

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

340

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

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

345

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

348

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

361

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

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

368

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

386

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

396

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

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

401

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

414

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

439

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

445

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

452

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

474

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

476

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

479

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

482

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

485

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

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

489

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

490

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

493

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

512

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

516

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

532

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

533

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

535

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

561

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

701

1.1
Location : search
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(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