tagsoup-ht 0.2 → 0.3
raw patch · 20 files changed
+1227/−488 lines, 20 filesdep +bytestringdep +containersdep +explicit-exceptiondep −mtldep ~basedep ~data-accessornew-component:exe:tagsoupspeed
Dependencies added: bytestring, containers, explicit-exception, old-time, transformers, utility-ht, xml-basic
Dependencies removed: mtl
Dependency ranges changed: base, data-accessor
Files
- example/Speed.hs +82/−0
- example/Validate.hs +10/−6
- src/Text/HTML/TagSoup/HT/Format.hs +90/−0
- src/Text/HTML/TagSoup/HT/HTMLChar.hs +0/−7
- src/Text/HTML/TagSoup/HT/Match.hs +0/−96
- src/Text/HTML/TagSoup/HT/Parser.hs +285/−116
- src/Text/HTML/TagSoup/HT/Parser/Combinator.hs +69/−34
- src/Text/HTML/TagSoup/HT/Parser/Custom.hs +26/−17
- src/Text/HTML/TagSoup/HT/Parser/MTL.hs +38/−17
- src/Text/HTML/TagSoup/HT/Parser/Status.hs +3/−3
- src/Text/HTML/TagSoup/HT/Parser/Stream.hs +68/−0
- src/Text/HTML/TagSoup/HT/ParserNM.hs +27/−15
- src/Text/HTML/TagSoup/HT/Position.hs +0/−93
- src/Text/HTML/TagSoup/HT/PositionTag.hs +69/−13
- src/Text/HTML/TagSoup/HT/Process.hs +103/−0
- src/Text/HTML/TagSoup/HT/Tag.hs +225/−51
- src/Text/HTML/TagSoup/HT/Tag/Match.hs +89/−0
- src/Text/HTML/TagSoup/HT/Test.hs +18/−7
- src/Text/HTML/TagSoup/HT/Utility.hs +0/−4
- tagsoup-ht.cabal +25/−9
+ example/Speed.hs view
@@ -0,0 +1,82 @@+module Main where++import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.HTML.TagSoup.HT.Parser as Parser+import qualified Text.HTML.TagSoup.HT.Parser.Stream as Str+import qualified Text.XML.Basic.Name.MixedCase as Name++import System.Time (getClockTime, diffClockTimes, tdSec, tdPicosec, )++import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BL+++measureTime :: String -> IO () -> IO ()+measureTime name act =+ do putStr (name++": ")+ timeA <- getClockTime+ act+ timeB <- getClockTime+ let td = diffClockTimes timeB timeA+ print (fromIntegral (tdSec td) ++ fromInteger (tdPicosec td) * 1e-12 :: Double)++number :: Int+number = 100000++main :: IO ()+main =+ do measureTime "String" $ print $+ (last $ Parser.runSoup $ concat $ replicate number "<li>item</li>" :: Tag.T Name.T String)+ measureTime "ByteString.Strict" $ print $+ (last $ Parser.runSoup $ BS.concat $ replicate number $ BS.pack "<li>item</li>" :: Tag.T Name.T String)+ measureTime "ByteString.Lazy" $ print $+ (last $ Parser.runSoup $ BL.concat $ replicate number $ BL.pack "<li>item</li>" :: Tag.T Name.T String)++ measureTime "Pointer.Strict" $ print $+ (last $ Parser.runSoup $ Str.pointerFromByteStringStrict $ BS.concat $ replicate number $ BS.pack "<li>item</li>" :: Tag.T Name.T String)+ measureTime "Pointer.Lazy" $ print $+ (last $ Parser.runSoup $ Str.pointerFromByteStringLazy $ BL.concat $ replicate number $ BL.pack "<li>item</li>" :: Tag.T Name.T String)++{-+Results without inlining on euler:++String: Close "li"+2.182197+ByteString.Strict: Close "li"+2.704024+ByteString.Lazy: Close "li"+3.203038++These figures remain, when Stream methods are inlined.+++Results without inlining on anubis:+String: Close "li"+3.507383+ByteString.Strict: Close "li"+4.406978+ByteString.Lazy: Close "li"+5.266979+Pointer.Strict: Close "li"+3.37898+Pointer.Lazy: Close "li"+3.185808+++The figures become worse, when MTL and Combinator functions are inlined, too:+String: Close "li"+3.56803+ByteString.String: Close "li"+4.314474+ByteString.Lazy: Close "li"+4.855984++With the main parser functions inlined we get:+tring: Close "li"+2.951519+ByteString.String: Close "li"+2.910444+ByteString.Lazy: Close "li"+3.046311+-}
example/Validate.hs view
@@ -5,9 +5,10 @@ module Main where import Text.HTML.TagSoup.HT.Parser-import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position import qualified Text.HTML.TagSoup.HT.PositionTag as PosTag import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.XML.Basic.Name.MixedCase as Name import System.Environment (getArgs) @@ -16,16 +17,19 @@ validate :: FilePath -> String -> String validate fileName input =- let tags :: [PosTag.T Char]+ let tags :: [PosTag.T Name.T String] tags = runSoupWithPositionsName fileName input warnings =- mapMaybe (\(pos,tag) -> fmap ((,) pos) $ Tag.maybeWarning tag) tags- in unlines $ map (\(pos,msg) -> Position.toReportText pos ++ " " ++ msg) warnings+ mapMaybe+ (\(PosTag.Cons pos tag) ->+ fmap (\msg -> Position.toReportText pos ++ " " ++ msg) $+ Tag.maybeWarning tag)+ tags+ in unlines warnings validateIO :: FilePath -> IO () validateIO fileName =- do text <- readFile fileName- putStrLn (validate fileName text)+ putStrLn . validate fileName =<< readFile fileName main :: IO ()
+ src/Text/HTML/TagSoup/HT/Format.hs view
@@ -0,0 +1,90 @@+{-|+Convert a tag soup to its text representation+respecting various conventions for merging open and close tags.+-}+module Text.HTML.TagSoup.HT.Format (+ xml, xmlCondensed, html, xhtml, htmlOrXhtml,+ ) where++import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.HTML.Basic.Tag as TagH+import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Format as Fmt++import Data.List.HT (viewL, )+import Data.Maybe (fromMaybe, )+import Control.Monad (guard, )+++{-+*Text.HTML.TagSoup.HT.Format> flip xml "" $ (Text.HTML.TagSoup.HT.Parser.runSoup "<?xml version=1.0 ?>" :: [Tag.T Text.XML.Basic.Name.LowerCase.T String])+-}+++{- |+All tags are formatted as they are.+-}+xml :: (Eq name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ [Tag.T name string] -> ShowS+xml = Fmt.many Fmt.run+++{- |+Adjacent corresponding open and close tags are merged to a self-closing tag.+E.g. @<a></a>@ becomes @<a/>@.+-}+xmlCondensed :: (Eq name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ [Tag.T name string] -> ShowS+xmlCondensed = xmlCondensedGen (==)++{- |+All tags that are defined being self-closing by the HTML standard+are formatted only as open tag.+E.g. @<br>@.+-}+html :: (Ord name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ [Tag.T name string] -> ShowS+html =+ Fmt.many Fmt.run .+ filter (maybe True (not . TagH.isEmpty) . Tag.maybeClose)++{- |+All tags that are defined being self-closing by the XHTML standard+are formatted as self-closing open tag.+E.g. @<br/>@.+-}+xhtml :: (Ord name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ [Tag.T name string] -> ShowS+xhtml =+ xmlCondensedGen+ -- e.g. <div></div> must not be merged to <div/>+ (\nameOpen nameClose ->+ nameOpen==nameClose && TagH.isEmpty nameOpen)++{- |+If the first tag is @<?xml ...?>@ then format in XHTML style,+else in HTML style.+-}+htmlOrXhtml :: (Ord name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ [Tag.T name string] -> ShowS+htmlOrXhtml tags =+ fromMaybe (html tags) $+ do (tag,_) <- viewL tags+ (name,_) <- Tag.maybeProcessing tag+ guard (Name.match "xml" name)+ return (xhtml tags)+++xmlCondensedGen :: (Eq name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ (Tag.Name name -> Tag.Name name -> Bool) ->+ [Tag.T name string] -> ShowS+xmlCondensedGen check =+ let recourse (Tag.Open nameOpen attrs : Tag.Close nameClose : ts) =+ (if check nameOpen nameClose+ then Tag.formatOpen True nameOpen attrs+ else Tag.formatOpen False nameOpen attrs .+ Tag.formatClose nameClose)+ . recourse ts+ recourse (t : ts) = Fmt.run t . recourse ts+ recourse [] = id+ in recourse
− src/Text/HTML/TagSoup/HT/HTMLChar.hs
@@ -1,7 +0,0 @@-module Text.HTML.TagSoup.HT.HTMLChar where--data T =- Char Char- | NumericRef Int- | NamedRef String- deriving (Show, Eq)
− src/Text/HTML/TagSoup/HT/Match.hs
@@ -1,96 +0,0 @@-module Text.HTML.TagSoup.HT.Match where--import qualified Text.HTML.TagSoup.HT.Tag as Tag---ignore :: a -> Bool-ignore _ = True----- | match an opening tag-open :: (String -> Bool) -> ([Tag.Attribute char] -> Bool) -> Tag.T char -> Bool-open pName pAttrs (Tag.Open name attrs) =- pName name && pAttrs attrs-open _ _ _ = False---- | match an closing tag-close :: (String -> Bool) -> Tag.T char -> Bool-close pName (Tag.Close name) = pName name-close _ _ = False---- | match a text-text :: ([char] -> Bool) -> Tag.T char -> Bool-text p (Tag.Text str) = p str-text _ _ = False--comment :: (String -> Bool) -> Tag.T char -> Bool-comment p (Tag.Comment str) = p str-comment _ _ = False--special :: (String -> Bool) -> (String -> Bool) -> Tag.T char -> Bool-special pType pInfo (Tag.Special typ info) = pType typ && pInfo info-special _ _ _ = False----- | match a opening tag's name literally-openLit :: String -> ([Tag.Attribute char] -> Bool) -> Tag.T char -> Bool-openLit name = open (name==)---- | match a closing tag's name literally-closeLit :: String -> Tag.T char -> Bool-closeLit name = close (name==)--openAttrLit :: (Eq char) =>- String -> Tag.Attribute char -> Tag.T char -> Bool-openAttrLit name attr =- openLit name (anyAttrLit attr)--{- |-Match a tag with given name, that contains an attribute-with given name, that satisfies a predicate.-If an attribute occurs multiple times,-all occurrences are checked.--}-openAttrNameLit :: String -> String -> ([char] -> Bool) -> Tag.T char -> Bool-openAttrNameLit tagName attrName pAttrValue =- openLit tagName- (anyAttr (\(name,value) -> name==attrName && pAttrValue value))----- | Check if the 'Tag.T' is 'Tag.Open' and matches the given name-openNameLit :: String -> Tag.T char -> Bool-openNameLit name = openLit name ignore---- | Check if the 'Tag.T' is 'Tag.Close' and matches the given name-closeNameLit :: String -> Tag.T char -> Bool-closeNameLit name = closeLit name-----anyAttr :: ((String,[char]) -> Bool) -> [Tag.Attribute char] -> Bool-anyAttr = any--anyAttrName :: (String -> Bool) -> [Tag.Attribute char] -> Bool-anyAttrName p = any (p . fst)--anyAttrValue :: ([char] -> Bool) -> [Tag.Attribute char] -> Bool-anyAttrValue p = any (p . snd)---anyAttrLit :: (Eq char) => (String,[char]) -> [Tag.Attribute char] -> Bool-anyAttrLit attr = anyAttr (attr==)--anyAttrNameLit :: String -> [Tag.Attribute char] -> Bool-anyAttrNameLit name = anyAttrName (name==)--anyAttrValueLit :: (Eq char) => [char] -> [Tag.Attribute char] -> Bool-anyAttrValueLit value = anyAttrValue (value==)---{--getTagContent :: String -> ([Tag.Attribute char] -> Bool) -> [Tag.T char] -> [Tag.T char]-getTagContent name pAttrs =- takeWhile (not . tagCloseLit name) . drop 1 .- head . sections (tagOpenLit name pAttrs)--}
src/Text/HTML/TagSoup/HT/Parser.hs view
@@ -14,26 +14,40 @@ import Text.HTML.TagSoup.HT.Parser.Combinator (allowFail, withDefault, char, dropSpaces, getPos,- many, many1, many1Satisfy, readUntil,+ many, manyNull, many0toN, many1toN,+ many1Satisfy, readUntil, satisfy, string,- emit, mfix, )+ emit, modifyEmission, ) import qualified Text.HTML.TagSoup.HT.Parser.Combinator as Parser -import qualified Text.HTML.TagSoup.HT.Tag as Tag import qualified Text.HTML.TagSoup.HT.PositionTag as PosTag-import qualified Text.HTML.TagSoup.HT.Position as Position-import qualified Text.HTML.TagSoup.HT.HTMLChar as HTMLChar+import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.XML.Basic.Position as Position+import qualified Text.HTML.Basic.Character as HTMLChar+import qualified Text.XML.Basic.ProcessingInstruction as PI+import qualified Text.XML.Basic.Attribute as Attr+import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Tag as TagName -import qualified Text.HTML.TagSoup.Entity as HTMLEntity+import qualified Text.HTML.TagSoup.HT.Parser.Stream as Stream +import qualified Text.HTML.Basic.Entity as HTMLEntity++import qualified Control.Monad.Exception.Synchronous as Exc++import Control.Monad.Fix (MonadFix, mfix, ) import Control.Monad (mplus, msum, when, liftM, ) --- import Control.Monad.Identity (Identity(..), )+import Data.Monoid (Monoid, mempty, mconcat, ) -import Data.Char (isAlphaNum, isAscii, isDigit, chr, )+import qualified Data.Map as Map++import Data.Tuple.HT (mapSnd, )+import Data.Char (isAlphaNum, isAscii, chr, ord, ) import Data.Maybe (fromMaybe, ) +-- import qualified Numeric -- * run parser in several ways@@ -42,8 +56,10 @@ Parse a single tag, throws an error if there is a syntax error. This is useful for parsing a match pattern. -}-runTag :: (CharType char, Show char) =>- String -> Tag.T char+runTag ::+ (Stream.C source, StringType sink, Show sink,+ Name.Attribute name, Name.Tag name, Eq name, Show name) =>+ source -> Tag.T name sink runTag str = let tags = fromMaybe (error "runTag: no parse at all") $@@ -52,50 +68,63 @@ "runTag: parsing results in\n" ++ unlines (map show tags) in case tags of- [(_,tag)] ->- if Tag.isWarning tag- then makeError- else tag+ [postag] ->+ let tag = PosTag.tag_ postag+ in if Tag.isWarning tag+ then makeError+ else tag _ -> makeError {- | Parse the inner of a single tag. That is, @runTag \"\<bla\>\"@ is the same as @runInnerOfTag \"\<bla\>\"@. -}-runInnerOfTag :: (CharType char, Show char) =>- String -> Tag.T char+runInnerOfTag ::+ (StringType sink, Show sink,+ Name.Attribute name, Name.Tag name, Eq name, Show name) =>+ String -> Tag.T name sink runInnerOfTag str = runTag $ "<"++str++">" -runSoupWithPositionsName :: CharType char =>- FilePath -> String -> [PosTag.T char]+runSoupWithPositionsName ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name, Eq name) =>+ FilePath -> source -> [PosTag.T name sink] runSoupWithPositionsName fileName =+ PosTag.concatTexts . Parser.runIdentity .- Parser.write fileName (many parsePosTag >> return ())+ Parser.write fileName (manyNull parsePosTag) -- | Parse an HTML document to a list of 'Tag.T'. -- Automatically expands out escape characters.-runSoupWithPositions :: CharType char =>- String -> [PosTag.T char]+runSoupWithPositions ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name, Eq name) =>+ source -> [PosTag.T name sink] runSoupWithPositions =- Parser.runIdentity .- Parser.write "input" (many parsePosTag >> return ())+ runSoupWithPositionsName "input" -- | Like 'runSoupWithPositions' but hides source file positions.-runSoup :: CharType char => String -> [Tag.T char]-runSoup = map snd . runSoupWithPositions+runSoup ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name, Eq name) =>+ source -> [Tag.T name sink]+runSoup = map PosTag.tag_ . runSoupWithPositions -- * parser parts -type Parser char a = Parser.Full (PosTag.T char) a-type ParserEmit char a = Parser.Emitting (PosTag.T char) a+type Parser name source sink a = Parser.Full source (PosTag.T name sink) a+type ParserEmit name source sink a = Parser.Emitting source (PosTag.T name sink) a -parsePosTag :: CharType char => Parser char ()+parsePosTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name, Eq name) =>+ Parser name source sink () parsePosTag = do pos <- getPos mplus@@ -107,20 +136,21 @@ parseCloseTag pos : parseOpenTag pos : [])- (do emitTag pos (Tag.Text [fromChar '<'])+ (do emitTag pos (Tag.Text $ stringFromChar '<') emitWarning pos "A '<', that is not part of a tag. Encode it as < please.")) (parseText pos) -parseOpenTag :: CharType char => Position.T -> Parser char ()+parseOpenTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name) =>+ Position.T -> Parser name source sink () parseOpenTag pos = do name <- parseName allowFail $ do dropSpaces- mfix- (\attrs ->- emit (pos, Tag.Open name attrs) >>- many parseAttribute)+ emittingTag pos (Tag.Open name) $+ modifyEmission (restrictSoup 10) $ many parseAttribute withDefault (do closePos <- getPos string "/>"@@ -131,9 +161,11 @@ emitWarningWhen (not $ null junk) junkPos ("Junk in opening tag: \"" ++ junk ++ "\""))- ("Unterminated open tag \"" ++ name ++ "\"") ">")+ ("Unterminated open tag \"" ++ Name.toString name ++ "\"") ">") -parseCloseTag :: Position.T -> Parser char ()+parseCloseTag ::+ (Stream.C source, Name.Tag name) =>+ Position.T -> Parser name source sink () parseCloseTag pos = do char '/' name <- parseName@@ -146,9 +178,11 @@ emitWarningWhen (not $ null junk) junkPos ("Junk in closing tag: \"" ++ junk ++"\""))- ("Unterminated closing tag \"" ++ name ++"\"") ">"+ ("Unterminated closing tag \"" ++ Name.toString name ++"\"") ">" -parseSpecialTag :: Position.T -> Parser char ()+parseSpecialTag ::+ (Stream.C source, Name.Tag name) =>+ Position.T -> Parser name source sink () parseSpecialTag pos = do char '!' msum $@@ -156,80 +190,89 @@ allowFail $ readUntilTerm (\ cmt -> emitTag pos (Tag.Comment cmt)) "Unterminated comment" "-->") :- (do string "[CDATA["+ (do string TagName.cdataString allowFail $ readUntilTerm- (\ cdata -> emitTag pos (Tag.Special "[CDATA[" cdata))+ (\ cdata -> emitTag pos (Tag.Special (Name.fromString TagName.cdataString) cdata)) "Unterminated cdata" "]]>") :- (do name <- many1Satisfy isAlphaNum+ (do name <- parseName allowFail $ do dropSpaces readUntilTerm (\ info -> emitTag pos (Tag.Special name info))- ("Unterminated special tag \"" ++ name ++ "\"") ">") :+ ("Unterminated special tag \"" ++ Name.toString name ++ "\"") ">") : [] -parseProcessingTag :: CharType char => Position.T -> Parser char ()+parseProcessingTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name, Eq name) =>+ Position.T -> Parser name source sink () parseProcessingTag pos = do char '?' name <- parseName allowFail $ do dropSpaces- mfix- (\proc ->- emit (pos, Tag.Processing name proc) >>- if elem name ["xml", "xml-stylesheet"]- then- do attrs <- many parseAttribute- junkPos <- getPos- readUntilTerm- (\ junk ->- emitWarningWhen (not $ null junk) junkPos- ("Junk in processing info tag: \"" ++ junk ++ "\""))- ("Unterminated processing info tag \"" ++ name ++ "\"") "?>"- return $ Tag.KnownProcessing attrs- else readUntilTerm (return . Tag.UnknownProcessing)- "Unterminated processing instruction" "?>")- return ()+ emittingTag pos (Tag.Processing name) $+ if Name.matchAny ["xml", "xml-stylesheet"] name+ then+ do attrs <- many parseAttribute+ junkPos <- getPos+ readUntilTerm+ (\ junk ->+ emitWarningWhen (not $ null junk) junkPos+ ("Junk in processing info tag: \"" ++ junk ++ "\""))+ ("Unterminated processing info tag \"" ++ Name.toString name ++ "\"") "?>"+ return $ PI.Known attrs+ else readUntilTerm (return . PI.Unknown)+ "Unterminated processing instruction" "?>" -parseText :: CharType char => Position.T -> Parser char ()+parseText ::+ (Stream.C source, StringType sink) =>+ Position.T -> Parser name source sink () parseText pos =- mfix- (\ text ->- allowFail (emitTag pos (Tag.Text text)) >>- parseString1 ('<'/=))- >> return ()+ emittingTag pos Tag.Text (parseCharAsString (const True))+-- emittingTag pos Tag.Text (parseCharAsString ('<'/=))+-- emittingTag pos Tag.Text (parseString1 ('<'/=)) -parseAttribute :: CharType char => Parser char (Tag.Attribute char)+parseAttribute ::+ (Stream.C source, StringType sink, Name.Attribute name) =>+ Parser name source sink (Attr.T name sink) parseAttribute = parseName >>= \name -> allowFail $ do dropSpaces value <- withDefault (string "=" >> allowFail (dropSpaces >> parseValue))- (return [])+ (return mempty) dropSpaces- return (name, value)+ return $ Attr.Cons name value -parseName :: Parser char String+parseName ::+ (Stream.C source, Name.C pname) =>+ Parser name source sink pname parseName =+ liftM Name.fromString $ -- we must restrict to ASCII alphanum characters in order to exclude umlauts- many1Satisfy (\c -> isAlphaNum c && isAscii c || c `elem` "_-:")+ many1Satisfy (\c -> isAlphaNum c && isAscii c || c `elem` "_-.:") -parseValue :: CharType char => ParserEmit char [char]+parseValue ::+ (Stream.C source, StringType sink) =>+ ParserEmit name source sink sink parseValue = (msum $ parseQuoted "Unterminated doubly quoted value string" '"' : parseQuoted "Unterminated singly quoted value string" '\'' : []) `withDefault`- parseUnquotedValue+ parseUnquotedValueAsString -parseUnquotedValueChar :: ParserEmit Char String+parseUnquotedValueChar ::+ (Stream.C source) =>+ ParserEmit name source String String parseUnquotedValueChar = let parseValueChar = do pos <- getPos- str <- parseChar (not . flip elem " >\"\'")+ str <- parseUnicodeChar (not . flip elem " >\"\'") let wrong = filter (not . isValidValueChar) str allowFail $ emitWarningWhen (not (null wrong)) pos $@@ -237,24 +280,35 @@ return str in liftM concat $ many parseValueChar -parseUnquotedValueHTMLChar :: ParserEmit HTMLChar.T [HTMLChar.T]+parseUnquotedValueHTMLChar ::+ (Stream.C source) =>+ ParserEmit name source [HTMLChar.T] [HTMLChar.T] parseUnquotedValueHTMLChar = let parseValueChar = do pos <- getPos hc <- parseHTMLChar (not . flip elem " >\"\'")- case hc of- HTMLChar.Char c ->- allowFail $- emitWarningWhen (not (isValidValueChar c)) pos $- "Illegal characters in unquoted value: '" ++ c : "'"- _ -> return ()+ {- We do the check after each parseHTMLChar+ and not after (many parseValueChar)+ in order to correctly interleave warnings. -}+ allowFail $ mapM_ (checkUnquotedChar pos) hc return hc- in many parseValueChar+ in liftM concat $ many parseValueChar +checkUnquotedChar :: Position.T -> HTMLChar.T -> ParserEmit name source sink ()+checkUnquotedChar pos x =+ case x of+ HTMLChar.Unicode c ->+ emitWarningWhen (not (isValidValueChar c)) pos $+ "Illegal characters in unquoted value: '" ++ c : "'"+ _ -> return ()++ isValidValueChar :: Char -> Bool isValidValueChar c = isAlphaNum c || c `elem` "_-:." -parseQuoted :: CharType char => String -> Char -> Parser char [char]+parseQuoted ::+ (Stream.C source, StringType sink) =>+ String -> Char -> Parser name source sink sink parseQuoted termMsg quote = char quote >> (allowFail $@@ -270,7 +324,8 @@ in 'mfix' in order to emit a tag, where some information is read later. -} readUntilTerm ::- (String -> ParserEmit char a) -> String -> String -> ParserEmit char a+ (Stream.C source) =>+ (String -> ParserEmit name source sink a) -> String -> String -> ParserEmit name source sink a readUntilTerm generateTag termWarning termPat = do ~(termFound,str) <- readUntil termPat result <- generateTag str@@ -281,42 +336,87 @@ class CharType char where fromChar :: Char -> char- parseString :: (Char -> Bool) -> ParserEmit char [char]- parseString1 :: (Char -> Bool) -> Parser char [char]- parseUnquotedValue :: ParserEmit char [char]+ parseChar :: (Stream.C source) => (Char -> Bool) -> Parser name source sink [char]+ parseUnquotedValue :: (Stream.C source) => ParserEmit name source [char] [char] instance CharType Char where fromChar = id- parseString p = liftM concat $ many (parseChar p)- parseString1 p = liftM concat $ many1 (parseChar p)+ parseChar = parseUnicodeChar parseUnquotedValue = parseUnquotedValueChar instance CharType HTMLChar.T where- fromChar = HTMLChar.Char- parseString p = many (parseHTMLChar p)- parseString1 p = many1 (parseHTMLChar p)+ fromChar = HTMLChar.Unicode+ parseChar = parseHTMLChar parseUnquotedValue = parseUnquotedValueHTMLChar -parseChar :: (Char -> Bool) -> Parser char String-parseChar p =+class Monoid sink => StringType sink where+ stringFromChar :: Char -> sink+ parseCharAsString ::+ (Stream.C source) =>+ (Char -> Bool) -> Parser name source sink sink+ parseUnquotedValueAsString ::+ (Stream.C source) =>+ ParserEmit name source sink sink++instance CharType char => StringType [char] where+ stringFromChar c = [fromChar c]+ parseCharAsString = parseChar+ parseUnquotedValueAsString = parseUnquotedValue+++parseString ::+ (Stream.C source, StringType sink) =>+ (Char -> Bool) -> ParserEmit name source sink sink+parseString p = liftM mconcat $ many (parseCharAsString p)++{-+parseString1 ::+ (Stream.C source, StringType sink) =>+ (Char -> Bool) -> Parser name source sink sink+parseString1 p = liftM mconcat $ many1 (parseCharAsString p)+-}++++parseUnicodeChar ::+ (Stream.C source) =>+ (Char -> Bool) -> Parser name source sink String+parseUnicodeChar p = do pos <- getPos x <- parseHTMLChar p- let returnChar c = return $ c:[]- allowFail $- case x of- HTMLChar.Char c -> returnChar c- HTMLChar.NumericRef num -> returnChar (chr num)- HTMLChar.NamedRef name ->- maybe- (let refName = '&':name++";"- in emitWarning pos ("Unknown HTML entity " ++ refName) >>- return refName)- (returnChar . chr)- (lookup name HTMLEntity.htmlEntities)+ allowFail $ liftM concat $+ mapM (htmlCharToString pos) x +htmlCharToString ::+ Position.T -> HTMLChar.T -> ParserEmit name source sink String+htmlCharToString pos x =+ let returnChar c = return $ c:[]+ in case x of+ HTMLChar.Unicode c -> returnChar c+ HTMLChar.CharRef num -> returnChar (chr num)+ HTMLChar.EntityRef name ->+ maybe+ (let refName = '&':name++";"+ in emitWarning pos ("Unknown HTML entity " ++ refName) >>+ return refName)+ returnChar+ (Map.lookup name HTMLEntity.mapNameToChar) -parseHTMLChar :: (Char -> Bool) -> Parser char HTMLChar.T+{- |+Only well formed entity references are interpreted as single HTMLChars,+whereas ill-formed entity references are interpreted as sequence of unicode characters without special meaning.+E.g. "& ;" is considered as plain "& ;",+and only "&" is considered an escaped ampersand.+It is a very common error in HTML documents to not escape an ampersand.+With the interpretation used here,+those ampersands are left as they are.++At most one warning can be emitted.+-}+parseHTMLChar ::+ (Stream.C source) =>+ (Char -> Bool) -> Parser name source sink [HTMLChar.T] parseHTMLChar p = do pos <- getPos c <- satisfy p@@ -326,22 +426,91 @@ withDefault (do ent <- mplus- (char '#' >>- liftM (HTMLChar.NumericRef . read) (many1Satisfy isDigit))- (liftM HTMLChar.NamedRef $ many1Satisfy isAlphaNum)+ (do char '#'+ digits <- allowFail $ many0toN 10 (satisfy isAlphaNum)+ -- exclude ';', '"', '<'+ -- include 'x'+ Exc.switch+ (\e ->+ allowFail (emitWarning pos ("Error in numeric entity: " ++ e)) >>+ return (map HTMLChar.fromUnicode ('&':'#':digits)))+ (return . (:[]) . HTMLChar.CharRef . ord)+ (HTMLEntity.numberToChar digits))+ (liftM ((:[]) . HTMLChar.EntityRef) $+ many1toN 10 (satisfy isAlphaNum)) char ';' return ent)+ (emitWarning pos "Non-terminated entity reference" >>+ return [HTMLChar.Unicode '&'])+ else return [HTMLChar.Unicode c]++{-+readHex :: (Num a) => String -> a+readHex str =+ case Numeric.readHex str of+ [(n,"")] -> n+ _ -> error "readHex: no parse"++{-+We cannot emit specific warnings,+because the sub-parsers simply fail+and then throw away the warnings.+-}+parseHTMLCharGenericWarning ::+ (Stream.C source) =>+ (Char -> Bool) -> Parser name source sink [HTMLChar.T]+parseHTMLCharGenericWarning p =+ do pos <- getPos+ c <- satisfy p+ allowFail $+ if c=='&'+ then+ withDefault+ (do ent <-+ mplus+ (char '#' >>+ liftM HTMLChar.CharRef+ (mplus+ (char 'x' >> liftM readHex (many1toN 8 (satisfy isHexDigit)))+ (liftM read (many1toN 10 (satisfy isDigit)))))+ (liftM HTMLChar.EntityRef $ many1toN 10 (satisfy isAlphaNum))+ char ';'+ return [ent]) (emitWarning pos "Ill formed entity" >>- return (HTMLChar.Char '&'))- else return (HTMLChar.Char c)+ return [HTMLChar.Unicode '&'])+ else return [HTMLChar.Unicode c]+-} -emitWarningWhen :: Bool -> Position.T -> String -> ParserEmit char ()+restrictSoup :: Int -> [PosTag.T name sink] -> [PosTag.T name sink]+restrictSoup n =+ uncurry (++) .+ mapSnd+ (\rest ->+ case rest of+ (PosTag.Cons pos _) : _ ->+ [PosTag.Cons pos (Tag.Warning "further warnings suppressed")]+ _ -> []) .+ splitAt n+++-- these functions have intentionally restricted types++emitWarningWhen :: Bool -> Position.T -> String -> ParserEmit name source sink () emitWarningWhen cond pos msg = when cond $ emitWarning pos msg -emitWarning :: Position.T -> String -> ParserEmit char ()+emitWarning :: Position.T -> String -> ParserEmit name source sink () emitWarning pos msg = emitTag pos (Tag.Warning msg) -emitTag :: Position.T -> Tag.T char -> ParserEmit char ()-emitTag = curry emit+emitTag :: Position.T -> Tag.T name sink -> ParserEmit name source sink ()+emitTag p t = emit (PosTag.cons p t)++emittingTag ::+ (MonadFix fail) =>+ Position.T ->+ (a -> Tag.T name sink) ->+ Parser.T source [PosTag.T name sink] fail a ->+ Parser.T source [PosTag.T name sink] fail ()+emittingTag pos f x =+ mfix (\r -> emit (PosTag.cons pos (f r)) >> x) >> return ()
src/Text/HTML/TagSoup/HT/Parser/Combinator.hs view
@@ -1,23 +1,24 @@ module Text.HTML.TagSoup.HT.Parser.Combinator ( Parser.T, Full, Emitting, Fallible, Plain,- char, dropSpaces, eof, getPos,- many, many1, many1Satisfy, manySatisfy, readUntil,+ char, dropSpaces, getPos,+ many, many1, manyNull, many1Null, many0toN, many1toN,+ many1Satisfy, manySatisfy, readUntil, satisfy, string,- emit,- eval, write, gets, mfix,+ emit, modifyEmission,+ eval, write, gets, withDefault, allowFail, allowEmit, Identity, runIdentity, ) where -import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position import qualified Text.HTML.TagSoup.HT.Parser.Status as Status+import qualified Text.HTML.TagSoup.HT.Parser.Stream as Stream -import Text.HTML.TagSoup.HT.Parser.Custom as Parser--- import Text.HTML.TagSoup.HT.Parser.MTL as Parser+-- import Text.HTML.TagSoup.HT.Parser.Custom as Parser+import Text.HTML.TagSoup.HT.Parser.MTL as Parser -import Control.Monad (liftM, liftM2, )-import Control.Monad.Fix (mfix, )+import Control.Monad (liftM, liftM2, guard, ) -- import Control.Monad.Trans (lift, ) import Data.Monoid (Monoid)@@ -26,21 +27,21 @@ -type Full w = Parser.T [w] Maybe-type Fallible = Parser.T () Maybe-type Emitting w = Parser.T [w] Identity-type Plain = Parser.T () Identity+type Full input w = Parser.T input [w] Maybe+type Fallible input = Parser.T input () Maybe+type Emitting input w = Parser.T input [w] Identity+type Plain input = Parser.T input () Identity write :: Monad fail =>- FilePath -> Parser.T [w] fail () -> String -> fail [w]+ FilePath -> Parser.T input [w] fail () -> input -> fail [w] write fileName p = liftM (\ ~(_,_,ws) -> ws) . run p . Status.Cons (Position.initialize fileName) eval :: Monad fail =>- FilePath -> Parser.T [w] fail a -> String -> fail a+ FilePath -> Parser.T input [w] fail a -> input -> fail a eval fileName p = liftM (\ ~(x,_,_) -> x) . run p .@@ -48,17 +49,14 @@ -eof ::- (Monoid output, Monad fail) =>- Parser.T output fail Bool-eof = gets (null . Status.source)- getPos :: (Monoid output, Monad fail) =>- Parser.T output fail Position.T+ Parser.T input output fail Position.T getPos = gets Status.sourcePos -satisfy :: Monoid output => (Char -> Bool) -> Parser.T output Maybe Char+satisfy ::+ (Monoid output, Stream.C input) =>+ (Char -> Bool) -> Parser.T input output Maybe Char satisfy p = do c <- nextChar if p c@@ -66,51 +64,88 @@ else fail "character not matched" -- | does never fail-many :: Monoid output => Parser.T output Maybe a -> Parser.T output Identity [a]+many :: Monoid output =>+ Parser.T input output Maybe a -> Parser.T input output Identity [a] many x = {- It is better to have 'force' at the place it is, instead of writing it to the recursive call, because 'x' can cause an infinite loop. -} withDefault (many1 x) (return []) -many1 :: Monoid output => Parser.T output Maybe a -> Parser.T output Maybe [a]+-- | fails when trying the sub-parser the first time or never+many1 :: Monoid output =>+ Parser.T input output Maybe a -> Parser.T input output Maybe [a] many1 x = liftM2 (:) x (allowFail $ many x) -manySatisfy :: (Char -> Bool) -> Parser.T [w] Identity String++manyNull :: Monoid output =>+ Parser.T input output Maybe () -> Parser.T input output Identity ()+manyNull x =+ withDefault (many1Null x) (return ())++many1Null :: Monoid output =>+ Parser.T input output Maybe () -> Parser.T input output Maybe ()+many1Null x = x >> (allowFail $ manyNull x)+++many0toN :: Monoid output =>+ Int -> Parser.T input output Maybe a -> Parser.T input output Identity [a]+many0toN n x =+ withDefault (guard (n>0) >> many1toN n x) (return [])++-- | condition: n>0, this will not be checked+many1toN :: Monoid output =>+ Int -> Parser.T input output Maybe a -> Parser.T input output Maybe [a]+many1toN n x = liftM2 (:) x (allowFail $ many0toN (pred n) x)+++manySatisfy :: (Stream.C input) =>+ (Char -> Bool) -> Parser.T input [w] Identity String manySatisfy = allowEmit . many . satisfy -many1Satisfy :: (Char -> Bool) -> Parser.T [w] Maybe String+many1Satisfy :: (Stream.C input) =>+ (Char -> Bool) -> Parser.T input [w] Maybe String many1Satisfy = allowEmit . many1 . satisfy -dropSpaces :: Parser.T [w] Identity ()+dropSpaces :: (Stream.C input) =>+ Parser.T input [w] Identity () dropSpaces = manySatisfy isSpace >> return () -char :: Monoid output => Char -> Parser.T output Maybe Char+char ::+ (Monoid output, Stream.C input) =>+ Char -> Parser.T input output Maybe Char char c = satisfy (c==) -string :: String -> Parser.T [w] Maybe String+string :: (Stream.C input) =>+ String -> Parser.T input [w] Maybe String string = allowEmit . mapM char -readUntil :: String -> Parser.T [w] Identity (Bool,String)+readUntil :: (Stream.C input) =>+ String -> Parser.T input [w] Identity (Bool,String) readUntil pattern =- let recurse =+ let recourse = foldr withDefault (return (False,[])) $ liftM (const (True,[])) (mapM char pattern) : (do c <- nextChar- ~(found,str) <- allowFail recurse+ ~(found,str) <- allowFail recourse return (found,c:str)) : []- in allowEmit recurse+ in allowEmit recourse {- runStateT (readUntil "-->") (Position.initialize "input", "<!-- comment --> other stuff") -} -emit :: Monad fail => w -> Parser.T [w] fail ()+emit :: Monad fail =>+ w -> Parser.T input [w] fail () emit w = tell [w]++modifyEmission :: Monad fail =>+ ([w] -> [w]) -> Parser.T input [w] fail a -> Parser.T input [w] fail a+modifyEmission f = censor f
src/Text/HTML/TagSoup/HT/Parser/Custom.hs view
@@ -7,9 +7,16 @@ ) where -import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position import qualified Text.HTML.TagSoup.HT.Parser.Status as Status+import qualified Text.HTML.TagSoup.HT.Parser.Stream as Stream +{-+If we want to stay independent from transformers package+we have to define Stream without StateT.+-}+import Control.Monad.Trans.State (runStateT, )+ import Control.Monad (MonadPlus, mzero, mplus, liftM, ) import Control.Monad.Fix (MonadFix, mfix, fix, ) @@ -30,11 +37,11 @@ but its monadic combinator @>>=@ is defined more lazily. See Parser.MTL. -}-newtype T output fail a =- Cons { run :: Status.T -> fail (a, Status.T, output) }+newtype T input output fail a =+ Cons { run :: Status.T input -> fail (a, Status.T input, output) } -instance (Monoid output, Monad fail) => Monad (T output fail) where+instance (Monoid output, Monad fail) => Monad (T input output fail) where return a = Cons $ \s -> return (a, s, mempty) m >>= k = Cons $ \s -> do ~(a, s', w) <- run m s@@ -42,7 +49,7 @@ return (b, s'', mappend w w') fail msg = Cons $ \_ -> fail msg -instance (Monoid output, MonadPlus fail) => MonadPlus (T output fail) where+instance (Monoid output, MonadPlus fail) => MonadPlus (T input output fail) where mzero = Cons $ \_ -> mzero m `mplus` n = Cons $ \s -> run m s `mplus` run n s @@ -50,45 +57,47 @@ {- | Cf. 'Control.Monad.State.gets' -}-gets :: (Monoid output, Monad fail) => (Status.T -> a) -> T output fail a+gets :: (Monoid output, Monad fail) =>+ (Status.T input -> a) -> T input output fail a gets f = Cons $ \ st -> return (f st, st, mempty) {- | Cf. 'Control.Monad.Writer.tell' -}-tell :: (Monad fail) => output -> T output fail ()+tell :: (Monad fail) => output -> T input output fail () tell ws = Cons $ \ st -> return ((),st,ws) -instance (Monoid output, MonadFix fail) => MonadFix (T output fail) where+instance (Monoid output, MonadFix fail) => MonadFix (T input output fail) where mfix f = Cons $ \ st -> mfix $ \ ~(a, _, _) -> run (f a) st -nextChar :: (Monoid output, MonadPlus fail) => T output fail Char+nextChar ::+ (Monoid output, Stream.C input) =>+ T input output Maybe Char nextChar = Cons $ \ (Status.Cons pos str) ->- case str of- [] -> mzero- (c:cs) -> return (c, Status.Cons (Position.updateOnChar c pos) cs, mempty)+ do (c,cs) <- runStateT Stream.getChar str+ return (c, Status.Cons (Position.updateOnChar c pos) cs, mempty) -- this replaces 'ignoreEmit' allowEmit :: Monad fail =>- T () fail a -> T [w] fail a+ T input () fail a -> T input [w] fail a allowEmit p = Cons $ liftM (\ ~(a,s,_) -> (a,s,mempty)) . run p -- this replaces 'force'-allowFail :: T output Identity a -> T output Maybe a+allowFail :: T input output Identity a -> T input output Maybe a allowFail p = Cons $ return . runIdentity . run p withDefault ::- T output Maybe a ->- T output Identity a ->- T output Identity a+ T input output Maybe a ->+ T input output Identity a ->+ T input output Identity a withDefault p q = Cons $ \s -> maybe (run q s) Identity (run p s)
src/Text/HTML/TagSoup/HT/Parser/MTL.hs view
@@ -1,20 +1,25 @@ module Text.HTML.TagSoup.HT.Parser.MTL ( T, nextChar, withDefault,- run, gets, tell, mfix,+ run, gets, tell, censor, mfix, allowFail, allowEmit, module Control.Monad.Identity, ) where -import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position import qualified Text.HTML.TagSoup.HT.Parser.Status as Status-import Text.HTML.TagSoup.HT.Utility (mapSnd, )+import qualified Text.HTML.TagSoup.HT.Parser.Stream as Stream+import Data.Tuple.HT (mapSnd, ) -import Control.Monad.Writer (WriterT(..), mapWriterT, tell, lift, liftM, )-import Control.Monad.State (StateT(..), mapStateT, gets, )+import qualified Control.Monad.Trans.State as State++import Control.Monad.Trans.Writer (WriterT(..), mapWriterT, tell, censor, )+import Control.Monad.Trans.State (StateT(..), mapStateT, ) import Control.Monad.Fix (mfix) import Control.Monad.Identity (Identity(..), )+import Control.Monad.Trans (lift, )+import Control.Monad (liftM, ) import Data.Monoid (Monoid) @@ -27,32 +32,48 @@ if the assumptions of non-failing or non-emission were wrong. Now, since we declare this properties in types, runtime errors cannot happen.++The downside is that we cannot easily extend that scheme+to embedded monads that are sources of Chars.+@StateT s Maybe@ and @MaybeT (State s)@ significantly differ+in case of parser failures.+The first one "resets" its state+(more precisely, you would use 'mplus' to give alternative parsers a try,+and 'mplus' would keep the original state),+and the second one stays with the updated state.+The second one would be close to @MaybeT (ReaderT Handle IO)@+which would allow to use @hGetChar@ as character source,+but for IO functions we had to maintain a list of characters+that might have to be re-parsed by a parser alternative. -}-type T output fail = WriterT output (StateT Status.T fail)+type T input output fail = WriterT output (StateT (Status.T input) fail) run :: Monad fail =>- T output fail a -> Status.T -> fail (a, Status.T, output)+ T input output fail a -> Status.T input -> fail (a, Status.T input, output) run p = liftM (\((a,w),st) -> (a,st,w)) . runStateT (runWriterT p) -nextChar :: Monoid output => T output Maybe Char+nextChar :: (Monoid output, Stream.C input) =>+ T input output Maybe Char nextChar = lift $ StateT $ \ (Status.Cons pos str) ->- case str of- [] -> Nothing- (c:cs) -> Just (c, Status.Cons (Position.updateOnChar c pos) cs)+ do (c,cs) <- runStateT Stream.getChar str+ return (c, Status.Cons (Position.updateOnChar c pos) cs) +gets :: (Monoid output, Monad fail) =>+ (Status.T input -> a) -> T input output fail a+gets = lift . State.gets -- this replaces 'ignoreEmit' allowEmit :: Monad fail =>- T () fail a -> T [w] fail a+ T input () fail a -> T input [w] fail a allowEmit =- mapWriterT (fmap (mapSnd (const [])))+ mapWriterT (liftM (mapSnd (const []))) allowFail' :: StateT s Identity a -> StateT s Maybe a@@ -60,7 +81,7 @@ mapStateT (Just . runIdentity) -- this replaces 'force'-allowFail :: T output Identity a -> T output Maybe a+allowFail :: T input output Identity a -> T input output Maybe a allowFail = mapWriterT allowFail' @@ -73,8 +94,8 @@ maybe (runStateT q st) Identity (runStateT p st) withDefault ::- T output Maybe a ->- T output Identity a ->- T output Identity a+ T input output Maybe a ->+ T input output Identity a ->+ T input output Identity a withDefault p q = WriterT $ withDefault' (runWriterT p) (runWriterT q)
src/Text/HTML/TagSoup/HT/Parser/Status.hs view
@@ -1,10 +1,10 @@ module Text.HTML.TagSoup.HT.Parser.Status where -import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position -data T =+data T stream = Cons { sourcePos :: Position.T,- source :: String+ source :: stream } deriving Show
+ src/Text/HTML/TagSoup/HT/Parser/Stream.hs view
@@ -0,0 +1,68 @@+module Text.HTML.TagSoup.HT.Parser.Stream where++import Control.Monad.Trans.State (StateT(StateT), gets, put, )+import Control.Monad (guard, mzero, )+import qualified Data.List.HT as L++import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BL++import qualified Prelude as P+import Prelude hiding (Char, getChar, )+++class C stream where+ getChar :: StateT stream Maybe P.Char+++class Char char where+ toChar :: char -> P.Char++instance Char P.Char where+ toChar = id+++instance Char char => C [char] where+ getChar = fmap toChar $ StateT L.viewL++instance C BS.ByteString where+ getChar = StateT BS.uncons++instance C BL.ByteString where+ getChar = StateT BL.uncons+++data PointerStrict =+ PointerStrict {psSource :: !BS.ByteString, psIndex :: !Int}++pointerFromByteStringStrict :: BS.ByteString -> PointerStrict+pointerFromByteStringStrict str =+ PointerStrict str 0++instance C PointerStrict where+ getChar =+ do s <- gets psSource+ i <- gets psIndex+ guard (i < BS.length s)+ put $ PointerStrict s (i+1)+ return (BS.index s i)++++data PointerLazy =+ PointerLazy {plSource :: ![BS.ByteString], plIndex :: !Int}++pointerFromByteStringLazy :: BL.ByteString -> PointerLazy+pointerFromByteStringLazy str =+ PointerLazy (BL.toChunks str) 0++instance C PointerLazy where+ getChar =+ do s <- gets plSource+ i <- gets plIndex+ case s of+ [] -> mzero+ (c:cs) ->+ if i < BS.length c+ then put (PointerLazy s (i+1)) >> return (BS.index c i)+ else put (PointerLazy cs (i - BS.length c)) >> getChar
src/Text/HTML/TagSoup/HT/ParserNM.hs view
@@ -6,32 +6,44 @@ ) where import qualified Text.HTML.TagSoup as TagSoup+import qualified Text.HTML.TagSoup.HT.Parser as Parser import qualified Text.HTML.TagSoup.HT.Tag as Custom import qualified Text.HTML.TagSoup.HT.PositionTag as PosTag-import qualified Text.HTML.TagSoup.HT.Parser as Parser-import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Position as Position+import qualified Text.XML.Basic.ProcessingInstruction as PI+import qualified Text.XML.Basic.Attribute as Attr+import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Name.MixedCase as NameMC import Data.Accessor.Basic ((^.), ) runSoup :: String -> [TagSoup.Tag] runSoup =- concatMap convertTag . Parser.runSoupWithPositions+ concatMap convertTag .+ (\x -> x :: [PosTag.T NameMC.T String]) .+ Parser.runSoupWithPositions -convertTag :: PosTag.T Char -> [TagSoup.Tag]-convertTag (pos,tag) =+convertTag ::+ (Name.Tag name, Name.Attribute name) =>+ PosTag.T name String -> [TagSoup.Tag]+convertTag (PosTag.Cons pos tag) = TagSoup.TagPosition (pos ^. Position.row) (pos ^. Position.column) : (case tag of- Custom.Open name attrs -> TagSoup.TagOpen name attrs- Custom.Close name -> TagSoup.TagClose name- Custom.Text text -> TagSoup.TagText text- Custom.Comment text -> TagSoup.TagComment text- Custom.Special name content- -> TagSoup.TagOpen ('!':name) [("",content)]- Custom.Processing name p- -> TagSoup.TagOpen ('?':name) $+ Custom.Open name attrs ->+ TagSoup.TagOpen (Name.toString name) (map Attr.toPair attrs)+ Custom.Close name ->+ TagSoup.TagClose (Name.toString name)+ Custom.Text text ->+ TagSoup.TagText text+ Custom.Comment text ->+ TagSoup.TagComment text+ Custom.Special name content ->+ TagSoup.TagOpen ('!' : Name.toString name) [("",content)]+ Custom.Processing name p ->+ TagSoup.TagOpen ('?' : Name.toString name) $ case p of- Custom.KnownProcessing attrs -> attrs- Custom.UnknownProcessing content -> [("",content)]+ PI.Known attrs -> map Attr.toPair attrs+ PI.Unknown content -> [("",content)] Custom.Warning text -> TagSoup.TagWarning text) : []
− src/Text/HTML/TagSoup/HT/Position.hs
@@ -1,93 +0,0 @@-{- |-Module : Text.HTML.TagSoup.HT.Position--Maintainer : tagsoup@henning-thielemann.de-Stability : provisional-Portability : portable--Position in a file.--Cf. to Text.ParserCombinators.Parsec.Pos--}--module Text.HTML.TagSoup.HT.Position (- T, FileName, Row, Column,- new, initialize,- row, column, fileName,- updateOnChar, updateOnString,- toReportText,- ) where--import qualified Data.Accessor.Basic as Accessor--- import Data.Accessor.Basic ((^=), )--import Data.List (foldl')---type FileName = String-type Row = Int-type Column = Int--{- |-Position in a file consisting of file name, row and column coordinates.-Upper left is (0,0), but show routines can display this with different offsets.--}-data T =- Cons {- fileName_ :: FileName,- row_ :: !Row,- column_ :: !Column- } deriving (Eq,Ord)---new :: FileName -> Row -> Column -> T-new = Cons--initialize :: FileName -> T-initialize fn = new fn 0 0----- * access functions--fileName :: Accessor.T T FileName-fileName = Accessor.fromSetGet (\fn p -> p{fileName_ = fn}) fileName_--row :: Accessor.T T Row-row = Accessor.fromSetGet (\n p -> p{row_ = n}) row_--column :: Accessor.T T Column-column = Accessor.fromSetGet (\n p -> p{column_ = n}) column_----- * update position according to read characters--updateOnString :: T -> String -> T-updateOnString pos string =- foldl' (flip updateOnChar) pos string--updateOnChar :: Char -> T -> T-updateOnChar char (Cons name r c) =- let (newRow, newColumn) =- case char of- '\n' -> (succ r, 0)- '\t' -> (r, c + 8 - mod c 8)- _ -> (r, succ c)- in Cons name newRow newColumn--- in (row ^= newRow) $ (column ^= newColumn) $ pos----- * update position according to read characters--{- |-Convert the file position to a format-that development environments can understand.--}-toReportText :: T -> String-toReportText (Cons name r c) =- concatMap (++":") [name, show (r+1), show (c+1)]--instance Show T where- showsPrec p (Cons name r c) =- showParen (p >= 10)- (showString $ unwords $- "Position.new" : show name : show r : show c : [])
src/Text/HTML/TagSoup/HT/PositionTag.hs view
@@ -1,25 +1,81 @@ module Text.HTML.TagSoup.HT.PositionTag where import qualified Text.HTML.TagSoup.HT.Tag as Tag-import qualified Text.HTML.TagSoup.HT.Position as Position+import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Position as Position -import Text.HTML.TagSoup.HT.Utility (mapSnd, )+import Data.Tuple.HT (mapFst, )+import Data.Monoid (Monoid, mempty, mappend, ) +import qualified Data.Accessor.Basic as Accessor -type T char = (Position.T, Tag.T char)+import Data.Foldable (Foldable(foldMap), )+import Data.Traversable (Traversable(sequenceA), )+import Control.Applicative (liftA, ) -lift :: (Tag.T char0 -> Tag.T char1) -> (T char0 -> T char1)-lift = mapSnd -canonicalizeSoup :: [T char] -> [T char]-canonicalizeSoup =- map canonicalize+data T name string =+ Cons {+ position_ :: Position.T,+ tag_ :: Tag.T name string+ } -{- |-See 'Tag.canonicalize'.+instance (Name.Attribute name, Show string, Show name) =>+ Show (T name string) where+ showsPrec p (Cons pos t) =+ showParen (p > 10)+ (showString "PosTag.cons " .+ showsPrec 11 pos . showString " " .+ showsPrec 11 t)++{-+> cons (Position.new "bla" 0 0) (Tag.Close $ Name.fromString "bla" :: Tag.T Text.XML.Basic.Name.LowerCase.T String) -}-canonicalize :: T char -> T char-canonicalize = lift Tag.canonicalize+cons :: Position.T -> Tag.T name string -> T name string+cons = Cons -textFromCData :: T Char -> T Char+position :: Accessor.T (T name string) Position.T+position = Accessor.fromSetGet (\n p -> p{position_ = n}) position_++tag :: Accessor.T (T name string) (Tag.T name string)+tag = Accessor.fromSetGet (\n p -> p{tag_ = n}) tag_++lift ::+ (Tag.T name0 string0 -> Tag.T name1 string1) ->+ (T name0 string0 -> T name1 string1)+lift f (Cons p t) = Cons p (f t)++instance Functor (T name) where+ fmap f = lift (fmap f)++instance Foldable (T name) where+ foldMap f = foldMap f . tag_++instance Traversable (T name) where+ sequenceA (Cons p t) = liftA (Cons p) $ sequenceA t+++textFromCData ::+ (Eq name, Name.Tag name) =>+ T name String -> T name String textFromCData = lift Tag.textFromCData+++{- |+Merge adjacent Text sections.+-}+concatTexts ::+ Monoid string =>+ [T name string] -> [T name string]+concatTexts =+ foldr+ (\t ts ->+ case t of+ Cons pos (Tag.Text str0) ->+ uncurry (:) $+ mapFst (cons pos . Tag.Text . mappend str0) $+ case ts of+ Cons _ (Tag.Text str1) : rest -> (str1,rest)+ _ -> (mempty,ts)+ _ -> t:ts)+ []
+ src/Text/HTML/TagSoup/HT/Process.hs view
@@ -0,0 +1,103 @@+module Text.HTML.TagSoup.HT.Process where++import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.HTML.TagSoup.HT.Tag.Match as Match++import qualified Text.XML.Basic.Attribute as Attr+import qualified Text.XML.Basic.Name as Name++import qualified Data.Char as Char+import Data.List.HT (viewL, takeWhileRev, )+import Data.Tuple.HT (mapFst, )+import Data.Maybe (fromMaybe, mapMaybe, )+import Control.Monad (guard, liftM2, )+++-- * analyse soup++{-+Rather the same as wraxml:HTML.Tree.findMetaEncoding+-}+findMetaEncoding ::+ (Name.Tag name, Name.Attribute name, Eq name) =>+ [Tag.T name String] -> Maybe String+findMetaEncoding =+ fmap (map Char.toLower . takeWhileRev ('='/=)) .+ lookup "content-type" .+ map (mapFst (map Char.toLower)) .+ getMetaHTTPHeaders++{- |+Extract META tags which contain HTTP-EQUIV attribute+and present these values like HTTP headers.+-}+getMetaHTTPHeaders ::+ (Name.Tag name, Name.Attribute name, Eq name) =>+ [Tag.T name string] -> [(string, string)]+getMetaHTTPHeaders =+ mapMaybe+ (\tag ->+ do (name, attrs) <- Tag.maybeOpen tag+ guard (Name.match "meta" name)+ liftM2 (,)+ (Attr.lookupLit "http-equiv" attrs)+ (Attr.lookupLit "content" attrs)) .+ getHeadTags++getHeadTags ::+ (Name.Tag name, Name.Attribute name, Eq name) =>+ [Tag.T name string] -> [Tag.T name string]+getHeadTags =+ takeWhile (not . Match.closeLit "head") .+ drop 1 .+ dropWhile (not . Match.openLit "head" (const True)) .+ takeWhile (not . Match.openLit "body" (const True))+++-- * transform soup++{- |+Modify attributes and tags of certain parts.+For limitations, see 'parts'.+-}+partAttrs ::+ (Eq name) =>+ (Tag.Name name -> Bool) ->+ (([Attr.T name string], [Tag.T name string]) ->+ ([Attr.T name string], [Tag.T name string])) ->+ [Tag.T name string] -> [Tag.T name string]+partAttrs p f =+ concatMap+ (either+ (\((name,attrs),part) ->+ let (newAttrs, newPart) = f (attrs, part)+ in Tag.Open name newAttrs : newPart ++ [Tag.Close name])+ id) .+ parts p++{- |+Extract parts from the tag soup+that are enclosed in corresponding open and close tags.+If a close tag is missing, the soup end is considered as end of the part.+However nested tags are not supported,+e.g. in @<a><a></a></a>@ the second @<a>@ is considered+to be enclosed in the first @<a>@ and the first @</a>@+and the second @</a>@ is ignored.+-}+parts ::+ (Eq name) =>+ (Tag.Name name -> Bool) ->+ [Tag.T name string] ->+ [Either+ ((Tag.Name name, [Attr.T name string]), [Tag.T name string])+ [Tag.T name string]]+parts p =+ let recourse ts =+ let (prefix0,suffix0) = break (Match.open p (const True)) ts+ in Right prefix0 :+ fromMaybe []+ (do (t, suffix1) <- viewL suffix0+ (name, attrs) <- Tag.maybeOpen t+ let (part,suffix2) = break (Match.close (name==)) suffix1+ return $ Left ((name, attrs), part) : recourse (drop 1 suffix2))+ in recourse
src/Text/HTML/TagSoup/HT/Tag.hs view
@@ -1,106 +1,233 @@-module Text.HTML.TagSoup.HT.Tag where+module Text.HTML.TagSoup.HT.Tag (+ T(..), Name(..),+ mapName, -import Data.Char (toLower, toUpper, )-import Data.Maybe (mapMaybe, )+ isOpen, maybeOpen,+ isClose, maybeClose,+ isText, maybeText, innerText,+ isComment, maybeComment,+ isSpecial, maybeSpecial,+ isCData, maybeCData,+ isProcessing, maybeProcessing,+ isWarning, maybeWarning, + formatOpen, formatClose, --- * type definitions+ textFromCData, concatTexts,+ mapText, mapTextA,+ ) where -{- | An HTML attribute @id=\"name\"@ generates @(\"id\",\"name\")@ -}-type Attribute char = (String,[char])+import qualified Text.XML.Basic.ProcessingInstruction as PI+import qualified Text.XML.Basic.Attribute as Attr+import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Format as Fmt+import Text.XML.Basic.Tag (Name(Name), cdata, ) +import Data.Tuple.HT (mapFst, )+import Data.Maybe (mapMaybe, fromMaybe, )+import Data.Monoid (Monoid, mempty, mappend, mconcat, )+import Control.Monad (guard, )++import Data.Foldable (Foldable(foldMap), )+import Data.Traversable (Traversable(sequenceA), traverse, )+import Control.Applicative (Applicative, pure, liftA, )+++-- * type definitions+ {- | An HTML element, a document is @[T]@. There is no requirement for 'Open' and 'Close' to match. -The type parameter @char@ lets you choose between-@Char@ for interpreted HTML entity references and-@HTMLChar.T@ for uninterpreted HTML entity.+The type parameter @string@ lets you choose between+@[Char]@ for interpreted HTML entity references and+@[HTMLChar.T]@ for uninterpreted HTML entities. You will most oftenly want plain @Char@, since @HTMLChar.T@ is only necessary if you want to know, whether a non-ASCII character was encoded as HTML entity or as non-ASCII Unicode character. -}-data T char =- Open String [Attribute char]- -- ^ An open tag with 'Attribute's in their original order.- | Close String+data T name string =+ Open (Name name) [Attr.T name string]+ -- ^ An open tag with 'Attr.T's in their original order.+ | Close (Name name) -- ^ A closing tag- | Text [char]+ | Text string -- ^ A text node, guaranteed not to be the empty string | Comment String -- ^ A comment- | Special String String+ | Special (Name name) String -- ^ A tag like @\<!DOCTYPE ...\>@- | Processing String (Processing char)+ | Processing (Name name) (PI.T name string) -- ^ A tag like @\<?xml ...\>@ | Warning String -- ^ Mark a syntax error in the input file deriving (Show, Eq, Ord) -data Processing char =- KnownProcessing [Attribute char]- | UnknownProcessing String- deriving (Show, Eq, Ord) +instance Functor (T name) where+ fmap f tag =+ case tag of+ Open name attrs -> Open name $ map (fmap f) attrs+ Close name -> Close name+ Text text -> Text $ f text+ Comment text -> Comment text+ Special name content -> Special name content+ Processing name proc -> Processing name $ fmap f proc+ Warning text -> Warning text ++instance Foldable (T name) where+ foldMap f tag =+ case tag of+ Open _name attrs -> foldMap (foldMap f) attrs+ Close _name -> mempty+ Text text -> f text+ Comment _text -> mempty+ Special _name _content -> mempty+ Processing _name proc -> foldMap f proc+ Warning _text -> mempty+++instance Traversable (T name) where+ sequenceA tag =+ case tag of+ Open name attrs -> liftA (Open name) $ traverse sequenceA attrs+ Close name -> pure $ Close name+ Text text -> liftA Text $ text+ Comment text -> pure $ Comment text+ Special name content -> pure $ Special name content+ Processing name proc -> liftA (Processing name) $ sequenceA proc+ Warning text -> pure $ Warning text+++mapName :: (name0 -> name1) -> T name0 string -> T name1 string+mapName f tag =+ case tag of+ Open (Name name) attrs -> Open (Name $ f name) $ map (Attr.mapName f) attrs+ Close (Name name) -> Close (Name $ f name)+ Text text -> Text text+ Comment text -> Comment text+ Special (Name name) content -> Special (Name $ f name) content+ Processing (Name name) proc -> Processing (Name $ f name) $ PI.mapName f proc+ Warning text -> Warning text++++++instance (Eq name, Name.Tag name, Name.Attribute name, Fmt.C string) =>+ Fmt.C (T name string) where+ run t =+ case t of+ Open name attrs -> formatOpen False name attrs+ Close name -> formatClose name+ Text str -> Fmt.run str+ Comment c ->+ showString "<!--" . showString c . showString "-->"+ Warning e ->+ showString "<!-- Warning: " . showString e . showString " -->"+ Special name str ->+ Fmt.angle $+ Fmt.exclam .+ Fmt.name name .+ if cdata == name+ then showString str . showString "]]"+ else Fmt.blank . showString str+ Processing name p ->+ Fmt.angle $+ Fmt.quest .+ Fmt.name name .+ Fmt.run p .+ Fmt.quest+++formatOpen :: (Name.Tag name, Name.Attribute name, Fmt.C string) =>+ Bool -> Name name -> [Attr.T name string] -> ShowS+formatOpen selfClosing name attrs =+ Fmt.angle $+ Fmt.name name .+ Attr.formatListBlankHead attrs .+ if selfClosing then Fmt.slash else id++formatClose :: (Name.Tag name) =>+ Name name -> ShowS+formatClose name =+ Fmt.angle $+ Fmt.slash . Fmt.name name++ -- * check for certain tag types -- | Test if a 'T' is a 'Open'-isOpen :: T char -> Bool+isOpen :: T name string -> Bool isOpen tag = case tag of (Open {}) -> True; _ -> False -maybeOpen :: T char -> Maybe (String, [Attribute char])+maybeOpen :: T name string -> Maybe (Name name, [Attr.T name string]) maybeOpen tag = case tag of Open name attrs -> Just (name, attrs); _ -> Nothing -- | Test if a 'T' is a 'Close'-isClose :: T char -> Bool+isClose :: T name string -> Bool isClose tag = case tag of (Close {}) -> True; _ -> False -maybeClose :: T char -> Maybe String+maybeClose :: T name string -> Maybe (Name name) maybeClose tag = case tag of Close x -> Just x; _ -> Nothing -- | Test if a 'T' is a 'Text'-isText :: T char -> Bool+isText :: T name string -> Bool isText tag = case tag of (Text {}) -> True; _ -> False -- | Extract the string from within 'Text', otherwise 'Nothing'-maybeText :: T char -> Maybe [char]+maybeText :: T name string -> Maybe string maybeText tag = case tag of Text x -> Just x; _ -> Nothing -- maybeText tag = do Text x <- Just tag; return x -- | Extract all text content from tags (similar to Verbatim found in HaXml)-innerText :: [T char] -> [char]-innerText = concat . mapMaybe maybeText+innerText :: (Monoid string) => [T name string] -> string+innerText = mconcat . mapMaybe maybeText -isComment :: T char -> Bool+isComment :: T name string -> Bool isComment tag = case tag of (Comment {}) -> True; _ -> False -maybeComment :: T char -> Maybe String+maybeComment :: T name string -> Maybe String maybeComment tag = case tag of Comment x -> Just x; _ -> Nothing -isSpecial :: T char -> Bool+isSpecial :: T name string -> Bool isSpecial tag = case tag of (Special {}) -> True; _ -> False -maybeSpecial :: T char -> Maybe (String, String)+maybeSpecial :: T name string -> Maybe (Name name, String) maybeSpecial tag = case tag of Special name content -> Just (name, content); _ -> Nothing -isProcessing :: T char -> Bool+isCData ::+ (Eq name, Name.Tag name) =>+ T name string -> Bool+isCData tag = case tag of (Special name _) -> cdata == name; _ -> False++maybeCData ::+ (Eq name, Name.Tag name) =>+ T name string -> Maybe String+maybeCData tag =+ do (name, content) <- maybeSpecial tag+ guard (cdata == name)+ return content+++isProcessing :: T name string -> Bool isProcessing tag = case tag of (Processing {}) -> True; _ -> False -maybeProcessing :: T char -> Maybe (String, Processing char)+maybeProcessing :: T name string -> Maybe (Name name, PI.T name string) maybeProcessing tag = case tag of Processing target instr -> Just (target, instr); _ -> Nothing -isWarning :: T char -> Bool+isWarning :: T name string -> Bool isWarning tag = case tag of (Warning {}) -> True; _ -> False -maybeWarning :: T char -> Maybe String+maybeWarning :: T name string -> Maybe String maybeWarning tag = case tag of Warning x -> Just x; _ -> Nothing -- maybeWarning tag = do Warning x <- Just tag; return x @@ -108,27 +235,74 @@ -- * tag processing -canonicalizeSoup :: [T char] -> [T char]-canonicalizeSoup =- map canonicalize- {- |-Turns all tag names to lower case and-converts DOCTYPE to upper case.+Replace CDATA sections by plain text. -}-canonicalize :: T char -> T char-canonicalize t =+textFromCData ::+ (Eq name, Name.Tag name) =>+ T name String -> T name String+textFromCData t =+ fromMaybe t $+ do (name, text) <- maybeSpecial t+ guard (cdata == name)+ return $ Text text++{- case t of- Open name attrs -> Open (map toLower name) attrs- Close name -> Close (map toLower name)- Special name info -> Special (map toUpper name) info+ Special name text ->+ if cdata == name+ then Text text+ else t _ -> t+-} {- |-Replace CDATA sections by plain text.+Merge adjacent Text sections. -}-textFromCData :: T Char -> T Char-textFromCData t =+concatTexts ::+ Monoid string =>+ [T name string] -> [T name string]+concatTexts =+ foldr+ (\t ts ->+ case t of+ Text str0 ->+ uncurry (:) $+ mapFst (Text . mappend str0) $+ case ts of+ Text str1 : rest -> (str1,rest)+ _ -> (mempty,ts)+ _ -> t:ts)+ []+++{- |+Modify content of a Text or a CDATA part.+-}+mapText ::+ (Eq name, Name.Tag name) =>+ (String -> String) ->+ T name String -> T name String+mapText f t = case t of- Special "[CDATA[" text -> Text text+ Text s -> Text $ f s+ Special name s ->+ Special name $+ if cdata == name+ then f s+ else s _ -> t++mapTextA ::+ (Eq name, Name.Tag name, Applicative f) =>+ (String -> f String) ->+ T name String -> f (T name String)+mapTextA f t =+ case t of+ Text s -> liftA Text $ f s+ Special name s ->+ liftA (Special name) $+ if cdata == name+ then f s+ else pure s+ _ -> pure t
+ src/Text/HTML/TagSoup/HT/Tag/Match.hs view
@@ -0,0 +1,89 @@+module Text.HTML.TagSoup.HT.Tag.Match where++import qualified Text.HTML.TagSoup.HT.Tag as Tag+import qualified Text.XML.Basic.Attribute as Attr+import qualified Text.XML.Basic.Name as Name+++ignore :: a -> Bool+ignore _ = True+++-- | match an opening tag+open :: (Tag.Name name -> Bool) -> ([Attr.T name string] -> Bool) -> Tag.T name string -> Bool+open pName pAttrs (Tag.Open name attrs) =+ pName name && pAttrs attrs+open _ _ _ = False++-- | match an closing tag+close :: (Tag.Name name -> Bool) -> Tag.T name string -> Bool+close pName (Tag.Close name) = pName name+close _ _ = False++-- | match a text+text :: (string -> Bool) -> Tag.T name string -> Bool+text p (Tag.Text str) = p str+text _ _ = False++comment :: (String -> Bool) -> Tag.T name string -> Bool+comment p (Tag.Comment str) = p str+comment _ _ = False++special :: (Tag.Name name -> Bool) -> (String -> Bool) -> Tag.T name string -> Bool+special pType pInfo (Tag.Special typ info) = pType typ && pInfo info+special _ _ _ = False+++-- | match a opening tag's name literally+openLit ::+ (Eq name, Name.Tag name) =>+ String -> ([Attr.T name string] -> Bool) -> Tag.T name string -> Bool+openLit name = open (Name.match name)++-- | match a closing tag's name literally+closeLit ::+ (Eq name, Name.Tag name) =>+ String -> Tag.T name string -> Bool+closeLit name = close (Name.match name)++openAttrLit ::+ (Eq name, Name.Attribute name, Name.Tag name, Eq string) =>+ String -> String -> string -> Tag.T name string -> Bool+openAttrLit name attrName attrValue =+ openLit name (Attr.anyLit attrName attrValue)++{- |+Match a tag with given name, that contains an attribute+with given name, that satisfies a predicate.+If an attribute occurs multiple times,+all occurrences are checked.+-}+openAttrNameLit ::+ (Eq name, Name.Attribute name, Name.Tag name) =>+ String -> String -> (string -> Bool) -> Tag.T name string -> Bool+openAttrNameLit tagName attrName pAttrValue =+ openLit tagName+ (Attr.any (\(Attr.Cons name value) ->+ Name.match attrName name && pAttrValue value))+++-- | Check if the 'Tag.T' is 'Tag.Open' and matches the given name+openNameLit ::+ (Eq name, Name.Tag name) =>+ String -> Tag.T name string -> Bool+openNameLit name = openLit name ignore++-- | Check if the 'Tag.T' is 'Tag.Close' and matches the given name+closeNameLit ::+ (Eq name, Name.Tag name) =>+ String -> Tag.T name string -> Bool+closeNameLit name = closeLit name++++{-+getTagContent :: String -> ([Attr.T name string] -> Bool) -> [Tag.T name string] -> [Tag.T name string]+getTagContent name pAttrs =+ takeWhile (not . tagCloseLit name) . drop 1 .+ head . sections (tagOpenLit name pAttrs)+-}
src/Text/HTML/TagSoup/HT/Test.hs view
@@ -8,7 +8,9 @@ import qualified Text.HTML.TagSoup.HT.Tag as Tag import qualified Text.HTML.TagSoup.HT.Parser as Parser+import qualified Text.XML.Basic.Name.MixedCase as Name import qualified Text.HTML.TagSoup as TagSoupNM+import Data.List (isPrefixOf, ) {- *Text.HTML.TagSoup.HT> mapM print $ runSoup $ "</html " ++ cycle " abc=a_b&c"@@ -27,9 +29,9 @@ you have found a laziness stopper. -} laziness :: [Char]-laziness = lazyTags ++ lazyWarnings+laziness = lazyTags ++ lazyWarnings ++ restrictedWarnings -runSoup :: String -> [Tag.T Char]+runSoup :: String -> [Tag.T Name.T String] runSoup = Parser.runSoup lazyTags :: [Char]@@ -37,6 +39,8 @@ map ((!!1000) . show . runSoup) $ (cycle "Rhabarber") : (repeat '&') :+ ('&' : '#' : repeat '1') :+ ('&' : repeat 'a') : ("<"++cycle "html") : ("<html "++cycle "name") : ("<html "++cycle "na!me=value ") :@@ -61,11 +65,18 @@ map ((!!1000) . show . tail . runSoup) $ (repeat '&') : ("<html "++cycle "na!me=value ") :+ ("</html "++cycle "junk") :+ (cycle "1<2 ") :+ []++restrictedWarnings :: [Char]+restrictedWarnings =+ map (last . show . head .+ dropWhile (not . maybe False (isPrefixOf "further") . Tag.maybeWarning) .+ runSoup) $ ("<html name="++cycle "val!ue") : ("<html name="++cycle "val&ue") : ("<html name="++cycle "va&l!ue") :- ("</html "++cycle "junk") :- (cycle "1<2 ") : [] @@ -79,9 +90,9 @@ sections_rec :: (a -> Bool) -> [a] -> [[a]] sections_rec f =- let recurse [] = []- recurse (x:xs) = (f x, x:xs) ?: recurse xs- in recurse+ let recourse [] = []+ recourse (x:xs) = (f x, x:xs) ?: recourse xs+ in recourse propSections :: Int -> [Int] -> Bool propSections y xs =
− src/Text/HTML/TagSoup/HT/Utility.hs
@@ -1,4 +0,0 @@-module Text.HTML.TagSoup.HT.Utility where--mapSnd :: (b -> c) -> (a,b) -> (a,c)-mapSnd f ~(a,b) = (a, f b)
tagsoup-ht.cabal view
@@ -1,5 +1,5 @@ Name: tagsoup-ht-Version: 0.2+Version: 0.3 License: GPL License-File: LICENSE Author: Henning Thielemann <tagsoup@henning-thielemann.de>@@ -12,28 +12,38 @@ Here I present my own parser, which I find (of course) more comprehensible and easier to extend. It also handles XML declarations and CDATA sections correctly.+ This package is abandoned and will be renamed to Tagchup. Build-Type: Simple-Build-Depends: base, mtl, tagsoup >=0.6 && <0.7, data-accessor >=0.1 && <0.2--- mtl for StateT monad transformer--- you can omit it and the module Text.HTML.TagSoup.HT.Parser.MTL,--- falling back to Text.HTML.TagSoup.HT.Parser.Custom+Build-Depends:+ xml-basic >=0.0.1 && <0.1,+ transformers >=0.0 && <0.2,+ -- 'transformers' for StateT monad transformer+ -- you can omit it and the module Text.HTML.TagSoup.HT.Parser.MTL,+ -- falling back to Text.HTML.TagSoup.HT.Parser.Custom+ explicit-exception >=0.1 && <0.2,+ tagsoup >=0.6 && <0.7,+ bytestring >=0.9.0.1 && <0.10,+ containers >=0.1 && <0.3,+ data-accessor >=0.2 && <0.3,+ utility-ht >=0.0.1 && <0.1,+ base >=3 && <4 GHC-Options: -Wall Hs-Source-Dirs: src Exposed-Modules: Text.HTML.TagSoup.HT.ParserNM Text.HTML.TagSoup.HT.Parser+ Text.HTML.TagSoup.HT.Format Text.HTML.TagSoup.HT.Tag+ Text.HTML.TagSoup.HT.Tag.Match Text.HTML.TagSoup.HT.PositionTag- Text.HTML.TagSoup.HT.Position- Text.HTML.TagSoup.HT.Match- Text.HTML.TagSoup.HT.HTMLChar+ Text.HTML.TagSoup.HT.Process Other-Modules: Text.HTML.TagSoup.HT.Parser.Combinator Text.HTML.TagSoup.HT.Parser.Custom Text.HTML.TagSoup.HT.Parser.MTL Text.HTML.TagSoup.HT.Parser.Status- Text.HTML.TagSoup.HT.Utility+ Text.HTML.TagSoup.HT.Parser.Stream Executable: tagsouptest GHC-Options: -Wall@@ -41,6 +51,12 @@ Other-Modules: Text.HTML.TagSoup.HT.Test Main-Is: example/Test.hs++Executable: tagsoupspeed+GHC-Options: -Wall+Hs-Source-Dirs: src, .+Build-Depends: old-time >=1.0 && <1.1+Main-Is: example/Speed.hs Executable: validate-tagsoup GHC-Options: -Wall