HandsomeSoup (empty) → 0.1
raw patch · 7 files changed
+329/−0 lines, 7 filesdep +HTTPdep +MaybeTdep +basesetup-changed
Dependencies added: HTTP, MaybeT, base, containers, hxt, mtl, network, parsec, transformers
Files
- HandsomeSoup.cabal +61/−0
- LICENSE +30/−0
- README.markdown +48/−0
- Setup.hs +2/−0
- Text/CSS/Parser.hs +106/−0
- Text/CSS/Utils.hs +12/−0
- Text/HandsomeSoup.hs +70/−0
+ HandsomeSoup.cabal view
@@ -0,0 +1,61 @@+-- HandsomeSoup.cabal auto-generated by cabal init. For additional+-- options, see+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.+-- The name of the package.+Name: HandsomeSoup++-- The package version. See the Haskell package versioning policy+-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for+-- standards guiding when and how versions should be incremented.+Version: 0.1++-- A short (one-line) description of the package.+Synopsis: Work with HTML more easily in HXT++-- A longer description of the package.+Description: See examples and full readme on the Github page: https:\/\/github.com\/egonSchiele\/HandsomeSoup++-- URL for the project homepage or repository.+Homepage: https://github.com/egonSchiele/HandsomeSoup++-- The license under which the package is released.+License: BSD3++-- The file containing the license text.+License-file: LICENSE++-- The package author(s).+Author: Aditya Bhargava++-- An email address to which users can send suggestions, bug reports,+-- and patches.+Maintainer: bluemangroupie@gmail.com++-- A copyright notice.+-- Copyright: ++Category: Text++Build-type: Simple++-- Extra files to be distributed with the package, such as examples or+-- a README.+Extra-source-files: README.markdown++-- Constraint on the version of Cabal needed to build this package.+Cabal-version: >=1.2+++Library+ -- Modules exported by the library.+ Exposed-modules: Text.HandsomeSoup, Text.CSS.Parser+ + -- Packages needed in order to build this package.+ Build-depends: base < 5, transformers, HTTP, parsec, containers, mtl, MaybeT, hxt, network+ + -- Modules not exported by this package.+ Other-modules: Text.CSS.Utils+ + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: +
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2012, Aditya Bhargava++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Aditya Bhargava nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.markdown view
@@ -0,0 +1,48 @@+# HandsomeSoup++Current Status: very very pre-alpha. Usable but buggy.++HandsomeSoup is the library I wish I had when I started parsing HTML in Haskell.++It is built on top of [HXT](http://www.fh-wedel.de/~si/HXmlToolbox/) and adds a few functions that make is easier to work with HTML.++Most importantly, it adds CSS selectors to HXT. The goal of HandsomeSoup is to be a complete CSS2 parser for HXT (it is very close to this right now).++## Example++[Nokogiri](http://nokogiri.org/), the HTML parser for Ruby, has an example showing how to scrape Google search results. This is easy in HandsomeSoup:++ main = do+ doc <- fromUrl "http://www.google.com/search?q=egon+schiele"+ links <- runX $ doc >>> css "h3.r a" ! "href"+ mapM_ putStrLn links++## What can HandsomeSoup do for you?++### Easily parse an online page using `fromUrl`++ doc <- fromUrl "http://example.com"++### Or a local page using `parseHtml`++ contents <- readFile [filename]+ doc <- parseHtml contents++### Easily extract elements using `css`++Here are some valid selectors:++ doc <<< css "a"+ doc <<< css "*"+ doc <<< css "a#link1"+ doc <<< css "a.foo"+ doc <<< css "p > a"+ doc <<< css "#container h1"+ doc <<< css "img[width]"+ doc <<< css "img[width=400]"+ doc <<< css "a[class~=bar]"++### Easily get attributes using `(!)`++ doc <<< css "img" ! "src"+ doc <<< css "a" ! "href"
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/CSS/Parser.hs view
@@ -0,0 +1,106 @@+module Text.CSS.Parser where++import Text.Parsec+import qualified Data.Functor.Identity as I+import Data.List+import Text.CSS.Utils++-- if no tag name was given, sName will be set to '*'+-- attrs are (attr name, attr value).+-- if attr value is the empty string, we just check to+-- make sure that the element has that attribute.+-- if the attr value is prefixed with a '~', we treat+-- that attribute as a list of words separated by a space+-- and make sure that at least one of those words matches.+data Selector = Selector { sName :: String, sAttrs :: [(String,String)], spseudoSelectores :: [String] } | Space | ChildOf | FollowedBy deriving (Show)++-- pretty printers for debugging+pp Space = "<space>"+pp ChildOf = "<child of>"+pp FollowedBy = "<followed by>"+pp (Selector name attrs pseudo) = show name ++ ":" ++ showMap attrs ++ ", " ++ show pseudo+ where showMap m = ("{" ++ (foldl (\acc (k,v) -> acc ++ (show k) ++ ":" ++ (show v) ++ ", ") "" m)) ++ "}"++{- TYPE SELECTORS FOLLOW -}++-- | selects a tag name, like @ h1 @+typeSelector :: ParsecT [Char] u I.Identity [Char]+typeSelector = many1 alphaNum++-- | universal selector, selects @ * @+universalSelector :: ParsecT [Char] u I.Identity String+universalSelector = string "*"++{- SECONDARY SELECTORS FOLLOW -}++-- | selects a pseudo-element or pseudo-class, like @ :link @, @ :first-child @ etc.+pseudoSelector :: ParsecT [Char] u I.Identity [Char]+pseudoSelector = char ':' >> many1 (alphaNum <|> oneOf "-()")++-- | class selector, selects @ .foo @+classSelector :: ParsecT [Char] u I.Identity ([Char], [Char])+classSelector = do+ val <- char '.' >> many1 alphaNum+ return ("class", '~':val)++-- | id selector, selects @ #foo @+idSelector :: ParsecT [Char] u I.Identity ([Char], [Char])+idSelector = do+ val <- char '#' >> many1 alphaNum+ return ("id", val)++-- | selects attributes, like @ [id] @ (element must have id) or @ [id=foo] @ (element must have id foo).+attributeSelector :: ParsecT [Char] u I.Identity ([Char], [Char])+attributeSelector = do+ contents <- between (char '[') (char ']') (many1 (alphaNum <|> oneOf "~="))+ if "~=" `isInfixOf` contents + then return $ (\(a, b) -> (a, '~':b)) $ splitOn "~=" contents+ else if '=' `elem` contents+ then return $ splitOn "=" contents+ else return (contents, "")++-- | selector for everything after the type except pseudoSelectores+secondarySelector = many1 (classSelector <|> idSelector <|> attributeSelector)++{- COMBINATOR SELECTORS FOLLOW -}++space_ = do+ many1 $ string " "+ return Space++childOf = do+ spaces >> string ">" >> spaces+ return ChildOf++followedBy = do+ spaces >> string "+" >> spaces+ return FollowedBy++{- SIMPLE SELECTORS FOLLOW -}++-- | selects a tagname followed by one or more secondary selectors+-- example: @ a.foo @, @ *#hello @, @ h1 @ etc+simpleSelectorTag :: ParsecT [Char] u I.Identity Selector+simpleSelectorTag = do+ tagName <- typeSelector <|> universalSelector+ attrs <- secondarySelector <|> return []+ pseudo <- many1 pseudoSelector <|> return []+ return $ Selector tagName attrs pseudo++-- | selects one or more secondary selectors+-- and automatically prepends the universal selector to them.+-- example: @ .foo @, @ #hello @ etc+simpleSelectorNoTag = do+ attrs <- secondarySelector+ pseudo <- many1 pseudoSelector <|> return []+ return $ Selector "*" attrs pseudo++-- | A simple selector is either a type selector or universal selector followed immediately by zero or more attribute selectors, ID selectors, or pseudo-classes, in any order.+simpleSelector :: ParsecT [Char] u I.Identity Selector+simpleSelector = simpleSelectorTag <|> simpleSelectorNoTag <|> try childOf <|> try followedBy <|> space_++-- | One or more simple selectors separated by combinators. +selector :: ParsecT [Char] u I.Identity [[Selector]]+selector = many1 simpleSelector `sepBy` (spaces >> string "," >> spaces)++css = parse selector ""
+ Text/CSS/Utils.hs view
@@ -0,0 +1,12 @@+module Text.CSS.Utils where+import Data.List++-- like break, except don't keep the element you broke on.+-- and it takes a list as the thing to break on.+splitOn a xs = _splitOn a "" xs++_splitOn _ begin [] = (begin, [])+_splitOn a begin end@(x:xs)+ | a `isPrefixOf` end = (begin, drop (length a) end)+ | otherwise = _splitOn a (begin ++ [x]) xs+
+ Text/HandsomeSoup.hs view
@@ -0,0 +1,70 @@+module Text.HandsomeSoup (openUrl, fromUrl, parseHtml, (!), css) where++import Text.XML.HXT.Core+import Network.HTTP+import Network.URI+import Data.Tree.NTree.TypeDefs+import Control.Monad.Maybe+import Control.Monad.Trans+import Data.Maybe+import Text.Parsec+import qualified Data.Map as M+import Data.Monoid (mconcat)+import qualified Data.Functor.Identity as I+import qualified Debug.Trace as D+import Data.List+import Control.Monad+import Text.CSS.Parser hiding (css)++-- | Helper function for getting page content. Example:+--+-- > contents <- runMaybeT $ openUrl "http://foo.com"+openUrl :: String -> MaybeT IO String+openUrl url = case parseURI url of+ Nothing -> fail "couldn't parse url"+ Just u -> liftIO (getResponseBody =<< simpleHTTP (mkRequest GET u))++-- | Given a url, returns a document. Example:+--+-- > doc <- fromUrl "http://foo.com"+fromUrl :: String -> IO (IOSArrow XmlTree (NTree XNode))+fromUrl url = do+ contents <- runMaybeT $ openUrl url+ return $ parseHtml (fromMaybe "" contents)++-- | Given a string, parses it and returns a document. Example:+--+-- > doc = parseHtml "<h1>hello!</h1>"+parseHtml :: String -> IOSArrow XmlTree (NTree XNode)+parseHtml = readString [withParseHTML yes, withWarnings no]++-- | Shortcut for getting attributes. Example:+--+-- > doc >>> css "a" ! "href"+(!) :: ArrowXml cat => cat a XmlTree -> String -> cat a String+(!) a str = a >>> getAttrValue str++-- | A css selector for getting elements from a document. Example:+--+-- > doc >>> css "#menu li"+css :: ArrowXml a => [Char] -> a (NTree XNode) (NTree XNode)+css tag = case (parse selector "" tag) of+ Left err -> D.trace (show err) this+ Right x -> fromSelectors x++-- | Used internally. works on a selector (i.e a list of simple selectors)+fromSelectors sel@(s:selectors) = foldl (\acc selector -> acc <+> _fromSelectors selector) (_fromSelectors s) selectors++-- | Used internally. works on simple selectors and their combinators+_fromSelectors (s:selectors) = foldl (\acc selector -> make acc selector) (make this s) selectors+ where + make acc sel@(Selector name attrs pseudo)+ | name == "*" = acc >>> (multi this >>> makeAttrs attrs)+ | otherwise = acc >>> (multi $ hasName name >>> makeAttrs attrs)+ make acc Space = acc >>> multi getChildren+ make acc ChildOf = acc >>> getChildren+ makeAttrs (a:attrs) = foldl (\acc attr -> acc >>> makeAttr attr) (makeAttr a) attrs+ makeAttrs [] = this+ makeAttr (name, "") = hasAttr name+ makeAttr (name, '~':value) = hasAttrValue name (elem value . words)+ makeAttr (name, value) = hasAttrValue name (==value)