packages feed

hakyll-diagrams-0.1.0.0: test/HTMLSVGCompare.hs

{-# LANGUAGE OverloadedStrings #-}

module HTMLSVGCompare (
  shouldBeSimilar,
) where

import Data.List (sortOn)
import Data.Text (Text, strip)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Read as TR
import Test.Hspec
import Text.HTML.TagSoup
import Text.HTML.TagSoup.Tree


data Norm = Norm
  { nName :: Text
  , nAttrs :: [(Text, Text)]
  , nChildren :: [Norm]
  , nText :: Text
  }
  deriving (Eq, Show)


-- | Expect two HTML snippets to be "structurally equal"
shouldBeSimilar :: Text -> FilePath -> Expectation
shouldBeSimilar actual expectedPath = do
  expected <- T.readFile expectedPath
  let normA = normalizeTree <$> parseTree (strip actual)
      normE = normalizeTree <$> parseTree (strip expected)
  normA `shouldBe` normE


normalizeTree :: TagTree Text -> Norm
normalizeTree node = case node of
  TagBranch name attrs children
    | name == "svg" ->
        Norm
          { nName = name
          , nAttrs = normalizeAttrs attrs
          , -- We don't care about the actual drawing, that
            -- is responsibility of the diagrams library
            nChildren = []
          , nText = ""
          }
    | otherwise ->
        Norm
          { nName = name
          , nAttrs = normalizeAttrs attrs
          , nChildren = normalizeTree <$> children
          , nText = ""
          }
  TagLeaf (TagText txt) ->
    Norm
      { nName = "text"
      , nAttrs = []
      , nChildren = []
      , nText = normalizeText txt
      }
  TagLeaf (TagOpen name _)
    | name `elem` ["?xml", "!DOCTYPE"] ->
        Norm
          { nName = name
          , nAttrs = []
          , nChildren = []
          , nText = ""
          }
  other -> error $ "normalizeTree: expected element but got: " ++ show other
  where
    normalizeAttrs attrs = sortOn fst [(k, normalizeText v) | (k, v) <- attrs]
    normalizeText t = T.unwords $ normalizeNumeric <$> T.words t


parseNumber :: T.Text -> Maybe Double
parseNumber t =
  case TR.double t of
    Right (n, rest) | T.null rest -> Just n
    _ -> Nothing


epsilon :: Double
epsilon = 0.001


normalizeNumeric :: T.Text -> T.Text
normalizeNumeric t =
  case parseNumber t of
    Just n ->
      let bucket = (fromIntegral :: Int -> Double) (round (n / epsilon)) * epsilon
       in T.pack (show bucket)
    Nothing -> T.strip t