Skip to content

Commit

Permalink
support parsing imported pages for a PageCollection
Browse files Browse the repository at this point in the history
  • Loading branch information
frett committed Oct 21, 2024
1 parent 4e74e75 commit c6adc54
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Manifest : BaseModel, Styles, HasPages {
if (config.parsePages) {
launch {
manifest.pages = manifest.pageXmlFiles
.map { (name, src) -> async { Page.parse(manifest, name, parseFile(src)) } }
.map { (name, src) -> async { Page.parse(manifest, name, parseFile(src), parseFile) } }
.awaitAll().filterNotNull()
}
} else {
Expand Down Expand Up @@ -198,7 +198,7 @@ class Manifest : BaseModel, Styles, HasPages {
internal var tips: Map<String, Tip> by setOnce()
private set

private val pageXmlFiles: List<XmlFile>
internal val pageXmlFiles: List<XmlFile>
private val tipXmlFiles: List<XmlFile>

val relatedFiles get() = buildSet {
Expand Down Expand Up @@ -304,7 +304,8 @@ class Manifest : BaseModel, Styles, HasPages {
resources: ((Manifest) -> List<Resource>)? = null,
shareables: ((Manifest) -> List<Shareable>)? = null,
tips: ((Manifest) -> List<Tip>)? = null,
pages: ((Manifest) -> List<Page>)? = null
pages: ((Manifest) -> List<Page>)? = null,
pageXmlFiles: List<XmlFile> = emptyList(),
) {
this.config = config

Expand Down Expand Up @@ -344,7 +345,7 @@ class Manifest : BaseModel, Styles, HasPages {
this.shareables = shareables?.invoke(this).orEmpty()
this.tips = tips?.invoke(this)?.associateBy { it.id }.orEmpty()

pageXmlFiles = emptyList()
this.pageXmlFiles = pageXmlFiles
tipXmlFiles = emptyList()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ abstract class Page : BaseModel, Styles, HasAnalyticsEvents {
@VisibleForTesting
internal val DEFAULT_BACKGROUND_IMAGE_SCALE_TYPE = ImageScaleType.FILL_X

internal suspend fun parse(
container: HasPages,
fileName: String?,
parser: XmlPullParser,
parseFile: suspend (String) -> XmlPullParser,
): Page? {
parser.require(XmlPullParser.START_TAG, null, XML_PAGE)

return when (parser.namespace to parser.getAttributeValue(XMLNS_XSI, XML_TYPE)) {
XMLNS_PAGE to TYPE_PAGE_COLLECTION -> {
if (!container.supportsPageType(PageCollectionPage::class)) return null
PageCollectionPage.parse(container, fileName, parser, parseFile)
}
else -> parse(container, fileName, parser)
}?.takeIf { container.supportsPageType(it::class) }
}

internal fun parse(container: HasPages, fileName: String?, parser: XmlPullParser): Page? {
parser.require(XmlPullParser.START_TAG, null, XML_PAGE)

Expand All @@ -90,7 +107,6 @@ abstract class Page : BaseModel, Styles, HasAnalyticsEvents {
XMLNS_PAGE -> when (val type = parser.getAttributeValue(XMLNS_XSI, XML_TYPE)) {
TYPE_CARD_COLLECTION -> CardCollectionPage(container, fileName, parser)
TYPE_CONTENT -> ContentPage(container, fileName, parser)
TYPE_PAGE_COLLECTION -> PageCollectionPage(container, fileName, parser)
else -> {
val message = "Unrecognized page type: <${parser.namespace}:${parser.name} type=$type>"
Logger.e(message, UnsupportedOperationException(message), "Page")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package org.cru.godtools.shared.tool.parser.model.page

import kotlin.reflect.KClass
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.ccci.gto.support.androidx.annotation.RestrictTo
import org.ccci.gto.support.androidx.annotation.RestrictToScope
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent.Companion.parseAnalyticsEvents
import org.cru.godtools.shared.tool.parser.model.HasPages
import org.cru.godtools.shared.tool.parser.model.Manifest
import org.cru.godtools.shared.tool.parser.model.XMLNS_ANALYTICS
import org.cru.godtools.shared.tool.parser.util.setOnce
import org.cru.godtools.shared.tool.parser.xml.XmlPullParser
import org.cru.godtools.shared.tool.parser.xml.parseChildren

Expand All @@ -16,12 +21,25 @@ class PageCollectionPage : Page, HasPages {
internal const val TYPE_PAGE_COLLECTION = "pagecollection"

private const val XML_PAGES = "pages"
private const val XML_IMPORT = "import"
private const val XML_IMPORT_FILENAME = "filename"

internal suspend fun parse(
container: HasPages,
fileName: String?,
parser: XmlPullParser,
parseFile: suspend (String) -> XmlPullParser
) = PageCollectionPage(container, fileName, parser).apply {
buildPagesFromParsedPages(parseFile)
}
}

override val analyticsEvents: List<AnalyticsEvent>
override val pages: List<Page>
private val parsedPages: List<PageOrImport>
override var pages: List<Page> by setOnce()
private set

internal constructor(
private constructor(
container: HasPages,
fileName: String?,
parser: XmlPullParser
Expand All @@ -30,15 +48,15 @@ class PageCollectionPage : Page, HasPages {
parser.requirePageType(TYPE_PAGE_COLLECTION)

analyticsEvents = mutableListOf()
pages = mutableListOf()
parsedPages = mutableListOf()
parser.parseChildren {
when (parser.namespace) {
XMLNS_ANALYTICS -> when (parser.name) {
AnalyticsEvent.XML_EVENTS -> analyticsEvents += parser.parseAnalyticsEvents(this)
}

XMLNS_PAGE -> when (parser.name) {
XML_PAGES -> pages += parser.parsePages()
XML_PAGES -> parsedPages += parser.parsePages()
}
}
}
Expand All @@ -53,19 +71,39 @@ class PageCollectionPage : Page, HasPages {
XMLNS_PAGE -> when (name) {
XML_PAGE -> {
parse(this@PageCollectionPage, null, this@parsePages)
?.takeIf { supportsPageType(it::class) }
?.let { add(it) }
?.let { add(PageOrImport(it)) }
}
XML_IMPORT -> add(PageOrImport(ref = getAttributeValue(XML_IMPORT_FILENAME)))
}
}
}
}

private suspend fun buildPagesFromParsedPages(parseFile: suspend (String) -> XmlPullParser) {
val pageIndex by lazy { manifest.pageXmlFiles.associate { it.name to it.src } }

pages = coroutineScope {
parsedPages
.map {
it.page?.let { CompletableDeferred(it) }
?: async {
val pageSrc = it.ref?.let { pageIndex[it] } ?: return@async null
Page.parse(this@PageCollectionPage, it.ref, parseFile(pageSrc), parseFile)
}
}
.awaitAll()
.filterNotNull()
}
}

@RestrictTo(RestrictToScope.TESTS)
internal constructor(manifest: Manifest) : super(manifest) {
analyticsEvents = emptyList()
parsedPages = emptyList()
pages = emptyList()

Check warning on line 103 in module/parser/src/commonMain/kotlin/org/cru/godtools/shared/tool/parser/model/page/PageCollectionPage.kt

View check run for this annotation

Codecov / codecov/patch

module/parser/src/commonMain/kotlin/org/cru/godtools/shared/tool/parser/model/page/PageCollectionPage.kt#L100-L103

Added lines #L100 - L103 were not covered by tests
}

override fun <T : Page> supportsPageType(type: KClass<T>) = type == ContentPage::class

private class PageOrImport(val page: Page? = null, val ref: String? = null)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package org.cru.godtools.shared.tool.parser.internal

import org.cru.godtools.shared.tool.parser.xml.XmlPullParser
import org.cru.godtools.shared.tool.parser.xml.XmlPullParserFactory

abstract class UsesResources(internal val resourcesDir: String? = "model") {
internal val parseFile: suspend (String) -> XmlPullParser = { getTestXmlParser(it) }

internal suspend fun getTestXmlParser(name: String) =
TEST_XML_PULL_PARSER_FACTORY.getXmlParser(name)!!.apply { nextTag() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ class PageCollectionPageTest : UsesResources("model/page") {
private val manifest = Manifest(
config = ParserConfig().withSupportedFeatures(FEATURE_PAGE_COLLECTION),
type = Manifest.Type.CYOA,
pageXmlFiles = listOf(
Manifest.XmlFile("ref_page_valid", "page_content.xml"),
Manifest.XmlFile("ref_page_invalid", "page_pagecollection_imports.xml"),
),
)

// region Parse XML
@Test
fun testParsePageCollectionPage() = runTest {
assertNotNull(PageCollectionPage(manifest, null, getTestXmlParser("page_pagecollection.xml"))) {
assertNotNull(
PageCollectionPage.parse(manifest, null, getTestXmlParser("page_pagecollection.xml"), parseFile)
) {
assertEquals(1, it.analyticsEvents.size)
assertEquals(1, it.pages.size)
assertNotNull(it.pages[0]) { page ->
Expand All @@ -34,10 +40,20 @@ class PageCollectionPageTest : UsesResources("model/page") {
}
}

@Test
fun testParsePageCollectionPage_PageImports() = runTest {
with(PageCollectionPage.parse(manifest, null, getTestXmlParser("page_pagecollection_imports.xml"), parseFile)) {
assertEquals(1, analyticsEvents.size)
assertEquals(2, pages.size)
assertEquals("embedded_content_page", pages[0].id)
assertEquals("content_page", pages[1].id)
}
}

@Test
fun testParsePageCollectionPageInvalidType() = runTest {
assertFailsWith(XmlPullParserException::class) {
PageCollectionPage(manifest, null, getTestXmlParser("page_invalid_type.xml"))
PageCollectionPage.parse(manifest, null, getTestXmlParser("page_invalid_type.xml"), parseFile)
}
}
// endregion Parse XML
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import kotlin.test.assertSame
import kotlinx.coroutines.test.runTest
import org.ccci.gto.support.androidx.test.junit.runners.AndroidJUnit4
import org.ccci.gto.support.androidx.test.junit.runners.RunOnAndroidWith
import org.cru.godtools.shared.tool.parser.ParserConfig
import org.cru.godtools.shared.tool.parser.ParserConfig.Companion.FEATURE_PAGE_COLLECTION
import org.cru.godtools.shared.tool.parser.internal.UsesResources
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent
import org.cru.godtools.shared.tool.parser.model.Manifest
Expand Down Expand Up @@ -43,6 +45,35 @@ class PageTest : UsesResources("model/page") {
assertNull(Page.parse(Manifest(type = Manifest.Type.TRACT), null, getTestXmlParser("../lesson/page.xml")))
}

@Test
fun testParsePageCollectionPage() = runTest {
val config = ParserConfig().withSupportedFeatures(FEATURE_PAGE_COLLECTION)
assertIs<PageCollectionPage>(
Page.parse(
Manifest(config = config, type = Manifest.Type.CYOA),
null,
getTestXmlParser("page_pagecollection.xml"),
parseFile,
),
)
assertNull(
Page.parse(
Manifest(type = Manifest.Type.CYOA),
null,
getTestXmlParser("page_pagecollection.xml"),
parseFile,
),
)
assertNull(
Page.parse(
Manifest(config = config, type = Manifest.Type.LESSON),
null,
getTestXmlParser("page_pagecollection.xml"),
parseFile,
),
)
}

@Test
fun testParseTractPage() = runTest {
assertIs<TractPage>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<page xmlns="https://mobile-content-api.cru.org/xmlns/page"
xmlns:analytics="https://mobile-content-api.cru.org/xmlns/analytics"
xmlns:content="https://mobile-content-api.cru.org/xmlns/content"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="content">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="content" id="content_page">
<analytics:events>
<analytics:event action="test" system="firebase" />
</analytics:events>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<page xmlns="https://mobile-content-api.cru.org/xmlns/page"
xmlns:analytics="https://mobile-content-api.cru.org/xmlns/analytics"
xmlns:content="https://mobile-content-api.cru.org/xmlns/content"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pagecollection">
<analytics:events>
<analytics:event action="test" system="firebase" />
</analytics:events>

<pages>
<page id="embedded_content_page" xsi:type="content">
<analytics:events>
<analytics:event action="test2" system="firebase" />
</analytics:events>
<content>
<content:spacer />
<content:paragraph>
<content:text>Text</content:text>
</content:paragraph>
</content>
</page>
<import filename="ref_page_valid" />
<import filename="ref_page_invalid" />
<import filename="ref_page_missing" />
<import />
</pages>
</page>

0 comments on commit c6adc54

Please sign in to comment.