hw-xml-0.5.1.0: app/App/Commands/Count.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeSynonymInstances #-}
module App.Commands.Count
( cmdCount
) where
import App.Options
import Control.Lens
import Control.Monad
import Data.Generics.Product.Any
import Data.Text (Text)
import GHC.Generics
import HaskellWorks.Data.TreeCursor
import HaskellWorks.Data.Xml.DecodeResult
import HaskellWorks.Data.Xml.RawDecode
import HaskellWorks.Data.Xml.RawValue
import HaskellWorks.Data.Xml.Succinct.Cursor.Load
import HaskellWorks.Data.Xml.Succinct.Cursor.MMap
import HaskellWorks.Data.Xml.Succinct.Index
import HaskellWorks.Data.Xml.Value
import Options.Applicative hiding (columns)
import qualified App.Commands.Types as Z
import qualified App.Naive as NAIVE
import qualified App.XPath.Parser as XPP
import qualified System.Exit as IO
import qualified System.IO as IO
-- | Document model. This does not need to be able to completely represent all
-- the data in the XML document. In fact, having a smaller model may improve
-- Count performance.
data Plant = Plant
{ common :: String
, price :: String
} deriving (Eq, Show, Generic)
newtype Catalog = Catalog
{ plants :: [Plant]
} deriving (Eq, Show, Generic)
tags :: Value -> Text -> [Value]
tags xml@(XmlElement n _ _) elemName = if n == elemName
then [xml]
else []
tags _ _ = []
kids :: Value -> [Value]
kids (XmlElement _ _ cs) = cs
kids _ = []
countAtPath :: [Text] -> Value -> DecodeResult Int
countAtPath [] _ = return 0
countAtPath [t] xml = return (length (tags xml t))
countAtPath (t:ts) xml = do
counts <- forM (tags xml t >>= kids) $ countAtPath ts
return (sum counts)
runCount :: Z.CountOptions -> IO ()
runCount opt = do
let input = opt ^. the @"input"
let xpath = opt ^. the @"xpath"
let method = opt ^. the @"method"
IO.putStrLn $ "XPath: " <> show xpath
cursorResult <- case method of
"mmap" -> Right <$> mmapFastCursor input
"memory" -> Right <$> loadFastCursor input
"naive" -> Right <$> NAIVE.loadFastCursor input
unknown -> return (Left ("Unknown method " <> show unknown))
case cursorResult of
Right !cursor -> do
-- Skip the XML declaration to get to the root element cursor
case nextSibling cursor of
Just rootCursor -> do
-- Get the root raw XML value at the root element cursor
let rootValue = rawValueAt (xmlIndexAt rootCursor)
-- Show what we have at this cursor
putStrLn $ "Raw value: " <> take 100 (show rootValue)
-- Decode the raw XML value
case countAtPath (xpath ^. the @"path") (rawDecode rootValue) of
DecodeOk count -> putStrLn $ "Count: " <> show count
DecodeFailed msg -> putStrLn $ "Error: " <> show msg
Nothing -> do
putStrLn "Could not read XML"
return ()
Left msg -> do
IO.putStrLn $ "Error: " <> msg
IO.exitFailure
optsCount :: Parser Z.CountOptions
optsCount = Z.CountOptions
<$> strOption
( long "input"
<> help "Input file"
<> metavar "FILE"
)
<*> optionParser XPP.path
( long "xpath"
<> help "XPath expression"
<> metavar "XPATH"
)
<*> textOption
( long "method"
<> help "Read method"
<> metavar "METHOD"
)
cmdCount :: Mod CommandFields (IO ())
cmdCount = command "count" $ flip info idm $ runCount <$> optsCount