{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeOperators #-} module Text.XML.Selectors.Parsers.JQ ( jq , jqFile , jqFile' , jqString , jqText , jqString' , jqText' , errorBundlePretty ) where import Text.Megaparsec import Text.Megaparsec.Char import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.IO as Text import Text.XML.Selectors.Types import Text.XML (Name) import Data.String (IsString, fromString) import Data.Char (isAlphaNum, isDigit) jq :: (IsString (Tokens s), Stream s, Token s ~ Char) => String -- ^ Name of source file -> s -- ^ Input stream -> Either (ParseErrorBundle s Text) Selector jq = parse jqSelector jqString :: String -- ^ Input stream -> Either (ParseErrorBundle String Text) Selector jqString = jq "" jqString' :: String -- ^ Input stream -> Selector jqString' = either (error . errorBundlePretty) id . jq "" jqText :: Text -- ^ Input stream -> Either (ParseErrorBundle Text Text) Selector jqText = jq "" jqText' :: Text -- ^ Input stream -> Selector jqText' = either (error . errorBundlePretty) id . jq "" jqFile :: FilePath -> IO (Either (ParseErrorBundle Text Text) Selector) jqFile fn = do jq fn <$> Text.readFile fn jqFile' :: FilePath -> IO Selector jqFile' fn = jqFile fn >>= either (fail . errorBundlePretty) return instance ShowErrorComponent Text where showErrorComponent = Text.unpack jqSelector :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector jqSelector = space *> choices <* space <* eof choices :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector choices = do xs <- item `sepBy` (space *> char ',' <* space) case xs of [] -> return None [x] -> return x _ -> return $ Choice xs item :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector item = do what <- option Any (anyElem <|> try elemName) qualifiers <- many qualifier mcont <- optional continuation let self = mconcat (what : qualifiers) case mcont of Nothing -> pure self Just cont -> pure $ self <> cont anyElem :: (Stream s, Token s ~ Char) => Parsec Text s Selector anyElem = char '*' *> pure Any elemName :: (Stream s, Token s ~ Char) => Parsec Text s Selector elemName = Elem <$> name name :: (IsString n, Stream s, Token s ~ Char) => Parsec Text s n name = fromString <$> some nameChar nameChar :: (Stream s, Token s ~ Char) => Parsec Text s Char nameChar = satisfy isNameChar isNameChar :: Char -> Bool isNameChar c = isAlphaNum c || (c `elem` ['_', '-']) continuation :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector continuation = choice [ child , sibling , nextSibling , descendant ] descendant :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector descendant = try space1 *> ((Descendant <>) <$> item) child :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector child = try (space *> char '>' *> space) *> ((Child <>) <$> item) sibling :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector sibling = try (space *> char '~' *> space) *> ((Sibling <>) <$> item) nextSibling :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector nextSibling = try (space *> char '+' *> space) *> ((NextSibling <>) <$> item) qualifier :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector qualifier = choice [ classAttrib , idAttrib , attribSelector , firstChild , nthChild , nthLastChild , having , not_ ] classAttrib :: (Stream s, Token s ~ Char) => Parsec Text s Selector classAttrib = do char '.' value <- name pure $ Attrib (AttribContainsWord "class" value) idAttrib :: (Stream s, Token s ~ Char) => Parsec Text s Selector idAttrib = do char '#' value <- name pure $ Attrib (AttribIs "id" value) attribSelector :: (Stream s, Token s ~ Char) => Parsec Text s Selector attribSelector = do char '[' aname <- name mop <- optional attribOp asel <- case mop of Nothing -> pure $ AttribExists aname Just op -> op aname <$> (quotedStr <|> name) char ']' pure $ Attrib asel attribOp :: (Stream s, Token s ~ Char) => Parsec Text s (Name -> Text -> AttribSelector) attribOp = choice [ AttribIs <$ try (char '=') , AttribIsNot <$ try (char '!' >> char '=') , AttribStartsWith <$ try (char '^' >> char '=') , AttribEndsWith <$ try (char '$' >> char '=') , AttribContains <$ try (char '*' >> char '=') , AttribContainsWord <$ try (char '~' >> char '=') , AttribContainsPrefix <$ try (char '|' >> char '=') ] quotedStr :: (IsString v, Stream s, Token s ~ Char) => Parsec Text s v quotedStr = do quoteSym <- char '"' <|> char '\'' val <- many ((char '\\' *> satisfy (const True)) <|> (satisfy (/= quoteSym))) _ <- char quoteSym pure $ fromString val firstChild :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector firstChild = FirstChild <$ try (string ":first-child") <* notFollowedBy nameChar lastChild :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector lastChild = LastChild <$ try (string ":last-child") <* notFollowedBy nameChar nthChild :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector nthChild = do NthChild <$> (try (string ":nth-child") *> char '(' *> space *> positiveInt <* space <* char ')') nthLastChild :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector nthLastChild = do NthChild . negate <$> (try (string ":nth-last-child") *> char '(' *> space *> positiveInt <* space <* char ')') positiveInt :: (Stream s, Token s ~ Char) => Parsec Text s Int positiveInt = read <$> some digitChar having :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector having = try (string ":has") *> space *> char '(' *> (Having <$> item) <* char ')' not_ :: (IsString (Tokens s), Stream s, Token s ~ Char) => Parsec Text s Selector not_ = try (string ":not") *> space *> char '(' *> (Not <$> item) <* char ')'