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

#3 ignore namespaces #5

Merged
merged 3 commits into from
Apr 21, 2024
Merged
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 @@ -57,6 +57,7 @@ class DecoderDerivation(ctx: blackbox.Context) extends Derivation(ctx) {
localName: $javaPkg.String,
namespaceUri: $scalaPkg.Option[$javaPkg.String],
): $decodingPkg.ElementDecoder[$classType] = {
$config.removeNamespaces.foreach(cursor.setRemoveNamespaces)
if (cursor.getEventType == _root_.com.fasterxml.aalto.AsyncXMLStreamReader.EVENT_INCOMPLETE) {
this
} else {
Expand Down Expand Up @@ -291,6 +292,7 @@ class DecoderDerivation(ctx: blackbox.Context) extends Derivation(ctx) {
if (cursor.getScopeDefaultNamespace == namespaceUri) $config.scopeDefaultNamespace
else $config.scopeDefaultNamespace.orElse(namespaceUri)
$config.scopeDefaultNamespace.foreach(cursor.setScopeDefaultNamespace)
$config.removeNamespaces.foreach(cursor.setRemoveNamespaces)
$decodingPkg.ElementDecoder
.errorIfWrongName[$classType](cursor, localName, newNamespaceUri.orElse(cursor.getScopeDefaultNamespace)) match {
case $scalaPkg.Some(error) => error
Expand Down Expand Up @@ -327,6 +329,7 @@ class DecoderDerivation(ctx: blackbox.Context) extends Derivation(ctx) {
case $scalaPkg.Right(result) =>
cursor.next()
$config.scopeDefaultNamespace.foreach(_ => cursor.unsetScopeDefaultNamespace())
$config.scopeDefaultNamespace.foreach(_ => cursor.unsetRemoveNamespaces())
new $decodingPkg.ElementDecoder.ConstDecoder[$classType](result)

case $scalaPkg.Left(error) =>
Expand Down
3 changes: 3 additions & 0 deletions modules/core/src/main/scala-3/phobos/derivation/decoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ object decoder {
result => {
$c.next()
$config.scopeDefaultNamespace.foreach(_ => $c.unsetScopeDefaultNamespace())
$config.removeNamespaces.foreach(_ => $c.unsetRemoveNamespaces())
new ConstDecoder[T](result)
},
)
Expand Down Expand Up @@ -397,6 +398,7 @@ object decoder {
if (c.getScopeDefaultNamespace == namespaceUri) $config.scopeDefaultNamespace
else $config.scopeDefaultNamespace.orElse(namespaceUri)
$config.scopeDefaultNamespace.foreach(c.setScopeDefaultNamespace)
$config.removeNamespaces.foreach(c.setRemoveNamespaces)
ElementDecoder
.errorIfWrongName[T](c, localName, newNamespaceUri.orElse(c.getScopeDefaultNamespace)) match {
case None =>
Expand Down Expand Up @@ -440,6 +442,7 @@ object decoder {
): ElementDecoder[T] = {
new ElementDecoder[T] {
def decodeAsElement(c: Cursor, localName: String, namespaceUri: Option[String]): ElementDecoder[T] = {
config.removeNamespaces.foreach(c.setRemoveNamespaces)
if (c.getEventType == AsyncXMLStreamReader.EVENT_INCOMPLETE) {
this
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ import phobos.Namespace
* the tuple is URL, second - optional preferred prefix of the namespace. The namespaces are only declared, but are
* not assigned to any elements or attributes. These declarations may be used by nested elements. This setting helps
* to avoid duplicate namespace declarations. No namespaces are defined by default.
*
* @param removeNamespaces
* Affects only decoders. Remove all namespaces in the node and propagate this to other inner decoders if inner
* decoder hasn't explicitly set this setting to "false". Otherwise node that has explicitly disabled setting will
* stop propagation.
*/
final case class ElementCodecConfig(
transformAttributeNames: String => String,
Expand All @@ -67,6 +72,7 @@ final case class ElementCodecConfig(
elementsDefaultNamespace: Option[String] = None,
defineNamespaces: List[(String, Option[String])] = Nil,
scopeDefaultNamespace: Option[String] = None,
removeNamespaces: Option[Boolean],
) {

/** See docs for [[transformElementNames]]. */
Expand Down Expand Up @@ -144,6 +150,9 @@ final case class ElementCodecConfig(
/** See docs for [[scopeDefaultNamespace]]. */
def withScopeDefaultNamespace[NS](namespace: NS)(implicit ns: Namespace[NS]): ElementCodecConfig =
copy(scopeDefaultNamespace = Some(ns.getNamespace))

def withRemoveNamespaces: ElementCodecConfig =
copy(removeNamespaces = Some(true))
}

object ElementCodecConfig {
Expand All @@ -156,5 +165,6 @@ object ElementCodecConfig {
discriminatorNamespace = Some("http://www.w3.org/2001/XMLSchema-instance"),
useElementNameAsDiscriminator = false,
defineNamespaces = Nil,
removeNamespaces = None,
)
}
25 changes: 18 additions & 7 deletions modules/core/src/main/scala/phobos/decoding/Cursor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ class Cursor(private val sr: XmlStreamReader) {
scopeDefaultNamespaceStack = scopeDefaultNamespaceStack.drop(1)
}

var removeNamespaces: List[Boolean] = List.empty

def setRemoveNamespaces(isRemoveNamespaces: Boolean): Unit =
removeNamespaces = isRemoveNamespaces :: removeNamespaces

def unsetRemoveNamespaces(): Unit = {
removeNamespaces = removeNamespaces.drop(1)
}

def getAttributeInfo: AttributeInfo = sr.getAttributeInfo
def getLocationInfo: LocationInfo = sr.getLocationInfo
def getText(w: Writer, preserveContents: Boolean): Int = sr.getText(w, preserveContents)
Expand Down Expand Up @@ -100,7 +109,8 @@ class Cursor(private val sr: XmlStreamReader) {
def getElementText: String = sr.getElementText
// def nextTag: Int = sr.nextTag

def getNamespaceURI(prefix: String): String = sr.getNamespaceURI(prefix)
def getNamespaceURI(prefix: String): String =
if (removeNamespaces.headOption.getOrElse(false)) "" else sr.getNamespaceURI(prefix)
def isStartElement: Boolean = sr.isStartElement
def isEndElement: Boolean = sr.isEndElement
def isCharacters: Boolean = sr.isCharacters
Expand All @@ -116,11 +126,12 @@ class Cursor(private val sr: XmlStreamReader) {
def isAttributeSpecified(index: Int): Boolean = sr.isAttributeSpecified(index)
def getNamespaceCount: Int = sr.getNamespaceCount
def getNamespacePrefix(index: Int): String = sr.getNamespacePrefix(index)
def getNamespaceURI(index: Int): String = sr.getNamespaceURI(index)
def getNamespaceContext: NamespaceContext = sr.getNamespaceContext
def getEventType: Int = sr.getEventType
def getText: String = sr.getText
def getTextCharacters: Array[Char] = sr.getTextCharacters
def getNamespaceURI(index: Int): String =
if (removeNamespaces.headOption.getOrElse(false)) "" else sr.getNamespaceURI(index)
def getNamespaceContext: NamespaceContext = sr.getNamespaceContext
def getEventType: Int = sr.getEventType
def getText: String = sr.getText
def getTextCharacters: Array[Char] = sr.getTextCharacters
def getTextCharacters(sourceStart: Int, target: Array[Char], targetStart: Int, length: Int): Int =
sr.getTextCharacters(sourceStart, target, targetStart, length)
def getTextStart: Int = sr.getTextStart
Expand All @@ -131,7 +142,7 @@ class Cursor(private val sr: XmlStreamReader) {
def getName: QName = sr.getName
def getLocalName: String = sr.getLocalName
def hasName: Boolean = sr.hasName
def getNamespaceURI: String = sr.getNamespaceURI
def getNamespaceURI: String = if (removeNamespaces.headOption.getOrElse(false)) "" else sr.getNamespaceURI
def getPrefix: String = sr.getPrefix
def getVersion: String = sr.getVersion
def isStandalone: Boolean = sr.isStandalone
Expand Down
55 changes: 55 additions & 0 deletions modules/core/src/test/scala/phobos/DecoderDerivationTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,61 @@ class DecoderDerivationTest extends AnyWordSpec with Matchers {
decodeElementsWithNestedScopeNamespacesFromConfig(pure)
"decode elements with nested scope namespaces from config async" in
decodeElementsWithNestedScopeNamespacesFromConfig(fromIterable)

"remove namespaces by configuration" in {

case class Foo(boo: Int)
implicit val xmlFooDecoder: XmlDecoder[Foo] = deriveXmlDecoderConfigured[Foo]("Foo", ElementCodecConfig.default.withRemoveNamespaces)

case class Baz(a: Int)
case class Bar(baz: Baz)
implicit val xmlBarDecoder: XmlDecoder[Bar] = deriveXmlDecoderConfigured[Bar]("Bar", ElementCodecConfig.default.withRemoveNamespaces)

XmlDecoder[Foo].decode("""<Foo xmlns="http://example.com"><boo>1</boo></Foo>""") match {
case Left(failure) => fail(s"Decoding result expected, got: ${failure.getMessage}")
case Right(value) => value.boo shouldBe 1
}

XmlDecoder[Foo].decode(
"""
|<Foo xmlns="http://example.com" xmlns:a="http://example.com">
| <boo>1</boo>
|</Foo>""".stripMargin) match {
case Left(failure) => fail(s"Decoding result expected, got: ${failure.getMessage}")
case Right(value) => value.boo shouldBe 1
}

XmlDecoder[Foo].decode(
"""
|<Foo xmlns="http://example.com" xmlns:a="http://example.com">
| <boo xmlns="http://example.com">1</boo>
|</Foo>""".stripMargin) match {
case Left(failure) => fail(s"Decoding result expected, got: ${failure.getMessage}")
case Right(value) => value.boo shouldBe 1
}

XmlDecoder[Foo].decode(
"""
|<Foo xmlns="http://example.com" xmlns:a="http://example.com">
| <a:boo xmlns="http://example.com">1</a:boo>
|</Foo>""".stripMargin) match {
case Left(failure) => fail(s"Decoding result expected, got: ${failure.getMessage}")
case Right(value) => value.boo shouldBe 1
}

XmlDecoder[Bar].decode(
"""
|<Bar xmlns="http://example.com">
| <baz xmlns="http://example.com">
| <a>1</a>
| </baz>
|</Bar>
|""".stripMargin
) match {
case Left(failure) => fail(s"Decoding result expected, got: ${failure.getMessage}")
case Right(value) => value.baz.a shouldBe 1
}
}
}

"Decoder derivation compilation" should {
Expand Down
Loading