All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.tinkoff.phobos.encoding.PhobosStreamWriter.scala Maven / Gradle / Ivy

The newest version!
package ru.tinkoff.phobos.encoding

import java.math.BigInteger
import javax.xml.namespace.{NamespaceContext, QName}
import javax.xml.stream.XMLStreamException
import org.codehaus.stax2.typed.Base64Variant
import org.codehaus.stax2.{XMLStreamLocation2, XMLStreamReader2, XMLStreamWriter2}
import org.codehaus.stax2.validation.{ValidationProblemHandler, XMLValidationSchema, XMLValidator}
import PhobosStreamWriter.prefixBase

/** [[PhobosStreamWriter]] implements most methods of [[XMLStreamWriter2]], but it does not extends [[XMLStreamWriter2]]
  *
  * Unlike [[XMLStreamWriter2]], [[PhobosStreamWriter]] does not throw any exceptions if it is used to write invalid
  * characters. Such characters are simply filtered out. However, [[PhobosStreamWriter]] can still throw exceptions if
  * it is used to write invalid attribute or element names or namespaces with invalid characters.
  */
final class PhobosStreamWriter(sw: XMLStreamWriter2) {

  private var discriminatorLocalName: Option[String] = None
  private var discriminatorNamespace: Option[String] = None
  private var discriminatorValue: Option[String]     = None

  /** Writes type-discriminator attribute inside next start element
    *
    * Following code  sw.memoizeDiscriminator(Some("http://www.w3.org/2001/XMLSchema-instance"), "type", "dog")
    * sw.writeStartElement("GoodBoy")  will result to something like   
    *
    * This API extension is required to keep ElementEncoder API simple. This method overrides old discriminator if it
    * was already memorized
    *
    * @param namespaceUri
    *   namespace uri of type discriminator
    * @param localName
    *   local name of type discriminator
    * @param value
    *   value of type discriminator
    */
  def memorizeDiscriminator(namespaceUri: Option[String], localName: String, value: String): Unit = {
    discriminatorNamespace = namespaceUri
    discriminatorLocalName = Some(localName)
    discriminatorValue = Some(value)
  }

  def isPropertySupported(name: String): Boolean =
    sw.isPropertySupported(name)

  def setProperty(name: String, value: Any): Boolean =
    sw.setProperty(name, value)

  def getLocation: XMLStreamLocation2 =
    sw.getLocation

  def getEncoding: String =
    sw.getEncoding

  def writeDTD(rootName: String, systemId: String, publicId: String, internalSubset: String): Unit =
    sw.writeDTD(rootName, systemId, publicId, internalSubset)

  def writeFullEndElement(): Unit =
    sw.writeFullEndElement()

  def writeStartDocument(version: String, encoding: String, standAlone: Boolean): Unit =
    sw.writeStartDocument(version, encoding, standAlone)

  def writeSpace(text: String): Unit =
    sw.writeSpace(text.filter(_.isWhitespace))

  def writeRaw(text: String): Unit =
    sw.writeRaw(filterXmlText(text))

  def copyEventFromReader(r: XMLStreamReader2, preserveEventData: Boolean): Unit =
    sw.copyEventFromReader(r, preserveEventData)

  def closeCompletely(): Unit =
    sw.closeCompletely()

  def writeBoolean(value: Boolean): Unit =
    sw.writeBoolean(value)

  def writeInt(value: Int): Unit =
    sw.writeInt(value)

  def writeLong(value: Long): Unit =
    sw.writeLong(value)

  def writeFloat(value: Float): Unit =
    sw.writeFloat(value)

  def writeDouble(value: Double): Unit =
    sw.writeDouble(value)

  def writeInteger(value: BigInteger): Unit =
    sw.writeInteger(value)

  def writeDecimal(value: java.math.BigDecimal): Unit =
    sw.writeDecimal(value)

  def writeQName(value: QName): Unit =
    sw.writeQName(value)

  def writeBinary(value: Array[Byte], from: Int, length: Int): Unit =
    sw.writeBinary(value, from, length)

  def writeBinary(variant: Base64Variant, value: Array[Byte], from: Int, length: Int): Unit =
    sw.writeBinary(variant, value, from, length)

  def writeIntArray(value: Array[Int], from: Int, length: Int): Unit =
    sw.writeIntArray(value, from, length)

  def writeLongArray(value: Array[Long], from: Int, length: Int): Unit =
    sw.writeLongArray(value, from, length)

