Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ import org.smartregister.fhircore.engine.util.extension.valueToString
/**
* HtmlPopulator class is responsible for processing an HTML template by replacing custom tags with
* data from QuestionnaireResponses. The class uses various regex patterns to find and replace
* custom tags such as @is-not-empty, @answer-as-list, @answer, @submitted-date, @contains,
* and @is-questionnaire-submitted.
* custom tags such
* as @is-not-empty, @answer-as-list, @answer, @submitted-date, @contains, @is-questionnaire-submitted,
* and @translate.
*
* @property questionnaireResponses The QuestionnaireResponses object containing data for
* replacement.
* @property translationProvider A high-order function that takes a translation key and returns the
* translated string. Defaults to returning an empty string.
*/
class HtmlPopulator(
questionnaireResponses: List<QuestionnaireResponse>,
private val translationProvider: (String) -> String = { "" },
) {
private var answerMap: Map<String, List<QuestionnaireResponseItemAnswerComponent>>
private var submittedDateMap: Map<String, Date>
Expand Down Expand Up @@ -104,6 +108,10 @@ class HtmlPopulator(
val matcher = isQuestionnaireSubmittedPattern.matcher(html.substring(i))
if (matcher.find()) processIsQuestionnaireSubmitted(i, html, matcher) else i++
}
html.startsWith("@translate", i) -> {
val matcher = translatePattern.matcher(html.substring(i))
if (matcher.find()) processTranslate(i, html, matcher) else i++
}
else -> i++
}
}
Expand Down Expand Up @@ -246,6 +254,20 @@ class HtmlPopulator(
}
}

/**
* Processes the @translate tag by calling the translation provider lambda to resolve the
* translation key.
*
* @param i The starting index of the tag in the HTML.
* @param html The StringBuilder containing the HTML.
* @param matcher The Matcher object for the regex pattern.
*/
private fun processTranslate(i: Int, html: StringBuilder, matcher: Matcher) {
val translationKey = matcher.group(1)
val translatedValue = translationProvider(translationKey)
html.replace(i, matcher.end() + i, translatedValue)
}

