Attoparsec /= версия stringCI
Я анализирую файлы robots.txt и написал анализатор для успешного анализа "правильно сформированного" файла robots.txt. Я был в состоянии настроить синтаксический анализатор, чтобы пропустить строки, которые начинаются с символа (например, # или / для комментариев), но только с использованием inClass "#/"
,
Одна проблема, которую я не смог решить, это пропустить строку, если она НЕ содержит строку, которую я хочу сопоставить.
User-agent: *
Disallow: /includes/
Disallow: /misc/
Disallow: /modules/
Doesn't belong here
Disallow: /profiles/
Disallow: /scripts/
Disallow: /themes/
Я сначала попытался сопоставить, используя:
satisfy (notInClass "DdUu") *> skipWhile (not . isEndOfLine)
И я подумал, что если я сделаю это таким образом, это отменит мою потребность в конкретном парсере строк комментариев, поскольку хэши или слэши не попадают в класс символов. Проблема в том, что это не работает.
Я также понимаю, что в любом случае это не сработало бы, потому что это не решало бы совпадение для чего-то вроде "Запретить" против "Не разрешать".
Вот код синтаксического анализа (без кода пропуска комментариев, это работает только для правильно сформированного файла robots.txt):
{-# LANGUAGE OverloadedStrings, RankNTypes #-}
import Prelude hiding (takeWhile)
import Control.Applicative hiding (many)
import Data.Char
import Data.Text as T hiding (toLower)
import Data.Text.Encoding as E
import Control.Monad
import Data.Attoparsec.ByteString
import qualified Data.Attoparsec.Char8 as AC
import Data.Array.Unboxed
import Data.ByteString as B hiding (takeWhile)
import qualified Data.ByteString.Internal as BI
import Data.Word (Word8)
type RuleMap = [(ByteString, ByteString)]
-- newtype for indexable ua
newtype UserAgent = UserAgent { unUA :: ByteString }
deriving (Eq, Ord, Show)
data RuleSet = RuleSet
{ userAgent :: UserAgent,
rules :: RuleMap }
deriving (Eq, Ord, Show)
main = do
r <- B.readFile "/Users/ixmatus/Desktop/robots.txt"
print $ parse (many1 parseUABlock) r
stripper = E.encodeUtf8 . T.strip . E.decodeUtf8
isNotEnd = not . AC.isEndOfLine
-- | Catch all character matching, basically
matchALL :: Word8 -> Bool
matchALL = inClass ":/?#[]@!$&'()*%+,;=.~a-zA-Z0-9 _-"
-- | @doParse@ Run the parser, complete the partial if the end of the stream has
-- a newline with an empty string
doParse :: ByteString -> [RuleSet]
doParse cont =
case parse (many1 parseUABlock) cont of
Done _ set -> set
Partial f -> handlePartial (f B.empty)
Fail {} -> []
-- | @handlePartial@ Handle a partial with empty string by simply
-- returning the last completion
handlePartial :: forall t a. IResult t [a] -> [a]
handlePartial (Done _ r) = r
handlePartial (Fail {}) = []
-- | @parseUABlock@ Parse a user-agent and rules block
parseUABlock = do
ua <- parseUACol *> uA
rulez <- many1 parseRules
return RuleSet { userAgent = UserAgent ua,
rules = rulez }
-- | @matchUACol@ Parse the UA column and value taking into account
-- possible whitespace craziness
parseUACol = AC.skipSpace
*> AC.stringCI "User-Agent"
<* AC.skipSpace
*> AC.char8 ':'
*> AC.skipSpace
uA = do
u <- takeWhile1 isNotEnd
return (stripper u)
-- | @parseRules@ Parse the directives row
parseRules = (,) <$> parseTransLower
<*> directiveRule
directiveRule = do
rule <- takeWhile1 matchALL <* many1 AC.endOfLine
return (stripper rule)
parseTransLower = do
res <- parseDirectives <* AC.skipSpace
return (lowercase res)
ctypeLower = listArray (0,255) (Prelude.map (BI.c2w . toLower) ['\0'..'\255']) :: UArray Word8 Word8
lowercase = B.map (\x -> ctypeLower!x)
directives = AC.stringCI "Disallow" <|> AC.stringCI "Allow"
-- | @parseDirectives@ Parse the directive column and any possibly
-- funny whitespace
parseDirectives = AC.skipSpace
*> directives -- <|> AC.stringCI "Crawl-delay" <|> AC.stringCI "Sitemap")
<* AC.skipSpace
<* AC.char8 ':'
1 ответ
Рассмотрим этот подход.
Определение:
data RobotsDirective = RobotsDirective String String
Это представляет разобранную директиву в файле robots.txt. Первая строка является директивой (т.е. UserAgent
, Allow
, Disallow
и т. д.) и вторая строка - это содержимое после двоеточия.
Теперь напишите парсер для RobotsDirective
:
parseRD :: Parser RobotsDirective
parseRD
будет искать имя директивы (которое должно содержать только буквы, цифры и тире и, возможно, подчеркивания), за которым следует двоеточие, за которым следует ноль или более не-новых символов. Игнорируйте пробел в случае необходимости. Если parseRD
находит такой шаблон, он создаст и вернет RobotsDirective
, В противном случае он пропустит одну строку символов и попытается снова.
Теперь, когда у вас есть парсер для RobotsDirective
Вы можете создать парсер для [RobotsDirective]
стандартным способом.
Этот парсер просто пропускает любую строку, которая не похожа на директиву, и это будет включать пустые строки, строки комментариев и строки, начинающиеся с Don't allow...
, Тем не менее, он может вернуть RobotsDirective
для строк, которые недопустимы в файле robots.txt, то есть:
foo: blah
вернусь RobotsDirective "foo" "blah"
, После того, как вы проанализировали файл robots.txt и получили список RobotsDirective
значения, просто просмотрите этот список и игнорируйте те, которые вас не интересуют.