  def writeFloatArray(value: Array[Float], from: Int, length: Int): Unit =
    sw.writeFloatArray(value, from, length)

  def writeDoubleArray(value: Array[Double], from: Int, length: Int): Unit =
    sw.writeDoubleArray(value, from, length)

  def writeBooleanAttribute(prefix: String, namespaceURI: String, localName: String, value: Boolean): Unit =
    sw.writeBooleanAttribute(prefix, namespaceURI, localName, value)

  def writeIntAttribute(prefix: String, namespaceURI: String, localName: String, value: Int): Unit =
    sw.writeIntAttribute(prefix, namespaceURI, localName, value)

  def writeLongAttribute(prefix: String, namespaceURI: String, localName: String, value: Long): Unit =
    sw.writeLongAttribute(prefix, namespaceURI, localName, value)

  def writeFloatAttribute(prefix: String, namespaceURI: String, localName: String, value: Float): Unit =
    sw.writeFloatAttribute(prefix, namespaceURI, localName, value)

  def writeDoubleAttribute(prefix: String, namespaceURI: String, localName: String, value: Double): Unit =
    sw.writeDoubleAttribute(prefix, namespaceURI, localName, value)

  def writeIntegerAttribute(prefix: String, namespaceURI: String, localName: String, value: BigInteger): Unit =
    sw.writeIntegerAttribute(prefix, namespaceURI, localName, value)

  def writeDecimalAttribute(
      prefix: String,
      namespaceURI: String,
      localName: String,
      value: java.math.BigDecimal,
  ): Unit =
    sw.writeDecimalAttribute(prefix, namespaceURI, localName, value)

  def writeQNameAttribute(prefix: String, namespaceURI: String, localName: String, value: QName): Unit =
    sw.writeQNameAttribute(prefix, namespaceURI, localName, value)

  def writeBinaryAttribute(prefix: String, namespaceURI: String, localName: String, value: Array[Byte]): Unit =
    sw.writeBinaryAttribute(prefix, namespaceURI, localName, value)

  def writeBinaryAttribute(
      variant: Base64Variant,
      prefix: String,
      namespaceURI: String,
      localName: String,
      value: Array[Byte],
  ): Unit =
    sw.writeBinaryAttribute(variant, prefix, namespaceURI, localName, value)

  def writeIntArrayAttribute(prefix: String, namespaceURI: String, localName: String, value: Array[Int]): Unit =
    sw.writeIntArrayAttribute(prefix, namespaceURI, localName, value)

  def writeLongArrayAttribute(prefix: String, namespaceURI: String, localName: String, value: Array[Long]): Unit =
    sw.writeLongArrayAttribute(prefix, namespaceURI, localName, value)

  def writeFloatArrayAttribute(prefix: String, namespaceURI: String, localName: String, value: Array[Float]): Unit =
    sw.writeFloatArrayAttribute(prefix, namespaceURI, localName, value)

  def writeDoubleArrayAttribute(prefix: String, namespaceURI: String, localName: String, value: Array[Double]): Unit =
    sw.writeDoubleArrayAttribute(prefix, namespaceURI, localName, value)

  private def maybeWriteDiscriminator(): Unit = {
    (discriminatorNamespace, discriminatorLocalName, discriminatorValue) match {
      case (None, None, None)                                 =>
      case (None, Some(dLocalName), Some(dValue))             => sw.writeAttribute(dLocalName, dValue)
      case (Some(dNamespace), Some(dLocalName), Some(dValue)) => sw.writeAttribute(dNamespace, dLocalName, dValue)
      case state                                              => throw new XMLStreamException(s"Unexpected discriminator names state: $state")
    }
    discriminatorNamespace = None
    discriminatorLocalName = None
    discriminatorValue = None
  }

  def writeStartElement(localName: String): Unit = {
    sw.writeStartElement(localName: String)
    maybeWriteDiscriminator()
  }

  def writeStartElement(namespaceURI: String, localName: String): Unit = {
    sw.writeStartElement(namespaceURI, localName)
    maybeWriteDiscriminator()
  }

  def writeStartElement(prefix: String, localName: String, namespaceURI: String): Unit = {
    sw.writeStartElement(prefix, localName, namespaceURI)
    maybeWriteDiscriminator()
  }

  def writeEmptyElement(namespaceURI: String, localName: String): Unit =
    sw.writeEmptyElement(namespaceURI, localName)

