Skip to content

Commit

Permalink
Merge pull request #5 from iyfedorov/feature/#3_ignore_namespaces
Browse files Browse the repository at this point in the history
#3 ignore namespaces
  • Loading branch information
valentiay authored Apr 21, 2024
2 parents 07484de + 63c47f8 commit 4d5ee65
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 7 deletions.
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

0 comments on commit 4d5ee65

Please sign in to comment.