packages feed

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

{-# LANGUAGE FlexibleContexts #-}

module Scrappy.Find where

--import Scrappy.Elem.Types (ElementRep, GroupHtml(GroupHtml), Elem, mkGH, Elem', TreeHTML, ShowHTML)
-- import Elem.TreeElemParser (findSameTreeH)
--import Scrappy.Types (ScrapeFail(..))

import Control.Monad.IO.Class
import Text.Parsec (ParsecT, ParseError, Parsec, Stream, parse, eof, anyChar, (<|>), try, parserZero, anyChar
                   , many) 
import Data.Text (Text)
import Data.Functor.Identity (Identity)
import Data.Either (fromRight)
import Scrappy.Types (ScrapeFail(..))

--data ScrapeFail = Eof | NonMatch

-- | This module provides an interface for getting patterns seperated by whatever in a given source
-- | that you plan to parse

-- | findSequential(_x) is for information rich elements such as products that should have multiple fields
-- | that the user would like to return 




-- | Converts a parsing/scraping pattern to one which either returns Nothing
-- | or Just a list of at least 1 element. Maybe type is used so that there is a clearer
-- | distinction between a failed search and a successful one
findNaive :: Stream s m Char => ParsecT s u m a -> ParsecT s u m (Maybe [a])
findNaive p = (justify .  (fromRight mempty) . sequenceA) <$> (find p)
  where
    justify x = if length x == 0 then Nothing else Just x 


findNaiveIO :: (MonadIO m, Stream s m Char, Show a) => ParsecT s u m a -> ParsecT s u m (Maybe [a])
findNaiveIO p = (justify .  (fromRight mempty) . sequenceA) <$> (findIO p)
  where
    justify x = if length x == 0 then Nothing else Just x 


-- | Great for debugging
findIO :: (MonadIO m, Stream s m Char, Show a) => ParsecT s u m a -> ParsecT s u m [Either ScrapeFail a]
findIO parser = do
  x <- (try (baseParser parser)) <|> givesNothing <|> endStream
  liftIO $ print x
  case x of
    Right a -> fmap (x :) (find parser)
    Left Eof -> return []
    Left NonMatch -> find parser


-- givesNothing :: ParsecT e s m (Either ScrapeFail a) 
-- givesNothing = Left NonMatch <$ anyChar

findSequential :: Stream s m Char => [ParsecT s u m a] -> ParsecT s u m [Either ScrapeFail a] 
findSequential parsers = undefined -- builds off findUntilMatch

findSequential2 :: Stream s m Char => (ParsecT s u m a, ParsecT s u m b) -> ParsecT s u m (a,b)
findSequential2 (a,b) = do
  a' <- findUntilMatch a
  b' <- findUntilMatch b
  return (a', b')

findSequential3 :: Stream s m Char => (ParsecT s u m a, ParsecT s u m b, ParsecT s u m c) -> ParsecT s u m (a,b,c)
findSequential3 (a,b,c) = do
  a' <- findUntilMatch a
  
  b' <- findUntilMatch b
  c' <- findUntilMatch c
  return (a', b', c')

-- | Like find naive except that finishes parsing on the first match it finds in the document
findUntilMatch :: Stream s m Char => ParsecT s u m a -> ParsecT s u m a
findUntilMatch parser = do
  x <- (try (baseParser parser)) <|> givesNothing
  case x of
    Right a -> return a
    Left NonMatch -> findUntilMatch parser 
    Left Eof -> parserZero


-- -- this is for sequencing matches amongst noise
-- findUntilMatch2 :: ParsecT s u m a -> ParsecT s u m (Either ScrapeFail a)
-- findUntilMatch2 parser = do
--   x <- (try (baseParser parser)) <|> givesNothing
--   case x of
--     Right a -> return $ Right a
--     Left NonMatch -> findUntilMatch parser 
--     Left Eof -> parserZero 
  


      
-- -- Note: List will be backwards as is 
find :: Stream s m Char => ParsecT s u m a -> ParsecT s u m [Either ScrapeFail a]
find parser = do
  x <- (try (baseParser parser)) <|> givesNothing <|> endStream
  case x of
    Right a -> fmap (x :) (find parser)
    Left Eof -> return []
    Left NonMatch -> find parser
-- return (x:xs)

-- | Should never throw Left or I did it wrong
streamEdit :: ParsecT String () Identity a -> (a -> String) -> String -> String
streamEdit p f src = fromRight undefined $ parse (try $ findEdit f p) "" src


-- -- Note: List will be backwards as is 
findEdit :: Stream String m Char => (a -> String) -> ParsecT String u m a -> ParsecT String u m String 
findEdit f parser = do
  let endStream = try eof >> (return EOF)
  x <- ((Edit . f) <$> (try parser)) <|> (Carry <$> anyChar) <|> endStream
  case x of
    Edit str -> fmap (str <>) (findEdit f parser) 
    Carry chr -> fmap ([chr] <>) (findEdit f parser) 
    EOF -> return [] 


-- -- Note: List will be backwards as is 
editFirst :: Stream String m Char => (a -> String) -> ParsecT String u m a -> ParsecT String u m String 
editFirst f parser = do
  let endStream = try eof >> (return EOF)
  x <- ((Edit . f) <$> (try parser)) <|> (Carry <$> anyChar) <|> endStream
  case x of
    Edit str -> fmap (str <>) $ many anyChar -- consume rest automatically  --  (findEdit f parser) 
    Carry chr -> fmap ([chr] <>) (findEdit f parser) 
    EOF -> return [] 



-- endStream :: (Stream s m t, Show t) => ParsecT s u m (Either ScrapeFail a)
-- endStream = try (eof) >> (return $ Left Eof)

    
-- return (x:xs)

-- | We can define Edit to be a string because we know it will turn back into one
data StreamEditCase = EOF
                    | Carry Char
                    | Edit String


-- findSome = undefined
-- findSomeSame = findSomeSameEl



baseParser :: Stream s m Char => ParsecT s u m a -> ParsecT s u m (Either ScrapeFail a)
baseParser parser = fmap Right parser

givesNothing :: Stream s m Char => ParsecT s u m (Either ScrapeFail a) 
givesNothing = Left NonMatch <$ anyChar

endStream :: (Stream s m t, Show t) => ParsecT s u m (Either ScrapeFail a)
endStream = try (eof) >> (return $ Left Eof)




-- | Just since do we really care about non matches?
findSomeHTMLNaive :: Stream s Identity Char => Parsec s () a -> s -> (Maybe [a])
findSomeHTMLNaive parser text =
  let parser' = findNaive parser  
  in 
    case parse parser' "from html:add-in URL soon" text of
      Left _ -> Nothing 
      Right maybe_A -> maybe_A

findSomeHTML :: Stream s Identity Char => Parsec s () a -> s -> Either ParseError (Maybe [a])
findSomeHTML parser text =
  let parser' = findNaive parser  
  in parse parser' "from html at this url: <unimplemented - derp>" text

-- findFirst :: ParsecT s u m a -> Text -> Maybe a 
-- findFirst = undefined

-- findAllHtml :: ParsecT s u m a -> Text -> Maybe a 
-- findAllHtml = undefined
-- | My findAll' function design / runParserOnHtml 
  --use Maybe instead of Either to toss failure
  --case [] -> Nothing

-- | so it returns :: Maybe [a] = Just [a] | Nothing
  -- which will be beautiful for modeling at high level from scrape result to scrape result

-- | I also really need to implement non-zero, non-ending predicate inner function
-- | like nonZeroSep https://hackage.haskell.org/package/replace-megaparsec-1.4.4.0/docs/src/Replace.Megaparsec.html#sepCap

-- | NOTE: I can replace manyTill_ with anyTill from Replace.Megaparsec


-- within :: m a -> m a -> m a
-- within ma mb = do
--   x <- do
--     ma 
--     y <- mb
    
--     return mb 





-- -- Mutually exclusive/non-overlapping patterns 
-- findAll' :: ParsecT s u m a -> ParsecT s u m [a]
-- findAll' parser = do
--   x <- skipManyTill anyChar parser <|> return []
--   xs <- findAll' parser
--   return (x : xs)


    
        
findAllBetween = undefined



-- | Use with constructed for parsing datatype 
buildSequentialElemsParser :: ParsecT s u m [a]
buildSequentialElemsParser = undefined
-- | to be applied to inner text of listlike elem


-- findOnChangeInput :: ParsecT s u m (Elem' a)
-- findOnChangeInput = undefined
-- eg : <select id="s-lg-sel-subjects" name="s-lg-sel-subjects" class="form-control" data-placeholder="All Subjects" onchange="springSpace.publicObj.filterAzBySubject(jQuery(this).val(), 3848);">


-- | Rewrite to being any pattern "a"

-- -- | Note: this isnt necessarily deprecated but just useful for when we want to find many of some pattern
-- -- | that doesnt need to exist right after the previous successful match
-- {-# DEPRECATED findSomeSameEl "need manytill out and useful for find, findAll" #-}
-- findSomeSameEl :: (Stream s m Char, ShowHTML a)
--                => Maybe (ParsecT s u m a)
--                -> Maybe [Elem]
--                -> [(String, Maybe String)]
--                -> ParsecT s u m [TreeHTML a]
-- findSomeSameEl matchh elemOpts attrsSubset = do
--   -- (_, treeH) <- manyTill_ (anyChar) (try $ treeElemParser elemOpts matchh attrsSubset)
--   treeH <- treeElemParser elemOpts matchh attrsSubset
--   treeHs <- findMore matchh treeH
--   case treeHs of
--     [] -> parserFail "no matches" -- by definition: this func should return at least 1 copy 
--     _ -> return (treeH : treeHs)
--   where
--     findMore :: (Stream s m Char, ShowHTML a) =>
--                 Maybe (ParsecT s u m a)
--              -> TreeHTML a
--              -> ParsecT s u m [TreeHTML a]    
--     findMore matchh treeH = do
--       treeH' <- --( fmap (:[]) (skipManyTill anyChar (try $ findSameTreeH matchh treeH) )  )
--                 (do
--                     -- note: using skipManyTill VIOLATES expectations of this functions use
--                     -- this is gonna return something like 19 <a></a> tags since it is not
--                     -- in any way required for the congruent elements to be neighbours 
                    
--                   x <- skipManyTill anyChar (try $ findSameTreeH matchh treeH)
--                   return (x:[])
--                 )
--                 <|> return []
--       case treeH' of
--         [] -> return []
--         _ -> fmap ((treeH:[]) <>) $ findMore matchh treeH -- TreeHTML : ParsecT s u m [TreeHTML]