WebvttCueParser { static void parseCueText( @Nullable String id, String markup, WebvttCue.Builder builder, List<WebvttCssStyle> styles) { SpannableStringBuilder spannedText = new SpannableStringBuilder(); ArrayDeque<StartTag> startTagStack = new ArrayDeque<>(); List<StyleMatch> scratchStyleMatches = new ArrayList<>(); int pos = 0; while (pos < markup.length()) { char curr = markup.charAt(pos); switch (curr) { case CHAR_LESS_THAN: if (pos + 1 >= markup.length()) { pos++; break; } int ltPos = pos; boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH; pos = findEndOfTag(markup, ltPos + 1); boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH; String fullTagExpression = markup.substring(ltPos + (isClosingTag ? 2 : 1), isVoidTag ? pos - 2 : pos - 1); if (fullTagExpression.trim().isEmpty()) { continue; } String tagName = getTagName(fullTagExpression); if (!isSupportedTag(tagName)) { continue; } if (isClosingTag) { StartTag startTag; do { if (startTagStack.isEmpty()) { break; } startTag = startTagStack.pop(); applySpansForTag(id, startTag, spannedText, styles, scratchStyleMatches); } while(!startTag.name.equals(tagName)); } else if (!isVoidTag) { startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length())); } break; case CHAR_AMPERSAND: int semiColonEndIndex = markup.indexOf(CHAR_SEMI_COLON, pos + 1); int spaceEndIndex = markup.indexOf(CHAR_SPACE, pos + 1); int entityEndIndex = semiColonEndIndex == -1 ? spaceEndIndex : (spaceEndIndex == -1 ? semiColonEndIndex : Math.min(semiColonEndIndex, spaceEndIndex)); if (entityEndIndex != -1) { applyEntity(markup.substring(pos + 1, entityEndIndex), spannedText); if (entityEndIndex == spaceEndIndex) { spannedText.append(" "); } pos = entityEndIndex + 1; } else { spannedText.append(curr); pos++; } break; default: spannedText.append(curr); pos++; break; } } while (!startTagStack.isEmpty()) { applySpansForTag(id, startTagStack.pop(), spannedText, styles, scratchStyleMatches); } applySpansForTag(id, StartTag.buildWholeCueVirtualTag(), spannedText, styles, scratchStyleMatches); builder.setText(spannedText); } WebvttCueParser(); boolean parseCue(
      ParsableByteArray webvttData, WebvttCue.Builder builder, List<WebvttCssStyle> styles); static final Pattern CUE_HEADER_PATTERN; }
