packages feed

scrappy-core-0.1.0.0: src/Scrappy/Scrape.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}

module Scrappy.Scrape where

-- -- Basically just html patterns from testing / courtney market stuff
import Scrappy.Elem.Types (Elem', innerText')
import Scrappy.Elem.ElemHeadParse (hrefParser, parseOpeningTag)
import Scrappy.Elem.SimpleElemParser (el)
import Scrappy.Elem.ChainHTML ((</>>))
import Scrappy.Find (findNaive, findNaiveIO)
import Scrappy.Links (maybeUsefulUrl)
import Scrappy.Types

import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.IO.Class (MonadIO)
import Data.Functor.Identity (Identity)
import Data.Either (fromRight)
import Data.Maybe (catMaybes, fromMaybe)
import Text.Parsec (Stream, ParsecT, parse, string, parserZero, anyChar, manyTill, char, many, try, runParserT)
import Control.Applicative (liftA2) 

type ScraperT a = ParsecT Html () Identity a 
---type Html = String


-- | Generate a scraping expression where when found, it will generate and link in a data structure
-- | to a relevant next pattern. For instance, an element of interest being found then switches the
-- | scraper expression to be a reference to itselfo
-- |
-- | for instance (el "a" [("id", "x")]) -> let ALPHANUM = document.select(this) -> someThingUsing ALPHANUM_MATCH
-- | in a statement --> which references [A, B, C] 
scrapeLinked :: ParsecT s u m a -> ParsecT s u m [String]
scrapeLinked = undefined


-- TODO(galen): move to Scrappy.Scrape
-- Generic function for dropping an abstract pattern from text 
filterFromTextP :: ScraperT a -> ScraperT String
filterFromTextP p = (many $ try p) >> (liftA2 (:) anyChar $ filterFromTextP p)

-- We can return a string since this will never fail
-- its provably impossible 
filterPattern :: String -> ScraperT a -> String
filterPattern txt p = either undefined id $ parse (filterFromTextP p) "" txt 



-- | Super common case analysis
coerceMaybeParser :: Maybe a -> ScraperT a
coerceMaybeParser = \case
  Just a -> return a
  Nothing -> parserZero


hoistMaybe :: Applicative m => Maybe a -> MaybeT m a 
hoistMaybe = MaybeT . pure


exists :: ScraperT a -> Html -> Bool 
exists p html = maybe False (const True) $ runScraperOnHtml p html 

scrape :: ScraperT a -> Html -> Maybe [a]
scrape = runScraperOnHtml

scrapeFirst' :: ScraperT a -> Html -> Maybe a
scrapeFirst' f h = case scrape f h of
                    Just (x:xs) -> return x
                    _ -> Nothing 


-- fmap head
getFirstSafe :: Maybe [a] -> Maybe a
getFirstSafe (Just (x:_)) = Just x
getFirstSafe _ = Nothing


getFirstFitSafe :: (a -> Bool) -> Maybe [a] -> Maybe a
getFirstFitSafe f (Just (x:xs)) = findFit f (x:xs) 
getFirstFitSafe _ _ = Nothing 

findFit :: (a -> Bool) -> [a] -> Maybe a
findFit _ [] = Nothing
findFit cond (x:xs) = if cond x then Just x else findFit cond xs 



-- | Find all occurences of a given parsing/scraping pattern
-- | e.g. getHtml' "https://google.ca" >>= return . runScraperOnHtml (el "a" []) , would give all 'a' tag html elements on google.ca  
runScraperOnHtml :: ParsecT String () Identity a -> String -> Maybe [a]
runScraperOnHtml p html = fromRight Nothing $ parse (findNaive $ p) "" html 


runScraperOnHtmlIO :: (MonadIO m, Show a, Stream String m Char) => ParsecT String () m a -> String -> m (Maybe [a])
runScraperOnHtmlIO p html = do
  x <- runParserT (findNaiveIO $ p) () "" html 
  pure $ fromRight Nothing x 

scrapeIO :: (MonadIO m, Show a, Stream String m Char) => ParsecT String () m a -> String -> m (Maybe [a])
scrapeIO = runScraperOnHtmlIO 

runScraperInBody :: ParsecT String () Identity a -> String -> Maybe [a]
runScraperInBody prsr html = fromRight Nothing $ parse (skipToInBody >> findNaive prsr) "" html

skipToInBody :: Stream s m Char => ParsecT s u m ()
skipToInBody = manyTill anyChar (parseOpeningTag (Just ["html"]) [] >> char '>')
               </>> el "head" []
               </>> parseOpeningTag (Just ["body"]) []
               >> char '>'
               >> return () 

  
runScraperOnBody :: ParsecT String () Identity a -> String -> Maybe [a] 
runScraperOnBody prsr html = fromRight Nothing $ parse (skipToBody >> findNaive prsr) "" html 

skipToBody :: Stream s m Char => ParsecT s u m ()
skipToBody = manyTill anyChar (parseOpeningTag (Just ["html"]) [] >> char '>') </>> el "head" [] >> return () 


runScraperOnHtml1 :: ParsecT String () Identity a -> String -> Maybe a
runScraperOnHtml1 p = (fmap head) . runScraperOnHtml p





-- {-# DEPRECATED simpleScrape' "from fba project - gives confusing String output" #-}
-- simpleScrape' :: ParsecT String () Identity String -> String -> String 
-- simpleScrape' p html = case parse (findNaive p) "" html of
--   Right (Just (x:_)) -> x
--   Right (Just []) -> "NothingA"
--   Right (Nothing) -> "NothingB"
--   Left err -> "Nothing" <> show err



-- clean :: String -> String
-- clean = undefined -- drop if == ( \n | \" | '\\' )


-- -- | uses maybeUsefulUrl to get all links on page pointing only to same site links
-- allLinks :: String -> ParsecT String () Identity [String] 
-- allLinks baseUrl = do
--   x <- findNaive hrefParser 
--   return $ case x of
--     Just (x':xs') -> catMaybes $ fmap (maybeUsefulUrl baseUrl) (x':xs')
--     Just [] -> []
--     Nothing -> [] 

-- type Name = String -- placeholder
-- tableItem :: Name -> Elem' String
-- tableItem = undefined




-- scrapeInnerText :: ParsecT String () Identity (Elem' String) -> String 
-- scrapeInnerText p = case parse (findNaive p) "" body of
--   Right (Just (x:_)) -> innerText' x
--   Right (Just []) -> "Nothing"
--   Right (Nothing) -> "Nothing"
--   Left err -> show err




scrapeFirst :: Stream s m Char => ParsecT s u m a -> ParsecT s u m (Maybe a)
scrapeFirst p = do
  x <- findNaive p
  case x of
    Just (x:_) -> return $ Just x
    Nothing -> return $ Nothing

 
findCount :: Stream s m Char => ParsecT s u m a -> ParsecT s u m Int
findCount p = do
  x <- findNaive p
  return $ length (fromMaybe [] x)




type Prefix' = String
{-# DEPRECATED scrapeBracketed "experimental, first attempt" #-}
scrapeBracketed :: Prefix' -> ScraperT a -> Html -> Maybe [a]
scrapeBracketed pre scraper html = mconcat <$> scrape (string pre >> manyTill scraper (string pre)) html



type Prefix = String 
scrapePrefixed :: Prefix -> ScraperT a -> Html -> Maybe [a]
scrapePrefixed pre scraper html = scrape (string pre >> scraper) html