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

fs2.data.xml.internals.NamespaceResolver.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 Lucas Satabin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fs2
package data
package xml
package internals

import cats._
import cats.implicits._

private case class ResolverEnv(parent: Option[ResolverEnv], nss: Map[String, String], depth: Int) {
  def resolve(prefix: String): Option[String] =
    nss.get(prefix).orElse(parent.flatMap(_.resolve(prefix)))
  def pop: Option[ResolverEnv] =
    if (depth <= 0)
      parent
    else
      Some(copy(depth = depth - 1))
  def push(env: Map[String, String]): ResolverEnv =
    if (env.isEmpty)
      copy(depth = depth + 1)
    else
      ResolverEnv(Some(this), env, 0)
}

private[xml] class NamespaceResolver[F[_]](implicit F: MonadError[F, Throwable]) {

  private val xmlNSURI = Some("http://www.w3.org/XML/1998/namespace")

  def pipe: Pipe[F, XmlEvent, XmlEvent] =
    _.evalMapAccumulate[F, ResolverEnv, XmlEvent](ResolverEnv(None, Map.empty, 0)) {
      case (env, evt @ XmlEvent.StartTag(name, attrs, _)) =>
        for {
          // create a new scoped env with the potentially declared namespaces
          mapping <- attrs.foldM(Map.empty[String, String]) { (env, attr) =>
            updateNS(env, attr)
          }
          env1 = env.push(mapping)
          // resolve attributes in the new env
          attrs <- attrs.traverse(attr => resolve(env1, attr.name, false).map(n => attr.copy(name = n)))
          // check attribute uiqueness
          _ <- checkDuplicates(attrs)
          // resolve tag name in the new env
          name <- resolve(env1, name, true)
        } yield (env1, evt.copy(name = name, attributes = attrs))
      case (env, evt @ XmlEvent.EndTag(name)) =>
        for {
          // resolve the tag name in the current env
          name <- resolve(env, name, true)
          // close the current environment scope
          env <- env.pop match {
            case Some(env) =>
              F.pure(env)
            case None =>
              F.raiseError(new XmlException(XmlSyntax("GIMatch"), s"unexpected closing tag ''"))
          }
        } yield (env, evt.copy(name = name))
      case v =>
        F.pure(v)
    }.map(_._2)

  private def resolve(env: ResolverEnv, name: QName, withDefault: Boolean): F[QName] =
    name match {
      case QName(Some(pfx), local@_) if pfx.take(3).toLowerCase =!= "xml" =>
        // prefixes starting by `xml` are reserved
        env.resolve(pfx) match {
          case None => F.raiseError(new XmlException(NSCPrefixDeclared, s"undeclared namespace $pfx"))
          case uri  => F.pure(name.copy(prefix = uri))
        }
      case QName(None, _) if withDefault =>
        env.resolve("") match {
          case uri @ Some(_) => F.pure(name.copy(prefix = uri))
          case None          => F.pure(name.copy(prefix = xmlNSURI))
        }
      case _ =>
        F.pure(name)
    }

  private def checkDuplicates(attrs: List[Attr]): F[Unit] =
    attrs
      .foldM(Set.empty[QName]) {
        case (seen, Attr(name, _)) =>
          if (seen.contains(name))
            F.raiseError[Set[QName]](
              new XmlException(NSCAttributesUnique, s"duplicate attribute with resolved name ${name.render}"))
          else
            F.pure(seen + name)
      }
      .void

  private def updateNS(env: Map[String, String], attr: Attr): F[Map[String, String]] =
    attr match {
      case Attr(QName(None, "xmlns"), v) =>
        val uri = v.map(_.render).mkString
        F.pure(env.updated("", uri))
      case Attr(QName(Some("xmlns"), name), v) =>
        val uri = v.map(_.render).mkString
        if (uri.isEmpty)
          F.raiseError(new XmlException(NSCNoPrefixUndeclaring, s"undeclaring namespace $name is not allowed"))
        else
          F.pure(env.updated(name, uri))
      case _ =>
        F.pure(env)
    }

}

private[xml] object NamespaceResolver {
  def apply[F[_]](implicit F: MonadError[F, Throwable]): NamespaceResolver[F] =
    new NamespaceResolver[F]
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy