diff --git a/Text/HTML/Onama.hs b/Text/HTML/Onama.hs
--- a/Text/HTML/Onama.hs
+++ b/Text/HTML/Onama.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 {-|
   Module: Text.HTML.Onama
   Description: Parsec extended with functions to handle HTML parsing.
@@ -32,13 +34,23 @@
 
   , tag
   , satisfy
-  , tagOpen, tagOpenAny
-  , tagOpen_, tagOpenAny_
-  , tagClose , tagCloseAny
-  , tagClose_, tagCloseAny_
 
-  , tagText , tagTextAny
-  , text
+  , TagOpenSelector(..)
+  , TagCloseSelector(..)
+  , AttrName(..)
+  , AttrValue(..)
+  , AttrSelector(..)
+
+  , (@:), (@=)
+
+  , tagOpen_, tagOpen
+  , tagClose_, tagClose
+  , tagText
+  , balancedTags
+  , anyOpenTag, anyCloseTag, anyValue
+  , innerText
+
+  , skip
   )
 where
 
@@ -63,6 +75,14 @@
   , unknownError, sysUnExpectError, mergeErrorReply
   )
 
+import qualified Data.Sequence as S
+import Data.Sequence
+  ( (<|), (|>), (><) )
+
+import Data.String (IsString, fromString)
+
+import Data.Foldable (toList)
+
 type Position = (TS.Row, TS.Column)
 
 data Tag str
@@ -102,6 +122,55 @@
                      TagText _ pos   -> pos
   in flip P.setSourceLine row $ flip P.setSourceColumn col $ pos
 
+tagName :: Tag str -> Maybe str
+tagName (TagOpen name _ _) = Just name
+tagName (TagClose name _ ) = Just name
+tagName _other             = Nothing
+
+data TagOpenSelector
+  = AnyOpenTag [AttrSelector]
+  | TagOpenSelector String [AttrSelector]
+
+data TagCloseSelector
+  = AnyCloseTag
+  | TagCloseSelector String
+
+instance IsString TagOpenSelector where
+  fromString str = TagOpenSelector str []
+
+instance IsString TagCloseSelector where
+  fromString = TagCloseSelector
+
+tagSelectorAttrs :: TagOpenSelector -> [AttrSelector]
+tagSelectorAttrs (AnyOpenTag attrs)        = attrs
+tagSelectorAttrs (TagOpenSelector _ attrs) = attrs
+
+newtype AttrName = AttrName String
+
+instance IsString AttrName where
+  fromString = AttrName
+
+data AttrValue
+  = AnyAttr
+  | AttrValue String
+
+instance IsString AttrValue where
+  fromString = AttrValue
+
+data AttrSelector = AttrSelector AttrName AttrValue
+
+instance IsString AttrSelector where
+  fromString str = AttrSelector (AttrName str) AnyAttr
+
+(@:) :: TagOpenSelector -> [AttrSelector] -> TagOpenSelector
+(@:) tagS attrS =
+  case tagS of
+    AnyOpenTag _           -> AnyOpenTag attrS
+    TagOpenSelector name _ -> TagOpenSelector name attrS
+
+(@=) :: AttrName -> AttrValue -> AttrSelector
+(@=) = AttrSelector
+
 -- | Primitive. Return the next input tag.
 --   All other primitive parsers should be implemented in terms of this.
 tag :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
@@ -113,77 +182,107 @@
 satisfy f = P.tokenPrim show updatePos $ \tag ->
               if f tag then Just tag else Nothing
 
--- | Tag name should be lowercase.
---   Return the parsed tag.
-tagOpen_ :: (Monad m, Show str, Eq str) => str -> P.ParsecT [Tag str] u m (Tag str)
-tagOpen_ str = satisfy $ \tag ->
-  case tag of
-    TagOpen name _ _ -> str == name
-    _                -> False
+matchAttrValue :: StringLike str => str -> AttrValue -> Bool
+matchAttrValue val attrS = case attrS of
+  AnyAttr        -> True
+  AttrValue val' -> toString val == val'
 
--- | Tag name should be lowercase.
---   Skips over any text nodes in the HTML before attempting to parse
---   an open tag. Usually this is what you want. If not, use 'tagOpen_'.
-tagOpen :: (Monad m, Show str, Eq str) => str -> P.ParsecT [Tag str] u m (Tag str)
-tagOpen str = optional text >> tagOpen_ str
+tagOpen_ :: (Monad m, StringLike str, Show str)
+         => TagOpenSelector
+         -> P.ParsecT [Tag str] u m (Tag str)
+tagOpen_ tagS =
+  satisfy (\tag -> case tag of
+              TagOpen name attrs _ ->
+                let attrS = tagSelectorAttrs tagS in
+                  case tagS of
+                    AnyOpenTag _ -> matchAttrs attrS attrs
+                    TagOpenSelector name' _ ->
+                      toString name == name' && matchAttrs attrS attrs
+              _other               -> False)
+  <?> "Couldn't parse an open tag."
+  where matchAttrs attrS attrs =
+          all (\(AttrSelector (AttrName name) attrValS) ->
+                 case lookup (fromString name) attrs of
+                   Just val -> matchAttrValue val attrValS
+                   Nothing  -> False)
+              attrS
 
-tagOpenAny_ :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
-tagOpenAny_ = satisfy $ \tag ->
-  case tag of
-    TagOpen _ _ _ -> True
-    _             -> False
+tagOpen :: (Monad m, StringLike str, Show str)
+        => TagOpenSelector
+        -> P.ParsecT [Tag str] u m (Tag str)
+tagOpen tagS = try (optional tagText >> tagOpen_ tagS)
 