companion object {
// Compile regex patterns for different tags
private val isNotEmptyPattern =
Expand All @@ -260,5 +282,6 @@ class HtmlPopulator(
Pattern.compile(
"@is-questionnaire-submitted\\('([^']+)'\\)((?s).*?)@is-questionnaire-submitted\\('\\1'\\)",
)
private val translatePattern = Pattern.compile("@translate\\('([^']+)'\\)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -665,4 +665,57 @@ class HtmlPopulatorTest {
val populatedHtml = htmlPopulator.populateHtml(html)
Assert.assertEquals("", populatedHtml)
}

@Test
fun testTranslateShouldRemoveTagWhenUsingDefaultProvider() {
val html = "<p>@translate('hello.world')</p>"
val questionnaireResponses = listOf<QuestionnaireResponse>()
// HtmlPopulator with default translation provider (returns empty string)
val htmlPopulator = HtmlPopulator(questionnaireResponses)
val populatedHtml = htmlPopulator.populateHtml(html)
// With default provider, the tag should be removed (empty string)
Assert.assertEquals("<p></p>", populatedHtml)
}

@Test
fun testTranslateShouldTranslateUsingProvidedMap() {
val html = "<p>@translate('hello.world')</p>"
val questionnaireResponses = listOf<QuestionnaireResponse>()
val translationMap = mapOf("hello.world" to "Hello World")
val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" }
val populatedHtml = htmlPopulator.populateHtml(html)
Assert.assertEquals("<p>Hello World</p>", populatedHtml)
}

@Test
fun testTranslateShouldHandleMultipleTranslateTagsWithMap() {
val html = "<p>@translate('key1') and @translate('key2')</p>"
val questionnaireResponses = listOf<QuestionnaireResponse>()
val translationMap =
mapOf(
"key1" to "Value 1",
"key2" to "Value 2",
)
val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" }
val populatedHtml = htmlPopulator.populateHtml(html)
Assert.assertEquals("<p>Value 1 and Value 2</p>", populatedHtml)
}

@Test
fun testTranslateShouldHandleTranslateTagInComplexContent() {
val html =
"<div>Welcome: @translate('welcome.message')</div><p>Thank you: @translate('thank.you.message')</p>"
val questionnaireResponses = listOf<QuestionnaireResponse>()
val translationMap =
mapOf(
"welcome.message" to "Welcome to our service",
"thank.you.message" to "Thank you for using our app",
)
val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" }
val populatedHtml = htmlPopulator.populateHtml(html)
Assert.assertEquals(
"<div>Welcome: Welcome to our service</div><p>Thank you: Thank you for using our app</p>",
populatedHtml,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ class PdfLauncherFragment : DialogFragment() {
}

val htmlContent = htmlBinary.content.decodeToString()
val populatedHtml = HtmlPopulator(questionnaireResponses).populateHtml(htmlContent)
val populatedHtml =
HtmlPopulator(questionnaireResponses, pdfLauncherViewModel.getTranslationProvider())
.populateHtml(htmlContent)

withContext(Dispatchers.Main) {
pdfGenerator.generatePdfWithHtml(populatedHtml, htmlTitle) { dismiss() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ package org.smartregister.fhircore.quest.ui.pdf
import androidx.lifecycle.ViewModel
import com.google.android.fhir.search.Search
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.Locale
import javax.inject.Inject
import org.hl7.fhir.r4.model.Binary
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.util.helper.LocalizationHelper

/**
* ViewModel for managing PDF generation related operations.
Expand All @@ -32,12 +35,14 @@ import org.smartregister.fhircore.engine.data.local.DefaultRepository
* required for generating PDFs.
*
* @param defaultRepository The repository for accessing local data.
* @param configurationRegistry The registry for configuration and localization support.
*/
@HiltViewModel
class PdfLauncherViewModel
@Inject
constructor(
val defaultRepository: DefaultRepository,
val configurationRegistry: ConfigurationRegistry,
) : ViewModel() {

/**
Expand Down Expand Up @@ -86,4 +91,17 @@ constructor(
suspend fun retrieveBinary(binaryId: String): Binary? {
return defaultRepository.loadResource<Binary>(binaryId)
}

/**
* Provides a translation lambda for HtmlPopulator.
*
* @return A function that takes a translation key and returns the translated string.
*/
fun getTranslationProvider(): (String) -> String = { translationKey ->
configurationRegistry.localizationHelper.parseTemplate(
LocalizationHelper.STRINGS_BASE_BUNDLE_NAME,
Locale.getDefault(),
"{{$translationKey}}",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.data.local.ContentCache
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.util.extension.asReference
Expand All @@ -49,26 +50,28 @@ class PdfLauncherViewModelTest : RobolectricTest() {

private lateinit var fhirEngine: FhirEngine
private lateinit var defaultRepository: DefaultRepository
private lateinit var configurationRegistry: ConfigurationRegistry
private lateinit var viewModel: PdfLauncherViewModel

@Before
fun setUp() {
hiltAndroidRule.inject()
fhirEngine = mockk()
configurationRegistry = mockk()
defaultRepository =
DefaultRepository(
fhirEngine = fhirEngine,
dispatcherProvider = mockk(),
sharedPreferencesHelper = mockk(),
configurationRegistry = mockk(),
configurationRegistry = configurationRegistry,
configService = mockk(),
configRulesExecutor = mockk(),
fhirPathDataExtractor = mockk(),
parser = mockk(),
context = mockk(),
contentCache = contentCache,
)
viewModel = PdfLauncherViewModel(defaultRepository)
viewModel = PdfLauncherViewModel(defaultRepository, configurationRegistry)
}

@Test
Expand Down
Loading