tagchup 0.3.1 → 0.4
raw patch · 16 files changed
+933/−544 lines, 16 filesdep +hxtdep ~xml-basicnew-component:exe:escape-htmlnew-component:exe:strip-html
Dependencies added: hxt
Dependency ranges changed: xml-basic
Files
- example/Escape.hs +52/−0
- example/Escape/iso8859.html +18/−0
- example/Escape/iso8859.xhtml +26/−0
- example/Escape/utf8.html +18/−0
- example/Escape/utf8.xhtml +26/−0
- example/Strip.hs +25/−0
- example/Strip/example.xhtml +32/−0
- example/Validate.hs +6/−6
- src/Text/HTML/Tagchup/Character.hs +13/−0
- src/Text/HTML/Tagchup/Parser.hs +7/−466
- src/Text/HTML/Tagchup/Parser/Tag.hs +472/−0
- src/Text/HTML/Tagchup/PositionTag.hs +13/−4
- src/Text/HTML/Tagchup/Process.hs +108/−19
- src/Text/HTML/Tagchup/Tag.hs +24/−8
- src/Text/HTML/Tagchup/Tag/Match.hs +2/−2
- tagchup.cabal +91/−39
+ example/Escape.hs view
@@ -0,0 +1,52 @@+{- |+Detects character encoding, decodes,+and convert all special characters to HTML references.+CDATA sections are converted to plain text.+Input is read from standard input,+output is written to standard output.+The encoding can be given as one argument,+which then overrides encoding information within the document.++> escape-html utf-8 <input.xhtml >output.xhtml+-}+module Main where++import qualified Text.XML.HXT.DOM.Unicode as Unicode++import qualified Text.HTML.Tagchup.Parser as Parser+import qualified Text.HTML.Tagchup.Format as Format+import qualified Text.HTML.Tagchup.Process as Process+import qualified Text.HTML.Tagchup.Tag as Tag+import qualified Text.HTML.Basic.Character as HTMLChar+import qualified Text.XML.Basic.Name.LowerCase as Name++import System.Environment (getArgs, )+++getDecoder :: Process.Encoding -> Process.Encoded -> String+getDecoder =+ maybe Unicode.latin1ToUnicode (fst.) .+ Unicode.getDecodingFct++escape :: [Tag.T Name.T String] -> String+escape =+ flip Format.htmlOrXhtml "" .+ map (fmap (map HTMLChar.asciiFromUnicode) . Tag.textFromCData)++main :: IO ()+main =+ do argv <- getArgs+ case argv of+ [] ->+ interact+ (escape .+ Process.evalDecodeAdaptive .+ Process.decodeAdaptive getDecoder .+ Parser.runSoup)+ [encoding] ->+ interact+ (escape .+ Parser.runSoup .+ getDecoder encoding)+ _ ->+ putStrLn "Error: more than one decoding given"
+ example/Escape/iso8859.html view
@@ -0,0 +1,18 @@+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">+<HTML>+<HEAD>+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">+<META HTTP-EQUIV="Content-Language" CONTENT="DE">+<META NAME="description" CONTENT="Testeingabe für Escape-Programm">+<META NAME="author" CONTENT="ich">+<META NAME="keywords" CONTENT="WWW, XML, HTML, XHTML, XML References">+<TITLE>Hennings HomePage</TITLE>+</HEAD>+<BODY>++<H1> De Bärchschafft vonne Früdrüch Schöller </H1>++<![CDATA[<gähn>]]>++</BODY>+</HTML>
+ example/Escape/iso8859.xhtml view
@@ -0,0 +1,26 @@+<?xml version="1.0" encoding="ISO-8859-1"?>+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">+<html xmlns="http://www.w3.org/1999/xhtml">+<head>+<meta name="description" content="Testeingabe für Escape-Programm" />+<meta name="author" content="ich" />+<meta name="keywords" content="WWW, XML, HTML, XHTML, XML References" />+<style type="text/css">+ body {+ color: maroon;+ background-color: yellow;+ }+</style>+<title> Märchenstunde </title>+</head>+<body>++<h1> De Bärchschafft vonne Früdrüch Schöller </h1>++<p>+<![CDATA[<gähn>]]>+</p>++</body>+</html>
+ example/Escape/utf8.html view
@@ -0,0 +1,18 @@+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">+<HTML>+<HEAD>+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">+<META HTTP-EQUIV="Content-Language" CONTENT="DE">+<META NAME="description" CONTENT="Testeingabe für Escape-Programm">+<META NAME="author" CONTENT="ich">+<META NAME="keywords" CONTENT="WWW, XML, HTML, XHTML, XML References">+<TITLE>Hennings HomePage</TITLE>+</HEAD>+<BODY>++<H1> De Bärchschafft vonne Früdrüch Schöller </H1>++<![CDATA[<gähn>]]>++</BODY>+</HTML>
+ example/Escape/utf8.xhtml view
@@ -0,0 +1,26 @@+<?xml version="1.0" encoding="UTF-8"?>+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">+<html xmlns="http://www.w3.org/1999/xhtml">+<head>+<meta name="description" content="Testeingabe für Escape-Programm" />+<meta name="author" content="ich" />+<meta name="keywords" content="WWW, XML, HTML, XHTML, XML References" />+<style type="text/css">+ body {+ color: maroon;+ background-color: yellow;+ }+</style>+<title> Märchenstunde </title>+</head>+<body>++<h1> De Bärchschafft vonne Früdrüch Schöller </h1>++<p>+<![CDATA[<gähn>]]>+</p>++</body>+</html>
+ example/Strip.hs view
@@ -0,0 +1,25 @@+{- |+Strip HTML comments, script and style tags.++> strip-html <input.xhtml >output.xhtml+-}+module Main where++import qualified Text.HTML.Tagchup.Parser as Parser+import qualified Text.HTML.Tagchup.Format as Format+import qualified Text.HTML.Tagchup.Process as Process+import qualified Text.HTML.Tagchup.Tag as Tag+import qualified Text.XML.Basic.Name.LowerCase as NameLC+import qualified Text.XML.Basic.Name as Name+++strip :: [Tag.T NameLC.T String] -> String+strip =+ flip Format.htmlOrXhtml "" .+ concatMap (either (const []) id) .+ Process.parts (Name.matchAny ["script", "style"]) .+ filter (not . Tag.isComment)++main :: IO ()+main =+ interact (strip . Parser.runSoup)
+ example/Strip/example.xhtml view
@@ -0,0 +1,32 @@+<?xml version="1.0" encoding="UTF-8"?>+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">+<html xmlns="http://www.w3.org/1999/xhtml">+<head>+<meta name="description" content="Testeingabe für Strip-Programm" />+<meta name="author" content="ich" />+<meta name="keywords" content="WWW, XML, HTML, XHTML, XML References" />+<style type="text/css">+ body {+ color: maroon;+ background-color: yellow;+ }+</style>+<title> Märchenstunde </title>+</head>+<body>++<!-- secret information -->++<h1> De Bärchschafft vonne Früdrüch Schöller </h1>++<script type="text/pseudocode">+<![CDATA[<gähn>]]>+</script>++<p>+Really irrelevant text!+</p>++</body>+</html>
example/Validate.hs view
@@ -1,24 +1,24 @@-{-+{- | Example program for TagSoup. Detects basic syntax errors like isolated ampersands and unquoted attribute values. -} module Main where -import Text.HTML.Tagchup.Parser-import qualified Text.XML.Basic.Position as Position+import qualified Text.HTML.Tagchup.Parser as Parser import qualified Text.HTML.Tagchup.PositionTag as PosTag import qualified Text.HTML.Tagchup.Tag as Tag+import qualified Text.XML.Basic.Position as Position import qualified Text.XML.Basic.Name.MixedCase as Name -import System.Environment (getArgs)+import System.Environment (getArgs, ) -import Data.Maybe (mapMaybe)+import Data.Maybe (mapMaybe, ) validate :: FilePath -> String -> String validate fileName input = let tags :: [PosTag.T Name.T String]- tags = runSoupWithPositionsName fileName input+ tags = Parser.runSoupWithPositionsName fileName input warnings = mapMaybe (\(PosTag.Cons pos tag) ->
+ src/Text/HTML/Tagchup/Character.hs view
@@ -0,0 +1,13 @@+module Text.HTML.Tagchup.Character where++import qualified Text.HTML.Basic.Character as HTMLChar+++class C char where+ fromChar :: Char -> char++instance C Char where+ fromChar = id++instance C HTMLChar.T where+ fromChar = HTMLChar.Unicode
src/Text/HTML/Tagchup/Parser.hs view
@@ -14,43 +14,21 @@ runTag, runInnerOfTag, ) where -import Text.HTML.Tagchup.Parser.Combinator- (allowFail, withDefault,- char, dropSpaces, getPos,- many, manyS, many0toN, many1toN,- many1Satisfy, readUntil,- satisfy, string,- emit, modifyEmission, )+import Text.HTML.Tagchup.Parser.Tag+ (CharType, StringType, parsePosTag, parsePosTagMergeWarnings, ) +import Text.HTML.Tagchup.Parser.Combinator (manyS, )+ import qualified Text.HTML.Tagchup.Parser.Combinator as Parser-import qualified Text.HTML.Tagchup.Parser.Status as Status+import qualified Text.HTML.Tagchup.Parser.Stream as Stream import qualified Text.HTML.Tagchup.PositionTag as PosTag import qualified Text.HTML.Tagchup.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.Tagchup.Parser.Stream as Stream--import qualified Text.HTML.Basic.Entity as HTMLEntity--import qualified Control.Monad.Exception.Synchronous as Exc--import Control.Monad.Trans.Writer (runWriterT, )-import Control.Monad.Trans.State (StateT(..), )-import Control.Monad (mplus, msum, when, liftM, )--import Data.Monoid (Monoid, mempty, mconcat, )--import qualified Data.Map as Map+import Control.Monad (liftM, ) -import Data.Tuple.HT (mapSnd, )-import Data.Char (isAlphaNum, isAscii, chr, ord, )-import Data.Maybe (fromMaybe, maybeToList, )+import Data.Maybe (fromMaybe, ) -- import qualified Numeric @@ -118,440 +96,3 @@ Name.Attribute name, Name.Tag name) => source -> [Tag.T name sink] runSoup = map PosTag.tag_ . runSoupWithPositions------ * parser parts--type Warning = (Position.T, String)--type Parser source a = Parser.Full source Warning a-type ParserEmit source a = Parser.Emitting source Warning a---parsePosTagMergeWarnings ::- (Stream.C source, StringType sink,- Name.Attribute name, Name.Tag name) =>- StateT (Status.T source) Maybe [PosTag.T name sink]-parsePosTagMergeWarnings =- liftM (\((ot,ct),warns) ->- ot :- map (\(pos,warn) -> PosTag.cons pos $ Tag.Warning warn) warns ++- maybeToList ct) $- runWriterT parsePosTag--parsePosTag ::- (Stream.C source, StringType sink,- Name.Attribute name, Name.Tag name) =>- Parser source (PosTag.T name sink, Maybe (PosTag.T name sink))-parsePosTag = do- let omitClose :: Monad m => m t -> m (t, Maybe t)- omitClose = liftM (\t -> (t, Nothing))- pos <- getPos- mplus- (do char '<'- allowFail $ withDefault- (msum $- omitClose (parseSpecialTag pos) :- omitClose (parseProcessingTag pos) :- omitClose (parseCloseTag pos) :- parseOpenTag pos :- [])- (do emitWarning pos "A '<', that is not part of a tag. Encode it as < please."- omitClose (returnTag pos (Tag.Text $ stringFromChar '<'))))- (omitClose (parseText pos))---{- |-Parsing an open tag may also emit a close tag-if the tag is self-closing, e.g. @<br/>@.--For formatting self-closing tags correctly-it would be better to emit tags in the order @open tag, close tag, warnings@.-However, if there are infinitely many warnings,-we don't know whether a self-closing slash comes-and thus whether there is a close tag or not.-This implies, that we cannot even emit the warnings.--Thus we choose the order @open tag, warnings, close tag@.--}-parseOpenTag ::- (Stream.C source, StringType sink,- Name.Attribute name, Name.Tag name) =>- Position.T ->- Parser source- (PosTag.T name sink, Maybe (PosTag.T name sink))-parseOpenTag pos =- do name <- parseName- allowFail $- do dropSpaces- tag <- returningTag pos (Tag.Open name) $- modifyEmission (restrictWarnings 10) $ many parseAttribute- liftM ((,) tag) $ withDefault- (do closePos <- getPos- string "/>"- allowFail $ liftM Just $ returnTag closePos (Tag.Close name))- (do junkPos <- getPos- readUntilTerm- (\ junk ->- emitWarningWhen- (not $ null junk)- junkPos ("Junk in opening tag: \"" ++ junk ++ "\""))- ("Unterminated open tag \"" ++ Name.toString name ++ "\"") ">"- return Nothing)--parseCloseTag ::- (Stream.C source, Name.Tag name) =>- Position.T -> Parser source (PosTag.T name sink)-parseCloseTag pos =- do char '/'- name <- parseName- allowFail $- do tag <- returnTag pos (Tag.Close name)- dropSpaces- junkPos <- getPos- readUntilTerm- (\ junk ->- emitWarningWhen- (not $ null junk)- junkPos ("Junk in closing tag: \"" ++ junk ++"\""))- ("Unterminated closing tag \"" ++ Name.toString name ++"\"") ">"- return tag--parseSpecialTag ::- (Stream.C source, Name.Tag name) =>- Position.T -> Parser source (PosTag.T name sink)-parseSpecialTag pos =- do char '!'- msum $- (do string "--"- allowFail $ readUntilTerm- (\ cmt -> returnTag pos (Tag.Comment cmt))- "Unterminated comment" "-->") :- (do string TagName.cdataString- allowFail $ readUntilTerm- (\ cdata -> returnTag pos (Tag.Special TagName.cdata cdata))- "Unterminated cdata" "]]>") :- (do name <- parseName- allowFail $- do dropSpaces- readUntilTerm- (\ info -> returnTag pos (Tag.Special name info))- ("Unterminated special tag \"" ++ Name.toString name ++ "\"") ">") :- []--parseProcessingTag ::- (Stream.C source, StringType sink,- Name.Attribute name, Name.Tag name) =>- Position.T -> Parser source (PosTag.T name sink)-parseProcessingTag pos =- do char '?'- name <- parseName- allowFail $- do dropSpaces- returningTag 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 ::- (Stream.C source, StringType sink) =>- Position.T -> Parser source (PosTag.T name sink)-parseText pos =- returningTag pos Tag.Text (parseCharAsString (const True))--- returningTag pos Tag.Text (parseCharAsString ('<'/=))--- returningTag pos Tag.Text (parseString1 ('<'/=))---parseAttribute ::- (Stream.C source, StringType sink, Name.Attribute name) =>- Parser source (Attr.T name sink)-parseAttribute =- parseName >>= \name -> allowFail $- do dropSpaces- value <-- withDefault- (string "=" >> allowFail (dropSpaces >> parseValue))- (return mempty)- dropSpaces- return $ Attr.Cons name value--parseName ::- (Stream.C source, Name.C pname) =>- Parser source 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` "_-.:")--parseValue ::- (Stream.C source, StringType sink) =>- ParserEmit source sink-parseValue =- (msum $- parseQuoted "Unterminated doubly quoted value string" '"' :- parseQuoted "Unterminated singly quoted value string" '\'' :- [])- `withDefault`- parseUnquotedValueAsString--parseUnquotedValueChar ::- (Stream.C source) =>- ParserEmit source String-parseUnquotedValueChar =- let parseValueChar =- do pos <- getPos- str <- parseUnicodeChar (not . flip elem " >\"\'")- let wrong = filter (not . isValidValueChar) str- allowFail $- emitWarningWhen (not (null wrong)) pos $- "Illegal characters in unquoted value: " ++ wrong- return str- in liftM concat $ many parseValueChar--parseUnquotedValueHTMLChar ::- (Stream.C source) =>- ParserEmit source [HTMLChar.T]-parseUnquotedValueHTMLChar =- let parseValueChar =- do pos <- getPos- hc <- parseHTMLChar (not . flip elem " >\"\'")- {- 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 liftM concat $ many parseValueChar--checkUnquotedChar :: Position.T -> HTMLChar.T -> ParserEmit source ()-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 ::- (Stream.C source, StringType sink) =>- String -> Char -> Parser source sink-parseQuoted termMsg quote =- char quote >>- (allowFail $- do str <- parseString (quote/=)- withDefault- (char quote >> return ())- (do termPos <- getPos- emitWarning termPos termMsg)- return str)--{--Instead of using 'generateTag' we could also wrap the call to 'readUntilTerm'-in 'mfix' in order to emit a tag, where some information is read later.--}-readUntilTerm ::- (Stream.C source) =>- (String -> ParserEmit source a) -> String -> String -> ParserEmit source a-readUntilTerm generateTag termWarning termPat =- do ~(termFound,str) <- readUntil termPat- result <- generateTag str- termPos <- getPos- emitWarningWhen (not termFound) termPos termWarning- return result---class CharType char where- fromChar :: Char -> char- parseChar :: (Stream.C source) => (Char -> Bool) -> Parser source [char]- parseUnquotedValue :: (Stream.C source) => ParserEmit source [char]--instance CharType Char where- fromChar = id- parseChar = parseUnicodeChar- parseUnquotedValue = parseUnquotedValueChar--instance CharType HTMLChar.T where- fromChar = HTMLChar.Unicode- parseChar = parseHTMLChar- parseUnquotedValue = parseUnquotedValueHTMLChar---class Monoid sink => StringType sink where- stringFromChar :: Char -> sink- parseCharAsString ::- (Stream.C source) =>- (Char -> Bool) -> Parser source sink- parseUnquotedValueAsString ::- (Stream.C source) =>- ParserEmit source sink--instance CharType char => StringType [char] where- stringFromChar c = [fromChar c]- parseCharAsString = parseChar- parseUnquotedValueAsString = parseUnquotedValue---parseString ::- (Stream.C source, StringType sink) =>- (Char -> Bool) -> ParserEmit source 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 source String-parseUnicodeChar p =- do pos <- getPos- x <- parseHTMLChar p- allowFail $ liftM concat $- mapM (htmlCharToString pos) x--htmlCharToString ::- Position.T -> HTMLChar.T -> ParserEmit source 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)--{- |-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 source [HTMLChar.T]-parseHTMLChar p =- do pos <- getPos- c <- satisfy p- allowFail $- if c=='&'- then- withDefault- (do ent <-- mplus- (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 source [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.Unicode '&'])- else return [HTMLChar.Unicode c]--}---restrictWarnings :: Int -> [Warning] -> [Warning]-restrictWarnings n =- uncurry (++) .- mapSnd- (\rest ->- case rest of- (pos, _) : _ ->- [(pos, "further warnings suppressed")]- _ -> []) .- splitAt n----- these functions have intentionally restricted types--emitWarningWhen :: Bool -> Position.T -> String -> ParserEmit source ()-emitWarningWhen cond pos msg =- when cond $ emitWarning pos msg--emitWarning :: Position.T -> String -> ParserEmit source ()-emitWarning = curry emit--returnTag ::- Position.T ->- Tag.T name sink ->- ParserEmit source (PosTag.T name sink)-returnTag p t = return (PosTag.cons p t)--returningTag ::- (Monad m) =>- Position.T ->- (a -> Tag.T name sink) ->- m a ->- m (PosTag.T name sink)-returningTag pos f =- liftM (PosTag.cons pos . f)
+ src/Text/HTML/Tagchup/Parser/Tag.hs view
@@ -0,0 +1,472 @@+module Text.HTML.Tagchup.Parser.Tag where++import Text.HTML.Tagchup.Parser.Combinator+ (allowFail, withDefault,+ char, dropSpaces, getPos,+ many, many0toN, many1toN,+ many1Satisfy, readUntil,+ satisfy, string,+ emit, modifyEmission, )++import qualified Text.HTML.Tagchup.Parser.Combinator as Parser+import qualified Text.HTML.Tagchup.Parser.Status as Status+import qualified Text.HTML.Tagchup.Parser.Stream as Stream++import qualified Text.HTML.Tagchup.PositionTag as PosTag+import qualified Text.HTML.Tagchup.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.Tagchup.Character as Chr+import Text.HTML.Tagchup.Character (fromChar, )++import qualified Text.HTML.Basic.Entity as HTMLEntity++import qualified Control.Monad.Exception.Synchronous as Exc++import Control.Monad.Trans.Writer (runWriterT, )+import Control.Monad.Trans.State (StateT(..), )+import Control.Monad (mplus, msum, when, liftM, )++import Data.Monoid (Monoid, mempty, mconcat, )++import qualified Data.Map as Map++import Data.Tuple.HT (mapSnd, )+import Data.Char (isAlphaNum, chr, ord, )+import Data.Maybe (maybeToList, )++-- import qualified Numeric++type Warning = (Position.T, String)++type Parser source a = Parser.Full source Warning a+type ParserEmit source a = Parser.Emitting source Warning a+++parsePosTagMergeWarnings ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name) =>+ StateT (Status.T source) Maybe [PosTag.T name sink]+parsePosTagMergeWarnings =+ liftM (\((ot,ct),warns) ->+ ot :+ map (\(pos,warn) -> PosTag.cons pos $ Tag.Warning warn) warns +++ maybeToList ct) $+ runWriterT parsePosTag++parsePosTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name) =>+ Parser source (PosTag.T name sink, Maybe (PosTag.T name sink))+parsePosTag = do+ let omitClose :: Monad m => m t -> m (t, Maybe t)+ omitClose = liftM (\t -> (t, Nothing))+ pos <- getPos+ mplus+ (do char '<'+ allowFail $ withDefault+ (msum $+ omitClose (parseSpecialTag pos) :+ omitClose (parseProcessingTag pos) :+ omitClose (parseCloseTag pos) :+ parseOpenTag pos :+ [])+ (do emitWarning pos "A '<', that is not part of a tag. Encode it as < please."+ omitClose (returnTag pos (Tag.Text $ stringFromChar '<'))))+ (omitClose (parseText pos))+++{- |+Parsing an open tag may also emit a close tag+if the tag is self-closing, e.g. @<br/>@.++For formatting self-closing tags correctly+it would be better to emit tags in the order @open tag, close tag, warnings@.+However, if there are infinitely many warnings,+we don't know whether a self-closing slash comes+and thus whether there is a close tag or not.+This implies, that we cannot even emit the warnings.++Thus we choose the order @open tag, warnings, close tag@.+-}+parseOpenTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name) =>+ Position.T ->+ Parser source+ (PosTag.T name sink, Maybe (PosTag.T name sink))+parseOpenTag pos =+ do name <- parseName+ allowFail $+ do dropSpaces+ tag <- returningTag pos (Tag.Open name) $+ modifyEmission (restrictWarnings 10) $ many parseAttribute+ liftM ((,) tag) $ withDefault+ (do closePos <- getPos+ string "/>"+ allowFail $ liftM Just $ returnTag closePos (Tag.Close name))+ (do junkPos <- getPos+ readUntilTerm+ (\ junk ->+ emitWarningWhen+ (not $ null junk)+ junkPos ("Junk in opening tag: \"" ++ junk ++ "\""))+ ("Unterminated open tag \"" ++ Name.toString name ++ "\"") ">"+ return Nothing)++parseCloseTag ::+ (Stream.C source, Name.Tag name) =>+ Position.T -> Parser source (PosTag.T name sink)+parseCloseTag pos =+ do char '/'+ name <- parseName+ allowFail $+ do tag <- returnTag pos (Tag.Close name)+ dropSpaces+ junkPos <- getPos+ readUntilTerm+ (\ junk ->+ emitWarningWhen+ (not $ null junk)+ junkPos ("Junk in closing tag: \"" ++ junk ++"\""))+ ("Unterminated closing tag \"" ++ Name.toString name ++"\"") ">"+ return tag++parseSpecialTag ::+ (Stream.C source, Name.Tag name) =>+ Position.T -> Parser source (PosTag.T name sink)+parseSpecialTag pos =+ do char '!'+ msum $+ (do string "--"+ allowFail $ readUntilTerm+ (\ cmt -> returnTag pos (Tag.Comment cmt))+ "Unterminated comment" "-->") :+ (do string TagName.cdataString+ allowFail $ readUntilTerm+ (\ cdata -> returnTag pos (Tag.cdata cdata))+ "Unterminated cdata" "]]>") :+ (do name <- parseName+ allowFail $+ do dropSpaces+ readUntilTerm+ (\ info -> returnTag pos (Tag.Special name info))+ ("Unterminated special tag \"" ++ Name.toString name ++ "\"") ">") :+ []++parseProcessingTag ::+ (Stream.C source, StringType sink,+ Name.Attribute name, Name.Tag name) =>+ Position.T -> Parser source (PosTag.T name sink)+parseProcessingTag pos =+ do char '?'+ name <- parseName+ allowFail $+ do dropSpaces+ returningTag 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 ::+ (Stream.C source, StringType sink) =>+ Position.T -> Parser source (PosTag.T name sink)+parseText pos =+ returningTag pos Tag.Text (parseCharAsString (const True))+-- returningTag pos Tag.Text (parseCharAsString ('<'/=))+-- returningTag pos Tag.Text (parseString1 ('<'/=))+++parseAttribute ::+ (Stream.C source, StringType sink, Name.Attribute name) =>+ Parser source (Attr.T name sink)+parseAttribute =+ parseName >>= \name -> allowFail $+ do dropSpaces+ value <-+ withDefault+ (string "=" >> allowFail (dropSpaces >> parseValue))+ (return mempty)+ dropSpaces+ return $ Attr.Cons name value++parseName ::+ (Stream.C source, Name.C pname) =>+ Parser source pname+parseName =+ liftM Name.fromString $+ many1Satisfy (\c -> isAlphaNum c || c `elem` "_-.:")++parseValue ::+ (Stream.C source, StringType sink) =>+ ParserEmit source sink+parseValue =+ (msum $+ parseQuoted "Unterminated doubly quoted value string" '"' :+ parseQuoted "Unterminated singly quoted value string" '\'' :+ [])+ `withDefault`+ parseUnquotedValueAsString++parseUnquotedValueChar ::+ (Stream.C source) =>+ ParserEmit source String+parseUnquotedValueChar =+ let parseValueChar =+ do pos <- getPos+ str <- parseUnicodeChar (not . flip elem " >\"\'")+ let wrong = filter (not . isValidValueChar) str+ allowFail $+ emitWarningWhen (not (null wrong)) pos $+ "Illegal characters in unquoted value: " ++ wrong+ return str+ in liftM concat $ many parseValueChar++parseUnquotedValueHTMLChar ::+ (Stream.C source) =>+ ParserEmit source [HTMLChar.T]+parseUnquotedValueHTMLChar =+ let parseValueChar =+ do pos <- getPos+ hc <- parseHTMLChar (not . flip elem " >\"\'")+ {- 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 liftM concat $ many parseValueChar++checkUnquotedChar :: Position.T -> HTMLChar.T -> ParserEmit source ()+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 ::+ (Stream.C source, StringType sink) =>+ String -> Char -> Parser source sink+parseQuoted termMsg quote =+ char quote >>+ (allowFail $+ do str <- parseString (quote/=)+ withDefault+ (char quote >> return ())+ (do termPos <- getPos+ emitWarning termPos termMsg)+ return str)++{-+Instead of using 'generateTag' we could also wrap the call to 'readUntilTerm'+in 'mfix' in order to emit a tag, where some information is read later.+-}+readUntilTerm ::+ (Stream.C source) =>+ (String -> ParserEmit source a) -> String -> String -> ParserEmit source a+readUntilTerm generateTag termWarning termPat =+ do ~(termFound,str) <- readUntil termPat+ result <- generateTag str+ termPos <- getPos+ emitWarningWhen (not termFound) termPos termWarning+ return result+++class Chr.C char => CharType char where+ parseChar :: (Stream.C source) => (Char -> Bool) -> Parser source [char]+ parseUnquotedValue :: (Stream.C source) => ParserEmit source [char]++instance CharType Char where+ parseChar = parseUnicodeChar+ parseUnquotedValue = parseUnquotedValueChar++instance CharType HTMLChar.T where+ parseChar = parseHTMLChar+ parseUnquotedValue = parseUnquotedValueHTMLChar+++class Monoid sink => StringType sink where+ stringFromChar :: Char -> sink+ parseCharAsString ::+ (Stream.C source) =>+ (Char -> Bool) -> Parser source sink+ parseUnquotedValueAsString ::+ (Stream.C source) =>+ ParserEmit source sink++instance CharType char => StringType [char] where+ stringFromChar c = [fromChar c]+ parseCharAsString = parseChar+ parseUnquotedValueAsString = parseUnquotedValue+++parseString ::+ (Stream.C source, StringType sink) =>+ (Char -> Bool) -> ParserEmit source 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 source String+parseUnicodeChar p =+ do pos <- getPos+ x <- parseHTMLChar p+ allowFail $ liftM concat $+ mapM (htmlCharToString pos) x++htmlCharToString ::+ Position.T -> HTMLChar.T -> ParserEmit source 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)++{- |+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 source [HTMLChar.T]+parseHTMLChar p =+ do pos <- getPos+ c <- satisfy p+ allowFail $+ if c=='&'+ then+ withDefault+ (do ent <-+ mplus+ (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 source [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.Unicode '&'])+ else return [HTMLChar.Unicode c]+-}+++restrictWarnings :: Int -> [Warning] -> [Warning]+restrictWarnings n =+ uncurry (++) .+ mapSnd+ (\rest ->+ case rest of+ (pos, _) : _ ->+ [(pos, "further warnings suppressed")]+ _ -> []) .+ splitAt n+++-- these functions have intentionally restricted types++emitWarningWhen :: Bool -> Position.T -> String -> ParserEmit source ()+emitWarningWhen cond pos msg =+ when cond $ emitWarning pos msg++emitWarning :: Position.T -> String -> ParserEmit source ()+emitWarning = curry emit++returnTag ::+ Position.T ->+ Tag.T name sink ->+ ParserEmit source (PosTag.T name sink)+returnTag p t = return (PosTag.cons p t)++returningTag ::+ (Monad m) =>+ Position.T ->+ (a -> Tag.T name sink) ->+ m a ->+ m (PosTag.T name sink)+returningTag pos f =+ liftM (PosTag.cons pos . f)
src/Text/HTML/Tagchup/PositionTag.hs view
@@ -1,5 +1,6 @@ module Text.HTML.Tagchup.PositionTag where +import qualified Text.HTML.Tagchup.Character as Chr import qualified Text.HTML.Tagchup.Tag as Tag import qualified Text.XML.Basic.Name as Name import qualified Text.XML.Basic.Position as Position@@ -8,10 +9,11 @@ import Data.Monoid (Monoid, mempty, mappend, ) import qualified Data.Accessor.Basic as Accessor+import qualified Control.Applicative as App import Data.Foldable (Foldable(foldMap), ) import Data.Traversable (Traversable(sequenceA), )-import Control.Applicative (liftA, )+import Control.Applicative (Applicative, ) data T name string =@@ -45,6 +47,13 @@ (T name0 string0 -> T name1 string1) lift f (Cons p t) = Cons p (f t) +liftA ::+ Applicative f =>+ (Tag.T name0 string0 -> f (Tag.T name1 string1)) ->+ (T name0 string0 -> f (T name1 string1))+liftA f (Cons p t) = App.liftA (Cons p) (f t)++ instance Functor (T name) where fmap f = lift (fmap f) @@ -52,12 +61,12 @@ foldMap f = foldMap f . tag_ instance Traversable (T name) where- sequenceA (Cons p t) = liftA (Cons p) $ sequenceA t+ sequenceA (Cons p t) = App.liftA (Cons p) $ sequenceA t textFromCData ::- (Name.Tag name) =>- T name String -> T name String+ (Name.Tag name, Chr.C char) =>+ T name [char] -> T name [char] textFromCData = lift Tag.textFromCData
src/Text/HTML/Tagchup/Process.hs view
@@ -1,32 +1,126 @@-module Text.HTML.Tagchup.Process where+module Text.HTML.Tagchup.Process (+ Encoding, Encoded,+ evalDecodeAdaptive, decodeAdaptive, decodeTagAdaptive,+ getXMLEncoding,+ findMetaEncoding,+ getMetaHTTPHeaders,+ getHeadTags,+ partAttrs,+ parts,+ ) where import qualified Text.HTML.Tagchup.Tag as Tag import qualified Text.HTML.Tagchup.Tag.Match as Match import qualified Text.XML.Basic.Attribute as Attr import qualified Text.XML.Basic.Name as Name+import qualified Text.XML.Basic.Tag as TagX+import qualified Text.HTML.Basic.Tag as TagH+import qualified Text.HTML.Basic.Character as HTMLChar+import qualified Text.HTML.Basic.String as HTMLString +import Text.HTML.Basic.String (Encoded, )++import Control.Monad.Trans.State (State, put, get, evalState, )+import Data.Traversable (traverse, )+ import qualified Data.Char as Char-import Data.List.HT (viewL, takeWhileRev, )-import Data.Tuple.HT (mapFst, )+import Data.List.HT (viewL, ) import Data.Maybe (fromMaybe, mapMaybe, )-import Control.Monad (guard, liftM2, )+import Control.Monad.HT ((<=<), )+import Control.Monad (msum, mplus, ) -- * analyse soup -{--Rather the same as wraxml:HTML.Tree.findMetaEncoding++type Encoding = String++++evalDecodeAdaptive ::+ State (Encoded -> String) a -> a+evalDecodeAdaptive =+ flip evalState id++{- |+Selects a decoder dynamically according+to xml-encoding and meta-http-equiv tags.+The @?xml@ tag should only appear at the beginning of a document,+but we respect it at every occurence.++> import qualified Text.XML.HXT.DOM.Unicode as Unicode++> evalDecodeAdaptive .+> decodeAdaptive+> (maybe Unicode.latin1ToUnicode (fst.) .+> Unicode.getDecodingFct) -}+decodeAdaptive ::+ (Name.Attribute name, Name.Tag name) =>+ (Encoding -> Encoded -> String) ->+ [Tag.T name [HTMLChar.T]] ->+ State (Encoded -> String) [Tag.T name String]+decodeAdaptive getDecoder =+ traverse (decodeTagAdaptive getDecoder)++{- |+@decodeTagAdaptive decoderSelector tag@ generates a state monad,+with a decoder as state.+It decodes encoding specific byte sequences+using the current decoder+and XML references using a fixed table.+-}+decodeTagAdaptive ::+ (Name.Attribute name, Name.Tag name) =>+ (Encoding -> Encoded -> String) ->+ Tag.T name [HTMLChar.T] ->+ State (Encoded -> String) (Tag.T name String)+decodeTagAdaptive getDecoder tag0 =+ do decoder <- get+ let tag1 =+ -- this is less elegant than using maybeCData but lazier+ maybe+ (fmap (HTMLString.decode decoder) tag0)+ (\(name, s) ->+ Tag.special name $+ if TagH.cdataName == name+ then decoder s+ else s)+ (Tag.maybeSpecial tag0)+ maybe+ (return ())+ (put . getDecoder) $+ mplus+ (uncurry TagH.maybeMetaEncoding =<<+ Tag.maybeOpen tag1)+ (uncurry TagX.maybeXMLEncoding =<<+ Tag.maybeProcessing tag1)+ return tag1++{- |+Check whether the first tag is an @xml@ processing instruction tag+and return the value of its @encoding@ attribute.+-}+getXMLEncoding ::+ (Name.Tag name, Name.Attribute name) =>+ [Tag.T name String] -> Maybe String+getXMLEncoding tags =+ do (t,_) <- viewL tags+ uncurry TagX.maybeXMLEncoding =<< Tag.maybeProcessing t++{- |+Rather the same as @wraxml:HTML.Tree.findMetaEncoding@+-} findMetaEncoding :: (Name.Tag name, Name.Attribute name) => [Tag.T name String] -> Maybe String findMetaEncoding =- fmap (map Char.toLower . takeWhileRev ('='/=)) .- lookup "content-type" .- map (mapFst (map Char.toLower)) .- getMetaHTTPHeaders+ msum .+ map (uncurry TagH.maybeMetaEncoding <=< Tag.maybeOpen) .+ getHeadTags + {- | Extract META tags which contain HTTP-EQUIV attribute and present these values like HTTP headers.@@ -35,23 +129,18 @@ (Name.Tag name, Name.Attribute 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)) .+ mapMaybe (uncurry TagH.maybeMetaHTTPHeader <=< Tag.maybeOpen) . getHeadTags + getHeadTags :: (Name.Tag name, Name.Attribute 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))+ dropWhile (not . Match.openNameLit "head") .+ takeWhile (not . Match.openNameLit "body") -- * transform soup
src/Text/HTML/Tagchup/Tag.hs view
@@ -17,11 +17,13 @@ mapText, mapTextA, ) where +import qualified Text.HTML.Tagchup.Character as Chr+ 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 Text.XML.Basic.Tag (Name(Name), cdataName, ) import Data.Tuple.HT (mapFst, ) import Data.Maybe (mapMaybe, fromMaybe, )@@ -134,7 +136,7 @@ Fmt.angle $ Fmt.exclam . Fmt.name name .- if cdata == name+ if cdataName == name then showString str . showString "]]" else Fmt.blank . showString str Processing name p ->@@ -177,6 +179,9 @@ special :: Name name -> String -> T name string special = Special +cdata :: (Name.Tag name) => String -> T name string+cdata = special cdataName+ processing :: Name name -> PI.T name string -> T name string processing = Processing @@ -234,14 +239,14 @@ isCData :: (Name.Tag name) => T name string -> Bool-isCData tag = case tag of (Special name _) -> cdata == name; _ -> False+isCData tag = case tag of (Special name _) -> cdataName == name; _ -> False maybeCData :: (Name.Tag name) => T name string -> Maybe String maybeCData tag = do (name, content) <- maybeSpecial tag- guard (cdata == name)+ guard (cdataName == name) return content @@ -267,18 +272,29 @@ Replace CDATA sections by plain text. -} textFromCData ::+ (Name.Tag name, Chr.C char) =>+ T name [char] -> T name [char]+textFromCData t =+ fromMaybe t $+ do (name, content) <- maybeSpecial t+ guard (cdataName == name)+ return $ Text $ map Chr.fromChar content++{-+textFromCData :: (Name.Tag name) => T name String -> T name String textFromCData t = fromMaybe t $ do (name, content) <- maybeSpecial t- guard (cdata == name)+ guard (cdataName == name) return $ Text content+-} {- case t of Special name text ->- if cdata == name+ if cdataName == name then Text text else t _ -> t@@ -316,7 +332,7 @@ Text s -> Text $ f s Special name s -> Special name $- if cdata == name+ if cdataName == name then f s else s _ -> t@@ -330,7 +346,7 @@ Text s -> liftA Text $ f s Special name s -> liftA (Special name) $- if cdata == name+ if cdataName == name then f s else pure s _ -> pure t
src/Text/HTML/Tagchup/Tag/Match.hs view
@@ -67,13 +67,13 @@ Name.match attrName name && pAttrValue value)) --- | Check if the 'Tag.T' is 'Tag.Open' and matches the given name+-- | Check whether the 'Tag.T' is 'Tag.Open' and matches the given name openNameLit :: (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+-- | Check whether the 'Tag.T' is 'Tag.Close' and matches the given name closeNameLit :: (Name.Tag name) => String -> Tag.T name string -> Bool
tagchup.cabal view
@@ -1,5 +1,5 @@ Name: tagchup-Version: 0.3.1+Version: 0.4 License: GPL License-File: LICENSE Author: Henning Thielemann <tagchup@henning-thielemann.de>@@ -22,45 +22,97 @@ . The name Tagchup resembles Ketchup. Build-Type: Simple-Build-Depends:- xml-basic >=0.1 && <0.2,- transformers >=0.0 && <0.2,- explicit-exception >=0.1 && <0.2,- 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.Tagchup.Parser- Text.HTML.Tagchup.Format- Text.HTML.Tagchup.Tag- Text.HTML.Tagchup.Tag.Match- Text.HTML.Tagchup.PositionTag- Text.HTML.Tagchup.Process+Cabal-Version: >= 1.6 -Other-Modules:- Text.HTML.Tagchup.Parser.Combinator- Text.HTML.Tagchup.Parser.Core- Text.HTML.Tagchup.Parser.Status- Text.HTML.Tagchup.Parser.Stream+Data-Files:+ example/Escape/iso8859.xhtml+ example/Escape/iso8859.html+ example/Escape/utf8.xhtml+ example/Escape/utf8.html+ example/Strip/example.xhtml -Executable: tagchuptest-GHC-Options: -Wall-Hs-Source-Dirs: src, .-Other-Modules:- Text.HTML.Tagchup.Test-Main-Is: example/Test.hs+Source-Repository head+ Type: darcs+ Location: http://code.haskell.org/~thielema/tagchup/ -Executable: tagchupspeed-GHC-Options: -Wall-Hs-Source-Dirs: src, .-Build-Depends: old-time >=1.0 && <1.1-Main-Is: example/Speed.hs+Source-Repository this+ Type: darcs+ Location: http://code.haskell.org/~thielema/tagchup/+ Tag: 0.4 -Executable: validate-tagchup-GHC-Options: -Wall-Hs-Source-Dirs: src, .-Main-Is: example/Validate.hs+Flag buildExamples+ description: Build example executables+ default: False++Flag buildTests+ description: Build test suite+ default: False+++Library+ Build-Depends:+ xml-basic >=0.1.1 && <0.2,+ transformers >=0.0 && <0.2,+ explicit-exception >=0.1 && <0.2,+ 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.Tagchup.Parser+ Text.HTML.Tagchup.Format+ Text.HTML.Tagchup.Tag+ Text.HTML.Tagchup.Tag.Match+ Text.HTML.Tagchup.PositionTag+ Text.HTML.Tagchup.Process++ Other-Modules:+ Text.HTML.Tagchup.Character+ Text.HTML.Tagchup.Parser.Combinator+ Text.HTML.Tagchup.Parser.Core+ Text.HTML.Tagchup.Parser.Status+ Text.HTML.Tagchup.Parser.Stream+ Text.HTML.Tagchup.Parser.Tag++Executable tagchuptest+ If !flag(buildTests)+ Buildable: False+ GHC-Options: -Wall+ Hs-Source-Dirs: src, example+ Other-Modules: Text.HTML.Tagchup.Test+ Main-Is: Test.hs++Executable tagchupspeed+ If flag(buildTests)+ Build-Depends: old-time >=1.0 && <1.1+ Else+ Buildable: False+ GHC-Options: -Wall+ Hs-Source-Dirs: src, example+ Main-Is: Speed.hs++Executable validate-tagchup+ If !flag(buildExamples)+ Buildable: False+ GHC-Options: -Wall+ Hs-Source-Dirs: src, example+ Main-Is: Validate.hs++Executable escape-html+ If flag(buildExamples)+ Build-Depends: hxt >=8.1 && <8.2+ Else+ Buildable: False+ GHC-Options: -Wall+ Hs-Source-Dirs: src, example+ Main-Is: Escape.hs++Executable strip-html+ If !flag(buildExamples)+ Buildable: False+ GHC-Options: -Wall+ Hs-Source-Dirs: src, example+ Main-Is: Strip.hs