  def writeEmptyElement(prefix: String, localName: String, namespaceURI: String): Unit =
    sw.writeEmptyElement(prefix, localName, namespaceURI)

  def writeEmptyElement(localName: String): Unit =
    sw.writeEmptyElement(localName)

  def writeEndElement(): Unit =
    sw.writeEndElement()

  def writeEndDocument(): Unit =
    sw.writeEndDocument()

  def close(): Unit =
    sw.close()

  def flush(): Unit =
    sw.flush()

  def writeAttribute(localName: String, value: String): Unit =
    sw.writeAttribute(localName, filterXmlText(value))

  def writeAttribute(prefix: String, namespaceURI: String, localName: String, value: String): Unit =
    sw.writeAttribute(prefix, namespaceURI, localName, filterXmlText(value))

  def writeAttribute(namespaceURI: String, localName: String, value: String): Unit =
    sw.writeAttribute(namespaceURI, localName, filterXmlText(value))

  def writeNamespace(prefix: String, namespaceURI: String): Unit =
    sw.writeNamespace(prefix, namespaceURI)

  /** Writes a namespace to the output stream Generates unbound prefix in format ans\d+ and binds it to URI
    *
    * @param namespaceURI
    *   the uri to bind the prefix to
    */
  def writeNamespace(namespaceURI: String): Unit = {
    val nsContext = sw.getNamespaceContext
    if (nsContext.getPrefix(namespaceURI) == null) {
      var left  = 1
      var right = 1
      while (nsContext.getNamespaceURI(prefixBase + right) != null) {
        left = right
        right *= 2
      }
      while (left + 1 < right) {
        val median = (left + right) / 2
        if (nsContext.getNamespaceURI(prefixBase + median) == null) right = median
        else left = median
      }
      sw.writeNamespace(prefixBase + right, namespaceURI)
    }
  }

  def writeDefaultNamespace(namespaceURI: String): Unit =
    sw.writeDefaultNamespace(namespaceURI)

  def writeComment(data: String): Unit =
    sw.writeComment(filterXmlText(data))

  def writeProcessingInstruction(target: String): Unit =
    sw.writeProcessingInstruction(target)

  def writeProcessingInstruction(target: String, data: String): Unit =
    sw.writeProcessingInstruction(target, data)

  def writeCData(data: String): Unit =
    sw.writeCData(filterXmlText(data))

  def writeDTD(dtd: String): Unit =
    sw.writeDTD(dtd)

  def writeEntityRef(name: String): Unit =
    sw.writeEntityRef(name)

  def writeStartDocument(): Unit =
    sw.writeStartDocument()

  def writeStartDocument(version: String): Unit =
    sw.writeStartDocument(version)

  def writeStartDocument(encoding: String, version: String): Unit =
    sw.writeStartDocument(encoding, version)

  def writeCharacters(text: String): Unit =
    sw.writeCharacters(filterXmlText(text))

  def getPrefix(uri: String): String =
    sw.getPrefix(uri)

  def setPrefix(prefix: String, uri: String): Unit =
    sw.setPrefix(prefix, uri)

  def setDefaultNamespace(uri: String): Unit =
    sw.setDefaultNamespace(uri)

  def setNamespaceContext(context: NamespaceContext): Unit =
    sw.setNamespaceContext(context)

  def getNamespaceContext: NamespaceContext =
    sw.getNamespaceContext

  def getProperty(name: String): AnyRef =
    sw.getProperty(name)

  def validateAgainst(schema: XMLValidationSchema): XMLValidator =
    sw.validateAgainst(schema)

  def stopValidatingAgainst(schema: XMLValidationSchema): XMLValidator =
    sw.stopValidatingAgainst(schema)

  def stopValidatingAgainst(validator: XMLValidator): XMLValidator =
    sw.stopValidatingAgainst(validator)

  def setValidationProblemHandler(h: ValidationProblemHandler): ValidationProblemHandler =
    sw.setValidationProblemHandler(h)

  private def filterXmlText(string: String) =
    if (string.forall(PhobosStreamWriter.isValidXmlCharacter)) string
    else string.filter(PhobosStreamWriter.isValidXmlCharacter)
}

object PhobosStreamWriter {
  val prefixBase = "ans"

  def isValidXmlCharacter(char: Char): Boolean =
    !(char < ' '
      || 0xd800 <= char && char <= 0xdfff
      || char == 0xfffe || char == 0xffff)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy