Skip to content

Commit

Permalink
Fix #3188: Added caption-with-value in ImageTagHandler (#5593)
Browse files Browse the repository at this point in the history
<!-- READ ME FIRST: Please fill in the explanation section below and
check off every point from the Essential Checklist! -->
## Explanation
Fix #3188 

This PR enhances the `ImageTagHandler` to support captions in the
`oppia-noninteractive-image` custom tag.

- The caption-with-value attribute is now parsed.
- The caption text is included as readable text following the image.

## Before

![IMG-20241214-WA0001](https://github.com/user-attachments/assets/746dd49c-f54f-47a8-aebb-dec821e057be)

![IMG-20241214-WA0003](https://github.com/user-attachments/assets/20a73d23-6987-4cab-811b-32313eff3076)

## After

![IMG-20241214-WA0005](https://github.com/user-attachments/assets/044d93ec-75c8-44a7-ad11-6bea16a39651)
![WhatsApp Image 2024-12-17 at 19 25
11_ae9c0cb6](https://github.com/user-attachments/assets/f45c01c3-23d9-4781-b26d-c6076e96828e)

![IMG-20241214-WA0006](https://github.com/user-attachments/assets/6f0d3204-5119-4d45-bfb6-3831529b1ee7)
![WhatsApp Image 2024-12-17 at 19 25
11_1c80032d](https://github.com/user-attachments/assets/a53b8143-6a24-4363-9c84-3e5702911fb2)




## Essential Checklist
<!-- Please tick the relevant boxes by putting an "x" in them. -->
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).
  • Loading branch information
manas-yu authored Dec 17, 2024
1 parent ea9225d commit 1273c31
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 1 deletion.
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=\"&amp;quot;&amp;quot;\" " +
"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

0 comments on commit 1273c31

Please sign in to comment.