packages feed

xml-types-content-0.0.1.1: src/hspec/XmlTypesContentSpec.hs

module XmlTypesContentSpec (spec) where

import qualified Data.ByteString.Lazy
import qualified Data.Text as Text
import Data.XML.Types
import LawfulConversions
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Test.QuickCheck.Property
import qualified Text.XML
import qualified Text.XML.Stream.Parse
import qualified Text.XML.Unresolved
import qualified XmlTypesContent
import Prelude

spec :: Spec
spec = do
  describe "textNodes" do
    it "Properly encodes a control character" do
      let text = "a&b"
      XmlTypesContent.textContents text
        `shouldBe` [ContentText "a", ContentEntity "amp", ContentText "b"]

    it "Properly encodes an undefined character" do
      let text = "a\64976b"
      XmlTypesContent.textContents text
        `shouldBe` [ContentText "a", ContentEntity "#64976", ContentText "b"]

    prop "Produces nodes that decode to the source" do
      -- For some reason xml-conduit remaps '\r' to '\n', so we simply work around that.
      text <- Text.pack <$> suchThat arbitrary (not . elem '\r')
      pure case extractTextContentFromXml (textXml text) of
        Right decodedText ->
          if decodedText == text
            then succeeded
            else failed {Test.QuickCheck.Property.reason}
          where
            reason =
              [ show text,
                "\n  =/=\n",
                show decodedText,
                "\nXML:\n",
                to @_ @Text (from (textXml text))
              ]
                & mconcat
        Left parsingErr ->
          failed {Test.QuickCheck.Property.reason}
          where
            reason =
              [ "Failed to parse due to: ",
                parsingErr,
                "\n",
                "XML:\n",
                from (textXml text)
              ]
                & mconcat
                & to

textXml :: Text -> Data.ByteString.Lazy.ByteString
textXml = nodesXml . fmap NodeContent . XmlTypesContent.textContents

nodesXml :: [Data.XML.Types.Node] -> Data.ByteString.Lazy.ByteString
nodesXml =
  buildLbs . buildDocument . buildRootElement
  where
    buildRootElement elementNodes =
      Data.XML.Types.Element
        { elementName = "a",
          elementAttributes = [],
          elementNodes
        }
    buildDocument documentRoot =
      Data.XML.Types.Document
        { documentPrologue =
            Data.XML.Types.Prologue [] Nothing [],
          documentEpilogue =
            [],
          documentRoot
        }
    buildLbs =
      Text.XML.Unresolved.renderLBS Text.XML.Unresolved.def

extractTextContentFromXml :: Data.ByteString.Lazy.ByteString -> Either Text Text
extractTextContentFromXml = parseLbs >=> parseAst
  where
    parseLbs =
      first (fromString . displayException) . Text.XML.parseLBS settings
      where
        settings =
          Text.XML.def
            { Text.XML.Stream.Parse.psDecodeIllegalCharacters = Just . chr
            }
    parseAst =
      parseNodes . Text.XML.elementNodes . Text.XML.documentRoot
      where
        parseNodes = \case
          [Text.XML.NodeContent content] -> pure content
          [] -> pure ""
          _ -> Left "Invalid nodes"