packages feed

dom-selector (empty) → 0.1.0.0

raw patch · 9 files changed

+694/−0 lines, 9 filesdep +QuickCheckdep +basedep +blaze-htmlsetup-changed

Dependencies added: QuickCheck, base, blaze-html, containers, html-conduit, parsec, template-haskell, text, th-lift, xml-conduit

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012, Nebuta Lab++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 Nebuta Lab 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/XML/Scraping.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE OverloadedStrings #-}++-- |Scraping (innerHTML/innerText) and modification (node removal) functions.+module Text.XML.Scraping where++import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Text.XML as X+import Text.XML.Cursor+import Data.String+import qualified Text.HTML.DOM as H+import qualified Data.Map as M+import Data.List+import Data.Maybe+import System.Environment (getArgs)+import Text.Blaze.Html as Bl+import Text.Blaze.Html.Renderer.Text+import Data.Text.Lazy  (fromStrict,toStrict, unpack)++import Text.XML.Selector+import Text.XML.Selector.Types++-- * InnerHTML / InnerText++-- |Get ''innerHTML'' from a list of cursors.+innerHtml :: [Cursor] -> TL.Text+innerHtml cs = renderNodes $ map node $ concatMap child cs++-- |Get ''innerText'' from a list of cursors.+innerText :: [Cursor] -> T.Text+innerText cs = T.concat $ map (innerTextN . node) cs+-- |''toHTML'' of a list of nodes.+renderNodes :: [Node] -> TL.Text+renderNodes ns = TL.concat $ map (renderHtml . Bl.toHtml) ns++-- |''toHTML'' of a list of cursors.+toHtml :: [Cursor] -> TL.Text+toHtml cs = renderNodes $ map node cs++-- |''innerText'' of a single node.+innerTextN :: Node -> T.Text+innerTextN (NodeElement (Element _ _ cs)) = T.concat $ map innerTextN cs+innerTextN (NodeContent txt) = txt+innerTextN _ = ""++-- * Attirbutes+-- |Tag name of element node. Return Nothing if the node is not an element.+ename :: Node -> Maybe T.Text+ename (NodeElement (Element n _ _)) = Just $ nameLocalName n+ename _ = Nothing++-- |Return an element id. If node is not an element or does not have an id, return Nothing.+eid :: Node -> Maybe T.Text+eid (NodeElement (Element _ as _)) = M.lookup "id" as+eid _ = Nothing++-- |Return element classes. If node is not an element or does not have a class, return an empty list.+eclass :: Node -> [T.Text]+eclass (NodeElement (Element _ as _)) = maybe [] T.words $ M.lookup "class" as+eclass _ = []++-- | Search a meta with a specified name under a cursor, and get a ''content'' field. +getMeta :: T.Text -> Cursor -> [T.Text] +getMeta n cursor = concat $ cursor $// element "meta" &| attributeIs "name" n &.// attribute "content"+++-- * Removing Nodes+-- |Remove descendant nodes that satisfie predicate (''Destructive'').+remove :: (Node->Bool)->Node->Node+remove f (NodeElement (Element a b cs)) = NodeElement (Element a b (map (remove f) (filter (not . f) cs)))+remove _ n = n++-- |Similar to 'remove', but with a depth.+removeDepth :: (Node->Bool)->Int->Node->Node+removeDepth _ (-1) n = n+removeDepth f d (NodeElement (Element a b cs)) = NodeElement (Element a b (map (removeDepth f (d-1)) (filter (not . f) cs)))+removeDepth _ _ n = n++-- |Remove elements with specified tags.+removeTags :: [T.Text] -> [Node] -> [Node]+removeTags ts ns = map (remove (\n -> ename n `elem` map Just ts)) ns++-- |Remove descendant nodes that match a query string.+removeQuery :: String -> [Node] -> [Node]+removeQuery q ns = map (remove (queryMatchNode q)) ns++-- |Remove descendant nodes that match any of query strings.+removeQueries :: [String] -> [Node] -> [Node]+removeQueries qs ns = map (remove f) ns+  where+    f :: Node -> Bool+    f n = any (flip queryMatchNode n) qs++-- |See if the node contains any descendant (and self) node that satisfies predicate.+-- To return false, this function needs to traverse all descendant elements....+nodeHaving :: (Node->Bool)->Node->Bool+nodeHaving f n@(NodeElement (Element _ _ cs)) = f n || any (nodeHaving f) cs+nodeHaving _ _ = False+++-- |Remove descendant nodes that match the condition (similar to 'remove')+rmElem :: String -> String -> [String] -> [Node] -> [Node]+rmElem tag id kl ns = map (remove f) ns+  where+    f :: Node -> Bool+    f (NodeElement e) = selectorMatch (JQSelector Descendant (g tag) (g id) kl []) e+    f _ = False+    g :: String -> Maybe String+    g "" = Nothing+    g s = Just s+++--+-- Conversion to trees+--+{-+toTree :: Node -> Tr.Tree String+toTree (TextNode t) = Tr.Node ("\""++T.unpack t++"\"") []+toTree (Comment t) = Tr.Node ("<-- "++T.unpack t++" -->") []+toTree (Element t as c) = Tr.Node str (map toTree c)+	where+		str = T.unpack t ++ g as+		g as | null as = ""+					| otherwise = ": [" ++ intercalate "," (map f as) ++ "]"+		f (k,v) = T.unpack k ++ "=\"" ++ T.unpack v ++ "\""+++showTree :: [Node] -> IO ()+showTree ns = do+	mapM_ (putStr . Tr.drawTree . toTree) ns+-}+--+-- Helper functions for XmlHtml data+--+{-+(!!!) :: Node -> Int -> Node+(Element _ _ c) !!! i = c !! i+_ !!! _ = error "No child elements"+-}++
+ Text/XML/Selector.hs view
@@ -0,0 +1,219 @@+{-# LANGUAGE OverloadedStrings, DoAndIfThenElse, TemplateHaskell, QuasiQuotes #-}++--  CSS selector for Text.XML (xml-conduit) library.+--+--+-- Examples+-- doc <- H.readFile "input.html"+-- let c = fromDocument c+-- c $| query+-- c $| query "meta[name='dc.Creator']" >=> attribute "content"++-- |This module has query functions for traversing DOM. 'queryT', a quasiquote version, is also available in "Text.XML.Selector.TH" module.+module Text.XML.Selector (+  query,+  query1,+  searchTree,+  showJQ,+  byId,+  byClass,+  selectorMatch,+  next,+  maybeText,+  headm,+  queryMatchNode+  )+where++-- import Import+-- import qualified Data.Tree as Tr+import Data.List+import qualified Data.Text as T+import Text.XML.Cursor+import Text.XML as X -- hiding (Name)+-- import Data.Either.Utils+import qualified Data.Map as M+import Data.Maybe+-- import Language.Haskell.TH (runQ)+import Text.XML.Selector.Parser+import Text.XML.Selector.Types+import Debug.Trace+++-- | Show a parsed selector.+-- (parseJQ . showJQ) == id+showJQ :: [JQSelector] -> String+showJQ ss = foldl f "" ss+  where+    f str sel = str ++ g (relPrev sel) ++ +      maybe "" id (jqTagName sel) +++      maybe "" (("#"++)) (jqTagId sel) ++ +      concat (map (("."++)) (jqTagClass sel)) +++      if null (jqTagAttr sel) then "" else (concatMap h (jqTagAttr sel))+    g Descendant = " "+    g Child = " > "+    g Next = " + "+    g Sibling = " ~ "+    h (TagAttr k Nothing _) = "["++k++"]"+    h (TagAttr k (Just v) r) = "["++k ++ relToStr r ++ "\"" ++ v ++ "\"]"+--    h _ = error "Invalid TagAttr"+++-- Some elementary search++-- | Axis for choosing elements by an id+byId :: String -> Axis+byId s = checkElement (elemHasId (Just s))++-- byIdT :: String -> Axis+byIdT s = [e| checkElement (elemHasId (Just s)) |]++-- | Axis for choosing elements by a class+byClass :: String -> Axis+byClass s = checkElement (elemHasClass [s])++-- Some traversing++-- |Gets the next sibling. Note that this is not a Axis.+next :: Cursor -> Maybe Cursor+next c = headm (c $| followingSibling)+++-- Auxiliary functions+headm :: [a] -> Maybe a+headm [] = Nothing+headm (x:_) = Just x++take1' :: [a] -> [a]+take1' xs = if null xs then [] else take 1 xs++maybeText :: T.Text -> Maybe T.Text+maybeText "" = Nothing+maybeText t = Just t++--+-- Traversing by jQuery string+--+++-- | Get 'Axis' from jQuery selector string.+query :: String -> Axis+query keystr = case parseJQ keystr of+                 [] -> error "query: Invalid selector"+                 sels -> searchTree sels+++-- | Return Just (the first element of query results). If no element matches, it returns Nothing.+query1 :: String -> Cursor -> Maybe Cursor+query1 s n | null res = Nothing+	| otherwise = Just (head res)+	where res = query s n++{-+-- | Old version: search direction should be child -> parent to avoid duplicates for nested elements with same tag.+searchTreeOld :: [JQSelector] -> Axis+searchTreeOld [] c = [c]+searchTreeOld (x@(JQSelector Descendant _ _ _ _):xs) c = c $// checkElement (selectorMatch x)  >=> searchTree xs+searchTreeOld (x@(JQSelector Child _ _ _ _):xs) c = c $/ checkElement (selectorMatch x) >=> searchTree xs+searchTreeOld (x@(JQSelector Next _ _ _ _):xs) c =+  case c $| followingSibling >=> anyElement of+    [] -> []+    cs -> (head cs) $| checkElement (selectorMatch x) >=> searchTree xs+searchTreeOld ((JQSelector Sibling _ _ _ _):xs) c =  c $| followingSibling >=> searchTree xs+-}++searchTree :: [JQSelector] -> Axis+searchTree xs = search (reverse xs)+  where+    search [] c = [c]+    search (x@(JQSelector rel _ _ _ _):xs) c = c $// checkElement (selectorMatch x) >=> check (traceAncestors rel xs)+++traceAncestors :: RelPrev -> [JQSelector] -> Axis+traceAncestors _ [] c = [c]+traceAncestors Child (x:xs) c+  | isJust p = if matchCursor x (fromJust p) then traceAncestors (relPrev x) xs (fromJust p) else []+  | otherwise = []+    where+      p :: Maybe Cursor+      p = headm (parent c)+traceAncestors Descendant (x:xs) c+  = case filter (matchCursor x) (ancestor c) of+      [] -> []+      as -> concatMap (traceAncestors (relPrev x) xs) as+traceAncestors Next (x:xs) c+  | isJust p = if matchCursor x (fromJust p) then traceAncestors (relPrev x) xs (fromJust p) else []+  | otherwise = []+    where+      p :: Maybe Cursor+      p = headm (precedingSibling c)+traceAncestors Sibling (x:xs) c+  = case filter (matchCursor x) (precedingSibling c) of+      [] -> []+      as -> concatMap (traceAncestors (relPrev x) xs) as+++-- |Return if an element matches a selector+selectorMatch :: JQSelector -> Element -> Bool+selectorMatch (JQSelector _ name id klass attr) e+  = elemIsTag name e && elemHasId id e && elemHasClass klass e && all (flip elemHasAttr e) attr++matchNode :: JQSelector -> Node -> Bool+matchNode sel (NodeElement elem) = selectorMatch sel elem+matchNode _ _ = False++matchCursor :: JQSelector -> Cursor -> Bool+matchCursor sel cursor = matchNode sel (node cursor)++-- |Return if a node matches a selector given by string+-- |Only first token is used (i.e. no hierarchy is enabled.)+queryMatchNode :: String -> Node -> Bool+queryMatchNode s (NodeElement e)+    = case qq of+        Just q -> selectorMatch q e+        Nothing -> False+  where qq = headm $ parseJQ s+queryMatchNode _ _ = False+++-- return False when tag is specified in selector (1st arg) but not the one in the element (2nd arg)+elemIsTag :: Maybe String -> Element -> Bool+elemIsTag Nothing _ = True+elemIsTag (Just tag) (Element n _ _) = nameLocalName n == T.pack tag++elemHasId :: Maybe String -> Element -> Bool+elemHasId Nothing _ = True+elemHasId (Just id) (Element _ as _) = M.lookup "id" as == Just (T.pack id)++elemHasClass :: [String] -> Element -> Bool+elemHasClass [] _ = True+elemHasClass ks (Element _ as _) = all (`elem` kl) $ map T.pack ks+  where+      kl = maybe [] T.words (M.lookup "class" as)+++-- This is different from the above three funcs: by default return False+elemHasAttr :: TagAttr -> Element -> Bool+elemHasAttr attr (Element _ as _) = relFunc (attrRel attr) (g as) (Just (fromMaybe "" (attrVal attr)))+  where+    relFunc :: AttrRel -> Maybe String -> Maybe String -> Bool+    relFunc Equal (Just a) (Just b) = a == b+    relFunc Exists a _ = isJust a   -- '[checked]'+    relFunc Contains (Just a) (Just b) = b `isInfixOf` a+    relFunc Begin (Just a) (Just b) = b `isPrefixOf` a+    relFunc End (Just a) (Just b) = b `isSuffixOf` a+    relFunc NotEqual (Just a) (Just b) = a /= b+    relFunc ContainsWord (Just a) (Just b) = all (`isInfixOf` a) (words b)+    relFunc _ _ _ = False+--    relFunc r a b = error ("Attribute query invalid pattern: " ++ show attr ++ " : " ++ show r ++ show a ++ show b)+--    nameToStr :: M.Map Name T.Text -> M.Map String T.Text+--    nameToStr m = trace (show m) m+    toName s = Name (T.pack s) Nothing Nothing+    g :: M.Map Name T.Text -> Maybe String+    g as = fmap T.unpack (M.lookup (toName $ attrName attr) as)+    ++-- elemClass :: Element -> [T.Text]+-- elemClass (Element _ a _) = concat $ maybeToList $ fmap T.words $ (M.lookup "class" a)++
+ Text/XML/Selector/Parser.hs view
@@ -0,0 +1,113 @@+--+-- Parsers for CSS query strings+--+{-# LANGUAGE DoAndIfThenElse #-}++module Text.XML.Selector.Parser (parseJQ) where+import Text.Parsec+-- import Data.Either.Utils+import Text.XML.Selector.Types++import Data.Maybe+import qualified Data.Map as M++type Parser a = Parsec String () a++-- | Parse a jQuery selector string and return a list of 'JQSelector'.+parseJQ :: String -> [JQSelector]+parseJQ s = either (const []) id (parse parseKey "" (s++" ")) -- (++" ") is super ad hoc!!++-- test_parseQuery s = forceEither $ parse parseKey "" (s++" ") ++data JQSelectorToken = JQSelectorToken {+	rel :: RelPrev,+	tagNameIdClassAttr :: [NameIdClassAttr]+} deriving (Eq,Show)++transformSelector :: JQSelectorToken -> JQSelector+transformSelector (JQSelectorToken rel name) = JQSelector rel (t1 t) (t2 t) (t3 t) (t4 t)+  where+    t = f name (Nothing,Nothing,[],[])+    f [] r = r+    f ((TagName s):xs) r = f xs (Just s,t2 r,t3 r,t4 r)+    f ((Id s):xs) r = f xs (t1 r,Just s,t3 r,t4 r)+    f ((Class s):xs) r = f xs (t1 r,t2 r,s:t3 r,t4 r)+    f ((Attr k op v):xs) r = f xs (t1 r,t2 r,t3 r,(g k op v):(t4 r))+    t1 (a,_,_,_) = a+    t2 (_,a,_,_) = a+    t3 (_,_,a,_) = a+    t4 (_,_,_,a) = a+    g k op v = TagAttr k v (fromMaybe Exists (op >>= (flip M.lookup attrOpList)))+    +attrOpList :: M.Map String AttrRel+attrOpList = M.fromList [("=",Equal),("|=",Contains),("!=",NotEqual),("^=",Begin),("$=",End),("*=",ContainsWord)]++parseKey :: Parser [JQSelector]+parseKey = many1 selector++selector :: Parser JQSelector+selector = do+  skipMany myspaces+  sep <- optionMaybe (choice (map char ">+~"))+  let t = case sep of+            Just '>' -> Child+            Just '+' -> Next+            Just '~' -> Sibling+            Nothing -> Descendant+            _ -> error "Incorrect option."+  skipMany myspaces+  tok <- many1 $ choice [try selId, try selClass, try selTag, try selAttr]+  skipMany myspaces+  return $ transformSelector (JQSelectorToken t tok)++data NameIdClassAttr = TagName String | Id String | Class String | Attr String (Maybe String) (Maybe String) deriving (Eq,Show,Ord)++selTag :: Parser NameIdClassAttr+selTag = do+	s <- many1 cssChar+	return $ TagName s++selId :: Parser NameIdClassAttr+selId = do+  char '#'+  s <- many1 cssChar+  return $ Id s++selClass :: Parser NameIdClassAttr+selClass = do+	char '.'+	s <- many1 cssChar+	return $ Class s++selAttr :: Parser NameIdClassAttr+selAttr = do+  char '['+  k <- many1 cssChar+  op <- optionMaybe attrOp+  q <- optionMaybe (oneOf "\"'")+  v <- optionMaybe $ many1 (noneOf (maybe "]" (:[]) q))+  if isJust q then do+    char (fromJust q)+  else do+    return ' '+  char ']'+  return (Attr k op v)++-- stopDelim = (lookAhead (choice (map char ".#>+~ \t")))++--+-- Tokens+--+-- myspaces :: Stream s m Char => ParsecT s u m Char+myspaces = choice (map char " \t\r\n")++-- attrOp :: Stream s m Char => ParsecT s u m String+attrOp = choice $ map string ["=","|=","!=","*=","$=","^="]++-- cssChar :: Stream s m Char => ParsecT s u m Char+cssChar = oneOf "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"+++++
+ Text/XML/Selector/TH.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}++module Text.XML.Selector.TH (jq,queryT) where +import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Language.Haskell.TH.Lift+import Text.Parsec+import Text.XML.Selector+import Text.XML.Cursor++import Text.Parsec+import Text.XML.Selector.Types+import Text.XML.Selector.Parser++import Data.Maybe+import qualified Data.Map as M++type Parser a = Parsec String () a++$(deriveLift ''JQSelector)+$(deriveLift ''TagAttr)+$(deriveLift ''RelPrev)+$(deriveLift ''AttrRel)++-- instance Lift JQSelector where+--  lift (JQSelector a b c d e) = [| JQSelector a b c d e |]++jqueryExpr :: String -> Q Exp+jqueryExpr str = do+  case parseJQ str of+                 [] -> error "query: Invalid selector"+                 sels -> [| sels |]++-- | QuasiQuoter for CSS selector+jq :: QuasiQuoter+jq = QuasiQuoter jqueryExpr undefined undefined undefined+++-- |Get 'Axis' from jQuery selector QQ.+--+-- >html = innerHtml $ cursor $| queryT [jq| ul.foo > li#bar |]+queryT :: [JQSelector] -> Axis+queryT sels = searchTree sels+++
+ Text/XML/Selector/Test.hs view
@@ -0,0 +1,57 @@+--+-- Testing+--++module Text.XML.Selector.Test (prop_parseJQ) where++import Text.XML.Selector (showJQ)+import Text.XML.Selector.Types+import Text.XML.Selector.Parser+import Test.QuickCheck+import Control.Monad+import Data.Maybe++-- |QuickCheck for a parser.+prop_parseJQ :: [JQSelector] -> Bool+prop_parseJQ ss = (parseJQ . showJQ) ss == ss+++instance Arbitrary JQSelector where+  arbitrary = do+    rel <- arbitrary+    nm <- arbCssId+    a <- choose (0,1)+    let name = [Nothing, Just nm] !! a+    ids <- arbCssId+    c <- choose (0,1)+    let id = [Nothing, Just ids] !! c+    kl <- arbCssId+    b <- choose (0,10)+    klass <- replicateM b arbCssId+    attr <- case (name, id, klass) of+              (Nothing, Nothing, []) -> do+                n <- choose (1,10)+                replicateM n (arbitrary :: Gen TagAttr)+              _ -> arbitrary+    return (JQSelector rel name id klass attr)++instance Arbitrary TagAttr where+  arbitrary = do+    name <- arbCssId+    rel <- arbitrary+    s <- arbCssId+    let v = Just s+    let val = if rel == Exists then Nothing else v+    return (TagAttr name val rel)++instance Arbitrary RelPrev where+  arbitrary = elements (enumFrom (toEnum 0))++instance Arbitrary AttrRel where+  arbitrary = elements (enumFrom (toEnum 0))+++arbCssId :: Gen String+arbCssId = do+  n <- choose (1,10)+  replicateM n (elements "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
+ Text/XML/Selector/Types.hs view
@@ -0,0 +1,32 @@+module Text.XML.Selector.Types where ++import Data.List (sort)+import qualified Data.Map as M (lookup,fromList)+import Data.Maybe (fromMaybe)++-- |JQSelector represents one token of jquery selector. One JQSelector corresponds to \"div#content\", \"a[href='/index.html']\", etc. You can get a list of JQSelector by 'parseJQ', and show them by 'showJQ'+-- As long as you use 'query', you don't need to handle this type directly.+data JQSelector = JQSelector {+	relPrev :: RelPrev,+  jqTagName :: Maybe String,+	jqTagId :: Maybe String,+	jqTagClass :: [String],+  jqTagAttr :: [TagAttr]+} deriving (Show,Read,Ord)++instance Eq JQSelector where+  (JQSelector r1 n1 i1 c1 a1) == (JQSelector r2 n2 i2 c2 a2) = r1 == r2 && n1 == n2 && i1 == i2 && (sort c1 == sort c2 && sort a1 == sort a2)++data TagAttr = TagAttr {+  attrName :: String,+  attrVal :: Maybe String,+  attrRel :: AttrRel+} deriving (Eq,Show,Read,Ord)++data AttrRel = Equal | Begin | End | Contains | NotEqual | ContainsWord | Exists deriving (Show,Eq,Ord,Enum,Read)+relToStr :: AttrRel -> String+relToStr r = fromMaybe "N/A" $ M.lookup r (M.fromList [(Equal,"="),(Begin,"^="),(End,"$="),(Contains,"|="),(NotEqual,"!="),(ContainsWord,"*=")])++-- |Relationship to the preceding selector.+data RelPrev = Descendant | Child | Next | Sibling deriving (Eq,Show,Enum,Read,Ord)+
+ dom-selector.cabal view
@@ -0,0 +1,54 @@+-- Initial dom-selector.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name:                dom-selector+version:             0.1.0.0+synopsis:            DOM traversal by CSS selectors for xml-conduit package+description:+  CSS selector support for xml-conduit/html-conduit. This package supports compile-time checking of CSS selectors using quasiquotes.+  .+  * Quick start+  .+  > -- The following pragmas should be put first (Haddock does not accept a pragma notation.)+  > -- LANGUAGE OverloadedStrings, QuasiQuotes+  > module Main (main) where+  >+  > import Text.XML.Cursor+  > import qualified Text.HTML.DOM as H (readFile)+  > import qualified Data.Text.Lazy.IO as TI (putStrLn)+  > import Text.XML.Scraping (innerHtml)+  > import Text.XML.Selector.TH +  >+  > main :: IO ()+  > main = do+  >   c <- fmap fromDocument $ H.readFile "input.html"+  >   let cs = queryT [jq| ul#foo > li.bar |] c+  >   TI.putStrLn $ innerHtml cs+  .+  You can use some elementary CSS selectors for traversing a DOM tree.+  .+  * Other examples+  .+  <https://github.com/nebuta/dom-selector/tree/master/examples>++homepage:            https://github.com/nebuta/+license:             BSD3+license-file:        LICENSE+author:              Nebuta Lab+maintainer:          nebuta.office@gmail.com+copyright:           Copyright 2012 by Nebuta Lab           +category:            Web+stability:           alpha+build-type:          Simple+cabal-version:       >=1.8++source-repository head+  type:     git+  location: https://github.com/nebuta/dom-selector++library+  exposed-modules:     Text.XML.Scraping, Text.XML.Selector, Text.XML.Selector.Parser, Text.XML.Selector.Test, Text.XML.Selector.TH, Text.XML.Selector.Types+  other-modules:       +  build-depends:       base >=4.0 && <5, text ==0.11.*, xml-conduit, html-conduit >=0.1, containers >=0.4, blaze-html >=0.5,  parsec >=3.1, QuickCheck >=2.4, template-haskell >=2.5, th-lift >=0.5+