packages feed

opc-xml-da-client-0.1: xml-builder/OpcXmlDaClient/XmlBuilder.hs

-- |
-- A utility library providing automatic namespacing,
-- which is required in handling of custom value types by the OPC protocol.
module OpcXmlDaClient.XmlBuilder
  ( -- * ByteString
    elementXml,

    -- * Element
    Element,
    element,

    -- * Node
    Node,
    elementNode,
    contentNode,
    astNode,

    -- * Content
    Content,
    textContent,
    qNameContent,

    -- * QName
    QName,
    namespacedQName,
    unnamespacedQName,
  )
where

import qualified Data.ByteString.Lazy as Lbs
import qualified Data.Map.Strict as Map
import OpcXmlDaClient.Base.Prelude
import qualified OpcXmlDaClient.XmlBuilder.Identified as Identified
import qualified Text.XML as Xml

-- *

elementXml :: Element -> ByteString
elementXml (Element namespacedElement) =
  case runNamespaced namespacedElement of
    (element, namespaces) ->
      let document = Xml.Document (Xml.Prologue [] Nothing []) element []
          renderingSettings = def {Xml.rsNamespaces = fmap swap namespaces}
       in Lbs.toStrict (Xml.renderLBS renderingSettings document)

-- *

newtype Element = Element (Namespaced Xml.Element)

element :: QName -> [(QName, Content)] -> [Node] -> Element
element (QName qName) attrList nodeList =
  Element $
    Xml.Element
      <$> qName
      <*> fmap Map.fromList (traverse attr attrList)
      <*> traverse runNode nodeList
  where
    attr (QName name, Content content) =
      (,) <$> name <*> content

-- *

newtype Node = Node {runNode :: Namespaced Xml.Node}

elementNode :: Element -> Node
elementNode (Element a) = Node (fmap Xml.NodeElement a)

contentNode :: Content -> Node
contentNode (Content a) = Node (fmap Xml.NodeContent a)

astNode :: Xml.Node -> Node
astNode = Node . pure

-- *

newtype Content = Content (Namespaced Text)

instance IsString Content where
  fromString = textContent . fromString

textContent :: Text -> Content
textContent text =
  Content (pure text)

qNameContent :: QName -> Content
qNameContent (QName qName) =
  Content (fmap renderName qName)
  where
    renderName (Xml.Name name _ prefix) =
      maybe name (\prefix -> prefix <> ":" <> name) prefix

-- *

newtype QName = QName (Namespaced Xml.Name)

instance IsString QName where
  fromString = unnamespacedQName . fromString

-- |
-- Namespaced QName.
namespacedQName ::
  -- | Namespace URI.
  Text ->
  -- | Name.
  Text ->
  QName
namespacedQName uri name =
  QName (Identified.identifying uri (\prefix -> Xml.Name name (Just uri) (Just prefix)))

-- |
-- Unnamespaced QName.
unnamespacedQName :: Text -> QName
unnamespacedQName name =
  QName (pure (Xml.Name name Nothing Nothing))

-- *

type Namespaced = Identified.Identified Text Text

runNamespaced :: Namespaced a -> (a, [(Text, Text)])
runNamespaced identified =
  Identified.run identified alias
  where
    alias x = "ns" <> fromString (show (succ x))