Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #3188: Added caption-with-value in ImageTagHandler #5593

Merged
merged 16 commits into from
Dec 17, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package org.oppia.android.util.parser.html

import android.graphics.Typeface
import android.text.Editable
import android.text.Layout
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.AlignmentSpan
import android.text.style.ImageSpan
import android.text.style.StyleSpan
import org.oppia.android.util.logging.ConsoleLogger
import org.xml.sax.Attributes

/** The custom tag corresponding to [ImageTagHandler]. */
const val CUSTOM_IMG_TAG = "oppia-noninteractive-image"
private const val CUSTOM_IMG_FILE_PATH_ATTRIBUTE = "filepath-with-value"
private const val CUSTOM_IMG_ALT_TEXT_ATTRIBUTE = "alt-with-value"
private const val CUSTOM_IMG_CAPTION_ATTRIBUTE = "caption-with-value"

/**
* A custom tag handler for supporting custom Oppia images parsed with [CustomHtmlContentHandler].
Expand All @@ -27,6 +32,8 @@ class ImageTagHandler(
) {
val source = attributes.getJsonStringValue(CUSTOM_IMG_FILE_PATH_ATTRIBUTE)
val contentDescription = attributes.getJsonStringValue(CUSTOM_IMG_ALT_TEXT_ATTRIBUTE)
val caption = attributes.getJsonStringValue(CUSTOM_IMG_CAPTION_ATTRIBUTE)

if (source != null) {
val (startIndex, endIndex) = output.run {
// Use a control character to ensure that there's at least 1 character on which to "attach"
Expand Down Expand Up @@ -57,6 +64,41 @@ class ImageTagHandler(
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
output.replace(openIndex, output.length, spannableBuilder)
} else consoleLogger.w("ImageTagHandler", "Failed to parse $CUSTOM_IMG_ALT_TEXT_ATTRIBUTE")
} else consoleLogger.w(
"ImageTagHandler",
"Failed to parse $CUSTOM_IMG_ALT_TEXT_ATTRIBUTE"
)
if (!caption.isNullOrBlank()) {
output.append("\n")
val captionStart = output.length
output.append(caption)
val captionEnd = output.length
output.setSpan(
StyleSpan(Typeface.ITALIC),
captionStart,
captionEnd,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
output.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
captionStart,
captionEnd,
Spannable.SPAN_PARAGRAPH
)

// Insert a newline after the caption and reset alignment.
output.append("\n")
val resetStart = output.length
output.append(" ")
output.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
resetStart,
output.length,
Spannable.SPAN_PARAGRAPH
)
} else consoleLogger.w(
"ImageTagHandler",
"Failed to parse $CUSTOM_IMG_CAPTION_ATTRIBUTE"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package org.oppia.android.util.parser.html

import android.app.Application
import android.content.Context
import android.graphics.Typeface
import android.text.Html
import android.text.Layout
import android.text.Spannable
import android.text.style.AlignmentSpan
import android.text.style.ImageSpan
import android.text.style.StyleSpan
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
Expand Down Expand Up @@ -70,6 +74,11 @@ private const val IMAGE_TAG_WITH_SPACE_ONLY_ALT_VALUE_MARKUP =
"caption-with-value=\"""\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

private const val IMAGE_TAG_WITH_CAPTION_MARKUP =
"<oppia-noninteractive-image alt-with-value=\"&amp;quot;alt text 3&amp;quot;\" " +
"caption-with-value=\"&amp;quot;Sample Caption&amp;quot;\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

/** Tests for [ImageTagHandler]. */
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
Expand Down Expand Up @@ -99,6 +108,53 @@ class ImageTagHandlerTest {

// TODO(#3085): Introduce test for verifying that the error log scenario is logged correctly.

@Test
fun testParseHtml_withImageCardMarkup_withCaption_addsCaptionWithStyleAndAlignment() {
val parsedHtml = CustomHtmlContentHandler.fromHtml(
html = IMAGE_TAG_WITH_CAPTION_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)

val parsedHtmlString = parsedHtml.toString()
assertThat(parsedHtmlString).contains("Sample Caption")

val styleSpans = parsedHtml.getSpans(
/* start = */ 0,
/* end = */ parsedHtml.length,
StyleSpan::class.java
)
assertThat(styleSpans).hasLength(1)
assertThat(styleSpans[0].style).isEqualTo(Typeface.ITALIC)

val alignmentSpans = parsedHtml.getSpans(
/* start = */ 0,
/* end = */ parsedHtml.length,
AlignmentSpan.Standard::class.java
)
assertThat(alignmentSpans).hasLength(2)

// Check the first AlignmentSpan for center alignment (caption)
assertThat(alignmentSpans[0].alignment).isEqualTo(Layout.Alignment.ALIGN_CENTER)

// Check the second AlignmentSpan for normal alignment (reset)
assertThat(alignmentSpans[1].alignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL)
}

@Test
fun testParseHtml_withMultipleImages_withCaptions_includesAllCaptions() {
val parsedHtml =
CustomHtmlContentHandler.fromHtml(
html = "$IMAGE_TAG_WITH_CAPTION_MARKUP and $IMAGE_TAG_WITH_CAPTION_MARKUP",
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)

val parsedHtmlStr = parsedHtml.toString()
assertThat(parsedHtmlStr).contains("Sample Caption")
assertThat(parsedHtmlStr).contains("Sample Caption")
}

@Test
fun testParseHtml_emptyString_doesNotIncludeImageSpan() {
val parsedHtml =
Expand Down
Loading