@Test public void testParseStrictValidClassesAndTrailingTokens() throws Exception { Spanned text = parseCueText("<v.first.loud Esme>" + "This <u.style1.style2 some stuff>is</u> text with <b.foo><i.bar>html</i></b> tags"); assertThat(text.toString()).isEqualTo("This is text with html tags"); UnderlineSpan[] underlineSpans = getSpans(text, UnderlineSpan.class); StyleSpan[] styleSpans = getSpans(text, StyleSpan.class); assertThat(underlineSpans).hasLength(1); assertThat(styleSpans).hasLength(2); assertThat(styleSpans[0].getStyle()).isEqualTo(ITALIC); assertThat(styleSpans[1].getStyle()).isEqualTo(BOLD); assertThat(text.getSpanStart(underlineSpans[0])).isEqualTo(5); assertThat(text.getSpanEnd(underlineSpans[0])).isEqualTo(7); assertThat(text.getSpanStart(styleSpans[0])).isEqualTo(18); assertThat(text.getSpanStart(styleSpans[1])).isEqualTo(18); assertThat(text.getSpanEnd(styleSpans[0])).isEqualTo(22); assertThat(text.getSpanEnd(styleSpans[1])).isEqualTo(22); }
@Test public void testParseStrictValidUnsupportedTagsStrippedOut() throws Exception { Spanned text = parseCueText("<v.first.loud Esme>This <unsupported>is</unsupported> text with " + "<notsupp><invalid>html</invalid></notsupp> tags"); assertThat(text.toString()).isEqualTo("This is text with html tags"); assertThat(getSpans(text, UnderlineSpan.class)).hasLength(0); assertThat(getSpans(text, StyleSpan.class)).hasLength(0); }
@Test public void testParseWellFormedUnclosedEndAtCueEnd() throws Exception { Spanned text = parseCueText("An <u some trailing stuff>unclosed u tag with " + "<i>italic</i> inside"); assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside"); UnderlineSpan[] underlineSpans = getSpans(text, UnderlineSpan.class); StyleSpan[] styleSpans = getSpans(text, StyleSpan.class); assertThat(underlineSpans).hasLength(1); assertThat(styleSpans).hasLength(1); assertThat(styleSpans[0].getStyle()).isEqualTo(ITALIC); assertThat(text.getSpanStart(underlineSpans[0])).isEqualTo(3); assertThat(text.getSpanStart(styleSpans[0])).isEqualTo(23); assertThat(text.getSpanEnd(styleSpans[0])).isEqualTo(29); assertThat(text.getSpanEnd(underlineSpans[0])).isEqualTo(36); }
@Test public void testParseWellFormedUnclosedEndAtParent() throws Exception { Spanned text = parseCueText("An unclosed u tag with <i><u>underline and italic</i> inside"); assertThat(text.toString()).isEqualTo("An unclosed u tag with underline and italic inside"); UnderlineSpan[] underlineSpans = getSpans(text, UnderlineSpan.class); StyleSpan[] styleSpans = getSpans(text, StyleSpan.class); assertThat(underlineSpans).hasLength(1); assertThat(styleSpans).hasLength(1); assertThat(text.getSpanStart(underlineSpans[0])).isEqualTo(23); assertThat(text.getSpanStart(styleSpans[0])).isEqualTo(23); assertThat(text.getSpanEnd(underlineSpans[0])).isEqualTo(43); assertThat(text.getSpanEnd(styleSpans[0])).isEqualTo(43); assertThat(styleSpans[0].getStyle()).isEqualTo(ITALIC); }
@Test public void testParseMalformedNestedElements() throws Exception { Spanned text = parseCueText("<b><u>An unclosed u tag with <i>italic</u> inside</i></b>"); assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside"); UnderlineSpan[] underlineSpans = getSpans(text, UnderlineSpan.class); StyleSpan[] styleSpans = getSpans(text, StyleSpan.class); assertThat(underlineSpans).hasLength(1); assertThat(styleSpans).hasLength(2); assertThat(text.getSpanStart(underlineSpans[0])).isEqualTo(0); assertThat(text.getSpanEnd(underlineSpans[0])).isEqualTo(29); if (styleSpans[0].getStyle() == Typeface.BOLD) { assertThat(text.getSpanStart(styleSpans[0])).isEqualTo(0); assertThat(text.getSpanStart(styleSpans[1])).isEqualTo(23); assertThat(text.getSpanEnd(styleSpans[1])).isEqualTo(29); assertThat(text.getSpanEnd(styleSpans[0])).isEqualTo(36); } else { assertThat(text.getSpanStart(styleSpans[1])).isEqualTo(0); assertThat(text.getSpanStart(styleSpans[0])).isEqualTo(23); assertThat(text.getSpanEnd(styleSpans[0])).isEqualTo(29); assertThat(text.getSpanEnd(styleSpans[1])).isEqualTo(36); } }
@Test public void testParseCloseNonExistingTag() throws Exception { Spanned text = parseCueText("blah<b>blah</i>blah</b>blah"); assertThat(text.toString()).isEqualTo("blahblahblahblah"); StyleSpan[] spans = getSpans(text, StyleSpan.class); assertThat(spans).hasLength(1); assertThat(spans[0].getStyle()).isEqualTo(BOLD); assertThat(text.getSpanStart(spans[0])).isEqualTo(4); assertThat(text.getSpanEnd(spans[0])).isEqualTo(8); }
@Test public void testParseEmptyTagName() throws Exception { Spanned text = parseCueText("An unclosed u tag with <>italic inside"); assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside"); }
@Test public void testParseEntities() throws Exception { Spanned text = parseCueText("&amp; &gt; &lt; &nbsp;"); assertThat(text.toString()).isEqualTo("& > < "); }
@Test public void testParseEntitiesUnsupported() throws Exception { Spanned text = parseCueText("&noway; &sure;"); assertThat(text.toString()).isEqualTo(" "); }
@Test public void testParseEntitiesNotTerminated() throws Exception { Spanned text = parseCueText("&amp here comes text"); assertThat(text.toString()).isEqualTo("& here comes text"); }
@Test public void testParseEntitiesNotTerminatedUnsupported() throws Exception { Spanned text = parseCueText("&surenot here comes text"); assertThat(text.toString()).isEqualTo(" here comes text"); }
@Test public void testParseEntitiesNotTerminatedNoSpace() throws Exception { Spanned text = parseCueText("&surenot"); assertThat(text.toString()).isEqualTo("&surenot"); }
@Test public void testParseVoidTag() throws Exception { Spanned text = parseCueText("here comes<br/> text<br/>"); assertThat(text.toString()).isEqualTo("here comes text"); }
@Test public void testParseMultipleTagsOfSameKind() { Spanned text = parseCueText("blah <b>blah</b> blah <b>foo</b>"); assertThat(text.toString()).isEqualTo("blah blah blah foo"); StyleSpan[] spans = getSpans(text, StyleSpan.class); assertThat(spans).hasLength(2); assertThat(text.getSpanStart(spans[0])).isEqualTo(5); assertThat(text.getSpanEnd(spans[0])).isEqualTo(9); assertThat(text.getSpanStart(spans[1])).isEqualTo(15); assertThat(text.getSpanEnd(spans[1])).isEqualTo(18); assertThat(spans[0].getStyle()).isEqualTo(BOLD); assertThat(spans[1].getStyle()).isEqualTo(BOLD); }
@Test public void testParseInvalidVoidSlash() { Spanned text = parseCueText("blah <b/.st1.st2 trailing stuff> blah"); assertThat(text.toString()).isEqualTo("blah blah"); StyleSpan[] spans = getSpans(text, StyleSpan.class); assertThat(spans).hasLength(0); }
@Test public void testParseMonkey() throws Exception { Spanned text = parseCueText("< u>An unclosed u tag with <<<<< i>italic</u></u></u></u >" + "</i><u><u> inside"); assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside"); text = parseCueText(">>>>>>>>>An unclosed u tag with <<<<< italic</u></u></u>" + "</u ></i><u><u> inside"); assertThat(text.toString()).isEqualTo(">>>>>>>>>An unclosed u tag with inside"); }
@Test public void testParseCornerCases() throws Exception { Spanned text = parseCueText(">"); assertThat(text.toString()).isEqualTo(">"); text = parseCueText("<"); assertThat(text.toString()).isEmpty(); text = parseCueText("<b.st1.st2 annotation"); assertThat(text.toString()).isEmpty(); text = parseCueText("<<<<<<<<<<<<<<<<"); assertThat(text.toString()).isEmpty(); text = parseCueText("<<<<<<>><<<<<<<<<<"); assertThat(text.toString()).isEqualTo(">"); text = parseCueText("<>"); assertThat(text.toString()).isEmpty(); text = parseCueText("&"); assertThat(text.toString()).isEqualTo("&"); text = parseCueText("&&&&&&&"); assertThat(text.toString()).isEqualTo("&&&&&&&"); }