--- | Skips over any text nodes in the HTML before attempting to parse
---   an open tag. Usually this is what you want. If not, use 'tagOpenAny_'.
-tagOpenAny :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
-tagOpenAny = optional text >> tagOpenAny_
+tagClose_ :: (Monad m, StringLike str, Show str)
+          => TagCloseSelector
+          -> P.ParsecT [Tag str] u m (Tag str)
+tagClose_ tagS =
+  satisfy (\tag -> case tag of
+              TagClose name _ ->
+                case tagS of
+                  AnyCloseTag            -> True
+                  TagCloseSelector name' -> toString name == name'
+              _other          -> False)
 
--- | Tag name should be lowercase.
---   Return the parsed tag.
-tagClose_ :: (Monad m, Show str, Eq str) => str -> P.ParsecT [Tag str] u m (Tag str)
-tagClose_ str = satisfy $ \tag ->
-  case tag of
-    TagClose name _ -> str == name
-    _               -> False
+tagClose :: (Monad m, StringLike str, Show str)
+         => TagCloseSelector
+         -> P.ParsecT [Tag str] u m (Tag str)
+tagClose tagS = try (optional tagText >> tagClose_ tagS)
 
--- | Tag name should be lowercase.
---   Skips over any text nodes in the HTML before attempting to parse
---   a close tag. Usually this is what you want. If not, use 'tagClose_'.
-tagClose :: (Monad m, Show str, Eq str) => str -> P.ParsecT [Tag str] u m (Tag str)
-tagClose str = optional text >> tagClose_ str
+-- | Take a parser, return a parser which only succeeds if the given parser
+--   fails. Consumes no input.
+notParse :: P.Stream s m t => P.ParsecT s u m t -> P.ParsecT s u m ()
+notParse parser = do
+  parsed <-     try $ Just <$> parser
+            <|> return Nothing
+  case parsed of
+    Nothing -> return ()
+    Just _  -> unexpected "parser given to notParse succeeded"
 
-tagCloseAny_ :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
-tagCloseAny_ = satisfy $ \tag ->
-  case tag of
-    TagClose _ _ -> True
-    _            -> False
+tagText :: (Monad m, Show str) => P.ParsecT [Tag str] u m str
+tagText = P.tokenPrim show updatePos $ \tag -> case tag of
+  TagText text _ -> Just text
+  _other         -> Nothing
 
--- | Skips over any text nodes in the HTML before attempting to parse
---   a close tag. Usually this is what you want. If not, use 'tagCloseAny_'.
-tagCloseAny :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
-tagCloseAny = optional text >> tagCloseAny_
+balancedTags_ :: (Monad m, StringLike str, Show str)
+              => TagOpenSelector
+              -> P.ParsecT [Tag str] u m (S.Seq (Tag str))
+balancedTags_ tagS = do
+  openTag <- tagOpen_ tagS
+  tailTags <- tagTail openTag
+  return $ openTag <| tailTags
 
--- | Return the parsed tag.
-tagText :: (Monad m, Show str, Eq str) => str -> P.ParsecT [Tag str] u m (Tag str)
-tagText str = satisfy $ \tag ->
-  case tag of
-    TagText text _ -> str == text
-    _              -> False
+tagTail :: (Monad m, StringLike str, Show str)
+        => Tag str
+        -> P.ParsecT [Tag str] u m (S.Seq (Tag str))
+tagTail (TagOpen name _ _) = do
+  innerTags <- P.many $ try notMatchingClose
+  matchingClose <- tagClose_ closeS
+  return $ mconcat innerTags |> matchingClose
+    where closeS = TagCloseSelector $ toString name
+          notMatchingClose =   ( (balancedTags_ anyOpenTag)
+                             <|> S.singleton <$> (notParse (tagClose_ closeS) >> tag)
+                               )
 
-tagTextAny :: (Monad m, Show str) => P.ParsecT [Tag str] u m (Tag str)
-tagTextAny = satisfy $ \tag ->
-  case tag of
-    TagText text _ -> True
-    _              -> False
+balancedTags :: (Monad m, StringLike str, Show str)
+             => TagOpenSelector
+             -> P.ParsecT [Tag str] u m [Tag str]
+balancedTags tagS = toList <$> balancedTags_ tagS
 
--- | Parse and return the text of a text tag.
-text :: (Monad m, Show str) => P.ParsecT [Tag str] u m str
-text = do
-  tag <- tagTextAny
-  case tag of
-    TagText t _ -> return t
-    _other      -> fail "Could not find a text tag."
+anyOpenTag :: TagOpenSelector
+anyOpenTag = AnyOpenTag []
 
+anyCloseTag :: TagCloseSelector
+anyCloseTag = AnyCloseTag
+
+anyValue :: AttrValue
+anyValue = AnyAttr
+
 -- | @skip p@ produces a parser which will ignore the output of @p@.
 skip :: P.Stream s m t => P.ParsecT s u m a -> P.ParsecT s u m ()
 skip p = p >> return ()
+
+innerText :: StringLike str => [Tag str] -> str
+innerText tags = fromString $ toList $ mconcat $ fmap tagInner tags
+  where tagInner (TagOpen "br" _ _) = S.singleton '\n'
+        tagInner (TagText text _)   = S.fromList $ toString text
+        tagInner _other             = S.empty
diff --git a/onama.cabal b/onama.cabal
--- a/onama.cabal
+++ b/onama.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.1.0.0
+version:             0.2.1.0
 
 synopsis: HTML-parsing primitives for Parsec.
 
@@ -64,6 +64,7 @@
   build-depends:       base >=4.8 && <4.9
                      , tagsoup
                      , parsec
+                     , containers
 
   -- Directories containing source files.
   -- hs-source-dirs:
