diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Revision history for asciidoc-hs
+
+## 0.1 -- 2025-11-30
+
+* Initial release.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,56 @@
+Copyright (c) 2025, John MacFarlane
+
+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 the copyright holder nor the names of its
+      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
+HOLDER 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.
+
+--
+
+The asciidoc content of the test cases in `test/asciidoctor`
+comes from https://github.com/asciidoctor/asciidoctor-doctest.
+It is governed by this license:
+
+The MIT License
+
+Copyright 2014-present Jakub Jirutka <jakub@jirutka.cz> and the Asciidoctor Project.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Data.Aeson (encode)
+import Options.Applicative
+import qualified Data.Text.IO as TIO
+import System.Exit (exitFailure)
+import Text.Show.Pretty (ppShow)
+import AsciiDoc.Parse (parseDocument)
+import qualified Data.ByteString.Lazy as B
+import System.IO (hPutStrLn, stderr)
+
+data OutputFormat
+  = AST    -- ^ Show the parsed AST in Haskell notation
+  | JSON   -- ^ Show the parsed AST in JSON serialization
+  deriving (Show, Eq)
+
+-- | Command line options
+data Options = Options
+  { optInputFiles :: [FilePath]
+  , optOutputFormat :: OutputFormat
+  } deriving (Show)
+
+-- | Parser for output format
+outputFormatParser :: ReadM OutputFormat
+outputFormatParser = eitherReader $ \s ->
+  case s of
+    "ast"  -> Right AST
+    "json" -> Right JSON
+    _      -> Left $ "Invalid output format: " ++ s ++ ". Use 'ast' or 'json'."
+
+-- | Command line options parser
+optionsParser :: Parser Options
+optionsParser = Options
+  <$> many (argument str
+      ( metavar "FILE"
+     <> help "Input files (omit to read from stdin)"
+      ))
+  <*> option outputFormatParser
+      ( long "to"
+     <> metavar "FORMAT"
+     <> value AST
+     <> help "Output format (ast*|json)"
+      )
+
+-- | Program description
+opts :: ParserInfo Options
+opts = info (optionsParser <**> helper)
+  ( fullDesc
+ <> progDesc "Parse AsciiDoc and output an AST"
+ <> header "hasciidoc - AsciiDoc parser in Haskell"
+  )
+
+
+main :: IO ()
+main = do
+  options <- execParser opts
+
+  let raiseError fp pos msg = do
+        hPutStrLn stderr $ "Parse error at " <> show fp <>
+                           " position " <> show pos <> ": " <> msg
+        exitFailure
+
+  -- Parse the document
+  doc <- case optInputFiles options of
+          [] -> TIO.getContents >>=
+                  parseDocument TIO.readFile raiseError "stdin"
+          fs -> mconcat <$> mapM (\fp ->
+                  TIO.readFile fp >>= parseDocument TIO.readFile raiseError fp)
+                fs
+  case optOutputFormat options of
+    AST -> putStrLn $ ppShow doc
+    JSON -> do
+      B.putStr $ encode doc
+      B.putStr "\n"
diff --git a/asciidoc.cabal b/asciidoc.cabal
new file mode 100644
--- /dev/null
+++ b/asciidoc.cabal
@@ -0,0 +1,76 @@
+cabal-version:      3.4
+name:               asciidoc
+version:            0.1
+synopsis:           AsciiDoc parser.
+description:        A parser for AsciiDoc syntax.
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             John MacFarlane
+maintainer:         jgm@berkeley.edu
+copyright:          (C) 2025 by John MacFarlane
+category:           Text
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+extra-source-files: test/asciidoctor/**/*.test
+                    test/feature/**/*.test
+                    test/feature/include/**/*.adoc
+
+Source-repository head
+  type:              git
+  location:          https://github.com/jgm/asciidoc-hs.git
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:  AsciiDoc
+                    , AsciiDoc.AST
+                    , AsciiDoc.Generic
+                    , AsciiDoc.Parse
+    build-depends:    base >=4.14 && <5
+                    , text
+                    , mtl
+                    , attoparsec
+                    , filepath
+                    , containers
+                    , tagsoup
+                    , aeson
+    hs-source-dirs:   src
+    default-language: Haskell2010
+
+executable hasciidoc
+    import:           warnings
+    main-is:          Main.hs
+    build-depends:
+        base >=4.14 && <5,
+        asciidoc,
+        mtl,
+        text,
+        pretty-show,
+        aeson,
+        bytestring,
+        optparse-applicative
+
+    hs-source-dirs:   app
+    default-language: Haskell2010
+
+test-suite asciidoc-hs-test
+    import:           warnings
+    default-language: Haskell2010
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    build-depends:
+        base >=4.14 && <5,
+        asciidoc,
+        tasty,
+        tasty-hunit,
+        tasty-golden,
+        text,
+        bytestring,
+        pretty-show,
+        containers,
+        directory,
+        filepath,
+        process
diff --git a/src/AsciiDoc.hs b/src/AsciiDoc.hs
new file mode 100644
--- /dev/null
+++ b/src/AsciiDoc.hs
@@ -0,0 +1,10 @@
+module AsciiDoc (
+  module AsciiDoc.AST,
+  module AsciiDoc.Parse,
+  module AsciiDoc.Generic
+) where
+
+import AsciiDoc.AST
+import AsciiDoc.Parse
+import AsciiDoc.Generic
+
diff --git a/src/AsciiDoc/AST.hs b/src/AsciiDoc/AST.hs
new file mode 100644
--- /dev/null
+++ b/src/AsciiDoc/AST.hs
@@ -0,0 +1,492 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+
+module AsciiDoc.AST
+  ( Document(..)
+  , Meta(..)
+  , Author(..)
+  , Revision(..)
+  , Block(..)
+  , BlockType(..)
+  , BlockTitle(..)
+  , Inline(..)
+  , InlineType(..)
+  , ListType(..)
+  , ListItem(..)
+  , CheckboxState(..)
+  , ColumnSpec(..)
+  , CellStyle(..)
+  , TableRow(..)
+  , TableCell(..)
+  , HorizAlign(..)
+  , VertAlign(..)
+  , AdmonitionType(..)
+  , Target(..)
+  , LinkType(..)
+  , MathType(..)
+  , Attr(..)
+  , attrNull
+  , Level(..)
+  , Language(..)
+  , Attribution(..)
+  , AltText(..)
+  , Width(..)
+  , Height(..)
+  , FootnoteId(..)
+  , AttributeName(..)
+  , Callout(..)
+  , IndexTerm(..)
+  , SourceLine(..)
+  , CounterType(..)
+  ) where
+
+import Control.Monad
+import Data.Text (Text)
+import Data.Data (Data)
+import Data.Typeable (Typeable)
+import GHC.Generics (Generic)
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
+import Data.Aeson (ToJSON(..), FromJSON(..), genericToEncoding, defaultOptions)
+
+-- | A complete AsciiDoc document
+data Document = Document
+  { docMeta :: Meta
+  , docBlocks :: [Block]
+  } deriving (Show, Eq, Generic, Data, Typeable)
+
+instance Semigroup Document where
+  d1 <> d2 = Document { docMeta = docMeta d1 <> docMeta d2
+                      , docBlocks = docBlocks d1 <> docBlocks d2
+                      }
+
+instance Monoid Document where
+  mappend = (<>)
+  mempty = Document mempty mempty
+
+instance ToJSON Document where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Document
+
+-- | Author information
+data Author = Author
+  { authorName :: Text
+  , authorEmail :: Maybe Text
+  } deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON Author where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Author
+
+-- | Revision information
+data Revision = Revision
+  { revVersion :: Text
+  , revDate :: Maybe Text
+  , revRemark :: Maybe Text
+  } deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON Revision where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Revision
+
+-- | Document metadata
+data Meta = Meta
+  { docTitle :: [Inline]
+  , docTitleAttributes :: Maybe Attr
+  , docAuthors :: [Author]
+  , docRevision :: Maybe Revision
+  , docAttributes :: Map Text Text
+  } deriving (Eq, Generic, Data, Typeable)
+
+instance Show Meta where
+  show x | x == mempty = "Meta mempty"
+  show (Meta title titleAttr authors revision attributes) =
+    "Meta{ docTitle = " <> show title <>
+    ", docTitleAttributes = " <> show titleAttr <>
+    ", docAuthors = " <> show authors <>
+    ", docRevision = " <> show revision <>
+    ", docAttributes = " <> show attributes
+    <> "}"
+
+instance ToJSON Meta where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Meta
+
+instance Semigroup Meta where  -- left-biased
+  m1 <> m2 = Meta { docTitle = case docTitle m1 of
+                                 [] -> docTitle m2
+                                 ils -> ils
+                  , docTitleAttributes =
+                               case docTitle m1 of
+                                 [] -> docTitleAttributes m2
+                                 _ -> docTitleAttributes m2
+                  , docAuthors = docAuthors m1 <> docAuthors m2
+                  , docRevision = docRevision m1 `mplus` docRevision m2
+                  , docAttributes = docAttributes m1 <> docAttributes m2
+                  }
+
+instance Monoid Meta where
+  mappend = (<>)
+  mempty = Meta [] Nothing [] Nothing mempty
+
+-- | Attributes attached to an element.
+-- The first parameter stores positional attributes in order.
+-- The second parameter stores named attributes (including special keys
+-- like id/role/options) in a map.
+data Attr = Attr [Text] (Map Text Text)
+  deriving (Eq, Generic, Data, Typeable)
+
+instance Show Attr where
+  show (Attr pos m)
+    | null pos && Map.null m = "mempty"
+    | otherwise = "Attr " ++ show (pos, m)
+
+instance Semigroup Attr where
+  Attr p1 m1 <> Attr p2 m2 =
+    let m = m2 <> m1 -- left-biased, favor m2
+        m' = (case (Map.lookup "role" m1, Map.lookup "role" m2) of
+                (Just x1, Just x2) -> Map.insert "role" (x1 <> " " <> x2)
+                _ -> id) .
+             (case (Map.lookup "options" m1, Map.lookup "options" m2) of
+                (Just x1, Just x2) -> Map.insert "options" (x1 <> "," <> x2)
+                _ -> id) $ m
+    in  Attr (p1 <> p2) m'
+
+instance Monoid Attr where
+  mempty = Attr [] Map.empty
+  mappend = (<>)
+
+instance ToJSON Attr where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Attr
+
+attrNull :: Attr -> Bool
+attrNull (Attr pos m) = null pos && Map.null m
+
+-- | Nesting or section level
+newtype Level = Level Int
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Level where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Level
+
+-- | Programming or markup language identifier
+newtype Language = Language Text
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Language where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Language
+
+-- | Attribution for quotes
+newtype Attribution = Attribution Text
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Attribution where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Attribution
+
+-- | Alternative text for images
+newtype AltText = AltText Text
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON AltText where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON AltText
+
+-- | Width specification in pixels
+newtype Width = Width Int
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Width where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Width
+
+-- | Height specification in pixels
+newtype Height = Height Int
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Height where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Height
+
+-- | Footnote identifier
+newtype FootnoteId = FootnoteId Text
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON FootnoteId where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON FootnoteId
+
+-- | Attribute name
+newtype AttributeName = AttributeName Text
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON AttributeName where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON AttributeName
+
+-- | Source line callout
+newtype Callout = Callout Int
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON Callout where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Callout
+
+-- | Source line with possible annotation
+data SourceLine =
+    SourceLine Text [Callout]
+  deriving (Show, Eq, Ord, Generic, Data, Typeable)
+
+instance ToJSON SourceLine where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON SourceLine
+
+-- | Block-level element with attributes
+data Block = Block Attr (Maybe BlockTitle) BlockType
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON Block where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Block
+
+-- | Block-level element types
+data BlockType
+  = Section Level [Inline] [Block]
+  | DiscreteHeading Level [Inline]
+  | Paragraph [Inline]
+  | Verse (Maybe Attribution) [Block]
+  | LiteralBlock Text
+  | Listing (Maybe Language) [SourceLine]
+  | IncludeListing (Maybe Language) FilePath (Maybe [SourceLine])
+  | ExampleBlock [Block]
+  | QuoteBlock (Maybe Attribution) [Block]
+  | Sidebar [Block]
+  | OpenBlock [Block]
+  | PassthroughBlock Text
+  | MathBlock (Maybe MathType) Text
+  | List ListType [ListItem]
+  | DefinitionList [([Inline], [Block])]
+  | Table [ColumnSpec] (Maybe [TableRow]) [TableRow] (Maybe [TableRow])
+  | BlockImage Target (Maybe AltText) (Maybe Width) (Maybe Height)
+  | BlockAudio Target
+  | BlockVideo Target
+  | TOC
+  | Admonition AdmonitionType [Block]
+  | PageBreak
+  | ThematicBreak
+  | Include FilePath (Maybe [Block])
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON BlockType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON BlockType
+
+newtype BlockTitle = BlockTitle [Inline]
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON BlockTitle where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON BlockTitle
+
+-- | Types of admonitions
+data AdmonitionType
+  = Note
+  | Tip
+  | Important
+  | Caution
+  | Warning
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON AdmonitionType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON AdmonitionType
+
+data IndexTerm =
+    TermInText Text
+  | TermConcealed [Text]
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON IndexTerm where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON IndexTerm
+
+-- | List types
+data ListType
+  = BulletList Level
+  | OrderedList Level (Maybe Int)
+  | CheckList
+  | CalloutList
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON ListType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON ListType
+
+-- | A list item
+data ListItem = ListItem (Maybe CheckboxState) [Block]
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON ListItem where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON ListItem
+
+-- | Checkbox state for checklists
+data CheckboxState
+  = Checked
+  | Unchecked
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON CheckboxState where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON CheckboxState
+
+-- | Column specification
+data ColumnSpec = ColumnSpec
+  { colHorizAlign :: Maybe HorizAlign
+  , colVertAlign :: Maybe VertAlign
+  , colWidth :: Maybe Int
+  , colStyle :: Maybe CellStyle
+  } deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON ColumnSpec where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON ColumnSpec
+
+-- | Defines how cell contents are parsed
+data CellStyle =
+    AsciiDocStyle
+  | DefaultStyle
+  | EmphasisStyle
+  | LiteralStyle
+  | HeaderStyle
+  | MonospaceStyle
+  | StrongStyle
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON CellStyle where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON CellStyle
+
+-- | Table row
+newtype TableRow = TableRow [TableCell]
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON TableRow where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON TableRow
+
+-- | Table cell
+data TableCell = TableCell
+  { cellContent :: [Block]
+  , cellHorizAlign :: Maybe HorizAlign
+  , cellVertAlign :: Maybe VertAlign
+  , cellColspan :: Int
+  , cellRowspan :: Int
+  } deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON TableCell where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON TableCell
+
+-- | Cell alignment
+data HorizAlign
+  = AlignLeft
+  | AlignCenter
+  | AlignRight
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON HorizAlign where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON HorizAlign
+
+data VertAlign
+  = AlignTop
+  | AlignMiddle
+  | AlignBottom
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON VertAlign where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON VertAlign
+
+-- | Inline element with attributes
+data Inline = Inline Attr InlineType
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON Inline where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Inline
+
+-- | Inline element types
+data InlineType
+  = Str Text
+  | HardBreak
+  | Bold [Inline]
+  | Italic [Inline]
+  | Monospace [Inline]
+  | Superscript [Inline]
+  | Subscript [Inline]
+  | Highlight [Inline]
+  | Strikethrough [Inline]
+  | DoubleQuoted [Inline]
+  | SingleQuoted [Inline]
+  | Math (Maybe MathType) Text
+  | Icon Text
+  | Button Text
+  | Kbd [Text]
+  | Menu [Text]
+  | Link LinkType Target [Inline]
+  | InlineImage Target (Maybe AltText) (Maybe Width) (Maybe Height)
+  | Footnote (Maybe FootnoteId) [Inline]
+  | InlineAnchor Text [Inline]
+  | BibliographyAnchor Text [Inline]
+  | CrossReference Text (Maybe [Inline])
+  | AttributeReference AttributeName
+  | Span [Inline]
+  | IndexEntry IndexTerm
+  | Counter Text CounterType Int
+  | Passthrough Text
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON InlineType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON InlineType
+
+data MathType
+  = AsciiMath
+  | LaTeXMath
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON MathType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON MathType
+
+-- | Link types
+data LinkType
+  = URLLink
+  | EmailLink
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON LinkType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON LinkType
+
+-- | Link or image target
+newtype Target = Target Text
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON Target where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON Target
+
+data CounterType =
+  DecimalCounter | UpperAlphaCounter | LowerAlphaCounter
+  deriving (Show, Eq, Generic, Data, Typeable)
+
+instance ToJSON CounterType where
+    toEncoding = genericToEncoding defaultOptions
+instance FromJSON CounterType
diff --git a/src/AsciiDoc/Generic.hs b/src/AsciiDoc/Generic.hs
new file mode 100644
--- /dev/null
+++ b/src/AsciiDoc/Generic.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module AsciiDoc.Generic
+  ( HasInlines(..)
+  , HasBlocks(..)
+  ) where
+
+import GHC.Generics hiding (Meta)
+import AsciiDoc.AST
+import Control.Monad
+
+class GHasInlines f where
+  gfoldInlines :: Monoid m => (Inline -> m) -> f p -> m
+  gmapInlines :: Monad m => (Inline -> m Inline) -> f p -> m (f p)
+
+instance GHasInlines U1 where
+  gfoldInlines _ _ = mempty
+  gmapInlines _ _ = pure mempty
+
+instance (GHasInlines f, GHasInlines g) => GHasInlines (f :*: g) where
+  gfoldInlines f (x :*: y) = gfoldInlines f x <> gfoldInlines f y
+  gmapInlines f (x :*: y) = liftM2 (:*:) (gmapInlines f x) (gmapInlines f y)
+
+instance (GHasInlines f, GHasInlines g) => GHasInlines (f :+: g) where
+  gfoldInlines f (L1 x) = gfoldInlines f x
+  gfoldInlines f (R1 y) = gfoldInlines f y
+  gmapInlines f (L1 x) = L1 <$> gmapInlines f x
+  gmapInlines f (R1 y) = R1 <$> gmapInlines f y
+
+instance GHasInlines f => GHasInlines (M1 i c f) where
+  gfoldInlines f (M1 x) = gfoldInlines f x
+  gmapInlines f (M1 x) = M1 <$> gmapInlines f x
+
+class HasInlines a where
+  foldInlines :: Monoid m => (Inline -> m) -> a -> m
+  mapInlines :: Monad m => (Inline -> m Inline) -> a -> m a
+
+  default foldInlines :: (Generic a, GHasInlines (Rep a), Monoid m)
+                      => (Inline -> m) -> a -> m
+  foldInlines f = gfoldInlines f . from
+
+  default mapInlines :: (Generic a, GHasInlines (Rep a), Monad m)
+                      => (Inline -> m Inline) -> a -> m a
+  mapInlines f x = to <$> gmapInlines f (from x)
+
+-- Field: delegate to HasInlines of the field type
+instance HasInlines a => GHasInlines (K1 i a) where
+  gfoldInlines f (K1 x) = foldInlines f x
+  gmapInlines f (K1 x) = K1 <$> mapInlines f x
+
+instance {-# OVERLAPPABLE #-} HasInlines a where
+  foldInlines _ _ = mempty
+  mapInlines _ = pure
+
+instance (HasInlines a, Traversable t, Foldable t) => HasInlines (t a) where
+  foldInlines f = foldMap (foldInlines f)
+  mapInlines f = mapM (mapInlines f)
+
+instance HasInlines Inline where
+  foldInlines f i@(Inline _ ty) =
+    f i <> foldInlines f ty
+  mapInlines f (Inline attr ty) =
+    mapInlines f ty >>= f . Inline attr
+
+instance HasInlines Document
+instance HasInlines Meta
+instance HasInlines Author
+instance HasInlines Block
+instance HasInlines BlockType
+instance HasInlines BlockTitle
+instance HasInlines ListItem
+instance HasInlines TableRow
+instance HasInlines TableCell
+instance HasInlines InlineType
+
+class GHasBlocks f where
+  gfoldBlocks :: Monoid m => (Block -> m) -> f p -> m
+  gmapBlocks :: Monad m => (Block -> m Block) -> f p -> m (f p)
+
+instance GHasBlocks U1 where
+  gfoldBlocks _ _ = mempty
+  gmapBlocks _ _ = pure mempty
+
+instance (GHasBlocks f, GHasBlocks g) => GHasBlocks (f :*: g) where
+  gfoldBlocks f (x :*: y) = gfoldBlocks f x <> gfoldBlocks f y
+  gmapBlocks f (x :*: y) = liftM2 (:*:) (gmapBlocks f x) (gmapBlocks f y)
+
+instance (GHasBlocks f, GHasBlocks g) => GHasBlocks (f :+: g) where
+  gfoldBlocks f (L1 x) = gfoldBlocks f x
+  gfoldBlocks f (R1 y) = gfoldBlocks f y
+  gmapBlocks f (L1 x) = L1 <$> gmapBlocks f x
+  gmapBlocks f (R1 y) = R1 <$> gmapBlocks f y
+
+instance GHasBlocks f => GHasBlocks (M1 i c f) where
+  gfoldBlocks f (M1 x) = gfoldBlocks f x
+  gmapBlocks f (M1 x) = M1 <$> gmapBlocks f x
+
+class HasBlocks a where
+  foldBlocks :: Monoid m => (Block -> m) -> a -> m
+  mapBlocks :: Monad m => (Block -> m Block) -> a -> m a
+
+  default foldBlocks :: (Generic a, GHasBlocks (Rep a), Monoid m)
+                      => (Block -> m) -> a -> m
+  foldBlocks f = gfoldBlocks f . from
+
+  default mapBlocks :: (Generic a, GHasBlocks (Rep a), Monad m)
+                      => (Block -> m Block) -> a -> m a
+  mapBlocks f x = to <$> gmapBlocks f (from x)
+
+-- Field: delegate to HasBlocks of the field type
+instance HasBlocks a => GHasBlocks (K1 i a) where
+  gfoldBlocks f (K1 x) = foldBlocks f x
+  gmapBlocks f (K1 x) = K1 <$> mapBlocks f x
+
+instance {-# OVERLAPPABLE #-} HasBlocks a where
+  foldBlocks _ _ = mempty
+  mapBlocks _ = pure
+
+instance (HasBlocks a, Traversable t, Foldable t) => HasBlocks (t a) where
+  foldBlocks f = foldMap (foldBlocks f)
+  mapBlocks f = mapM (mapBlocks f)
+
+instance HasBlocks Block where
+  foldBlocks f i@(Block _ _ ty) =
+    f i <> foldBlocks f ty
+  mapBlocks f (Block attr mbtit ty) =
+    mapBlocks f ty >>= f . Block attr mbtit
+
+instance HasBlocks Document
+instance HasBlocks Meta
+instance HasBlocks Inline
+instance HasBlocks InlineType
+instance HasBlocks ListItem
+instance HasBlocks TableRow
+instance HasBlocks TableCell
+instance HasBlocks BlockType
+ 
diff --git a/src/AsciiDoc/Parse.hs b/src/AsciiDoc/Parse.hs
new file mode 100644
--- /dev/null
+++ b/src/AsciiDoc/Parse.hs
@@ -0,0 +1,1784 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module AsciiDoc.Parse
+  ( parseDocument
+  ) where
+
+import Prelude hiding (takeWhile)
+import Text.HTML.TagSoup.Entity (lookupNamedEntity)
+import Data.Maybe (isNothing, listToMaybe, fromMaybe)
+import Data.Bifunctor (first)
+import Data.Either (lefts, rights)
+import qualified Data.Map as M
+import qualified Data.Text as T
+import qualified Data.Text.Read as TR
+import Data.Text (Text)
+import Data.List (foldl', intersperse)
+import qualified Data.Attoparsec.Text as A
+import System.FilePath
+import Control.Applicative
+import Control.Monad
+import Control.Monad.State
+import Control.Monad.Reader
+import Data.Char (isAlphaNum, isAscii, isSpace, isLetter, isPunctuation, chr, isDigit,
+                  isUpper, isLower, ord)
+import AsciiDoc.AST
+import AsciiDoc.Generic
+-- import Debug.Trace
+
+-- | Parse an AsciiDoc document into an AST.
+parseDocument :: Monad m
+              => (FilePath -> m Text)
+                  -- ^ Get contents of an included file
+              -> (FilePath -> Int -> String -> m Document)
+                  -- ^ Raise an error given source pos and message
+              -> FilePath
+                  -- ^ Path of file containing the text
+              -> Text -- ^ Text to convert
+              -> m Document
+parseDocument getFileContents raiseError path t =
+   handleResult (parse pDocument path t) >>= handleIncludes
+     >>= resolveAttributeReferences . addIdentifiers
+     >>= resolveCrossReferences
+ where
+  handleResult (Left err) =
+    raiseError path (errorPosition err) (errorMessage err)
+  handleResult (Right r) = pure r
+
+  toAnchorMap d =
+    foldBlocks blockAnchor d <> foldInlines inlineAnchor d
+
+  blockAnchor (Block (Attr _ kvs) _ (Section _ ils _))
+    | Just ident <- M.lookup "id" kvs = M.singleton ident ils
+  blockAnchor _ = mempty
+
+  inlineAnchor (Inline _ (InlineAnchor ident ils)) = M.singleton ident ils
+  inlineAnchor (Inline _ (BibliographyAnchor ident ils)) = M.singleton ident ils
+  inlineAnchor _ = mempty
+
+  resolveCrossReferences d = mapInlines (resolveCrossReference (toAnchorMap d)) d
+  resolveCrossReference anchorMap
+   x@(Inline attr (CrossReference ident Nothing)) =
+    let ident' = T.takeWhileEnd (/= '#') ident -- strip off file part
+    in case M.lookup ident' anchorMap of
+        Just ils -> pure $ Inline attr (CrossReference ident' (Just ils))
+        _ -> pure x
+  resolveCrossReference _ x = pure x
+
+  resolveAttributeReferences doc =
+    mapInlines (goAttref (docAttributes (docMeta doc))) doc
+
+  goAttref atts il@(Inline attr (AttributeReference (AttributeName at))) =
+     case M.lookup at atts of
+       Nothing -> return il
+       Just x -> return $ Inline attr (Str x)
+  goAttref _ il = return il
+
+  handleIncludes = mapBlocks handleIncludeBlock
+
+  handleIncludeBlock (Block attr mbtitle (Include fp Nothing)) =
+    (do contents <- getFileContents fp
+        Block attr mbtitle . Include fp . Just . docBlocks <$>
+          handleResult (parse pDocument fp contents))
+      >>= mapBlocks handleIncludeBlock
+  handleIncludeBlock (Block attr mbtitle
+                         (IncludeListing mblang fp Nothing)) =
+    (do contents <- getFileContents fp
+        pure $ Block attr mbtitle $ IncludeListing mblang fp
+             $ Just (map (`SourceLine` []) (T.lines contents)))
+      >>= mapBlocks handleIncludeBlock
+  handleIncludeBlock x = pure x
+
+-- | Make a relative path relative to a parent's directory.
+-- Leaves absolute paths alone.
+resolvePath :: FilePath -> FilePath -> FilePath
+resolvePath parentPath fp
+  | isRelative fp =
+      normalise (takeDirectory parentPath </> fp)
+  | otherwise = fp
+
+--- Wrapped parser type:
+
+newtype P a = P { unP :: ReaderT ParserConfig (StateT ParserState A.Parser) a }
+  deriving (Functor, Applicative, Alternative, Monad, MonadPlus,
+            MonadFail, MonadReader ParserConfig, MonadState ParserState)
+
+newtype ParserState = ParserState
+                     { counterMap :: M.Map Text (CounterType, Int)
+                     }
+        deriving (Show)
+
+data ParserConfig = ParserConfig
+                    { filePath :: FilePath
+                    , blockContexts :: [BlockContext]
+                    , hardBreaks :: Bool
+                    } deriving (Show)
+
+data ParseError = ParseError { errorPosition :: Int
+                             , errorMessage :: String
+                             } deriving (Show)
+
+parse :: P a -> FilePath -> T.Text -> Either ParseError a
+parse p fp = parse' (ParserConfig{ filePath = fp
+                                 , blockContexts = []
+                                 , hardBreaks = False
+                                 })
+                    (ParserState { counterMap = mempty })
+                    p
+
+parse' :: ParserConfig -> ParserState
+       -> P a -> T.Text -> Either ParseError a
+parse' cfg st p t =
+  go $ A.parse (evalStateT ( runReaderT (unP p) cfg ) st) t
+ where
+  go (A.Fail i _ msg) = Left $ ParseError (T.length t - T.length i) msg
+  go (A.Partial continue) = go (continue "")
+  go (A.Done _i r) = Right r
+
+localP :: (ParserConfig -> ParserConfig) -> P a -> P a
+localP f (P p) = P (local f p)
+
+withBlockContext :: BlockContext -> P a -> P a
+withBlockContext bc =
+  localP (\conf -> conf{ blockContexts = bc : blockContexts conf })
+
+withHardBreaks :: P a -> P a
+withHardBreaks = localP (\conf -> conf{ hardBreaks = True })
+
+liftP :: A.Parser a -> P a
+liftP = P . lift . lift
+
+vchar :: Char -> P ()
+vchar = liftP . void . A.char
+
+char :: Char -> P Char
+char = liftP . A.char
+
+peekChar :: P (Maybe Char)
+peekChar = liftP A.peekChar
+
+peekChar' :: P Char
+peekChar' = liftP A.peekChar'
+
+anyChar :: P Char
+anyChar = liftP A.anyChar
+
+satisfy :: (Char -> Bool) -> P Char
+satisfy = liftP . A.satisfy
+
+space :: P Char
+space = liftP A.space
+
+isEndOfLine :: Char -> Bool
+isEndOfLine = A.isEndOfLine
+
+match :: P a -> P (T.Text, a)
+match p = P $ do
+  parseInfo <- ask
+  parserState <- get
+  lift . lift $ A.match (evalStateT (runReaderT (unP p) parseInfo) parserState)
+
+string :: T.Text -> P T.Text
+string = liftP . A.string
+
+decimal :: Integral a => P a
+decimal = liftP A.decimal
+
+endOfInput :: P ()
+endOfInput = liftP A.endOfInput
+
+endOfLine :: P ()
+endOfLine = liftP A.endOfLine
+
+takeWhile :: (Char -> Bool) -> P T.Text
+takeWhile f = liftP (A.takeWhile f)
+
+takeWhile1 :: (Char -> Bool) -> P T.Text
+takeWhile1 f = liftP (A.takeWhile1 f)
+
+skipWhile :: (Char -> Bool) -> P ()
+skipWhile f = liftP (A.skipWhile f)
+
+skipMany :: P a -> P ()
+skipMany = A.skipMany
+
+option :: Alternative f => a -> f a -> f a
+option = A.option
+
+choice :: [P a] -> P a
+choice = A.choice
+
+count :: Int -> P a -> P [a]
+count = A.count
+
+manyTill :: P a -> P b  -> P [a]
+manyTill = A.manyTill
+
+sepBy :: P a -> P b -> P [a]
+sepBy = A.sepBy
+
+sepBy1 :: P a -> P b -> P [a]
+sepBy1 = A.sepBy1
+
+--- Block parsing:
+
+data BlockContext =
+    SectionContext Int
+  | ListContext Char Int
+  | DelimitedContext Char Int
+  deriving (Show, Eq)
+
+
+pDocument :: P Document
+pDocument = do
+  meta <- pDocumentHeader
+  let minSectionLevel = case M.lookup "doctype" (docAttributes meta) of
+                          Just "book" -> 0
+                          _ -> 1
+  bs <- (case M.lookup "hardbreaks-option" (docAttributes meta) of
+            Just "" -> withHardBreaks
+            _ -> id) $
+        withBlockContext (SectionContext (minSectionLevel - 1)) (many pBlock)
+  skipWhile isSpace
+  endOfInput
+  pure $ Document { docMeta = meta , docBlocks = bs }
+
+pDocumentHeader :: P Meta
+pDocumentHeader = do
+  let handleAttr m (Left k) = M.delete k m
+      handleAttr m (Right (k,v)) = M.insert k v m
+  let defaultDocAttrs = M.insert "sectids" "" mempty
+  skipBlankLines
+  topattrs <- foldl' handleAttr defaultDocAttrs <$> many pDocAttribute
+  skipBlankLines
+  (title, titleAttr) <- option ([], Nothing) $ do
+    (_,titleAttr) <- pTitlesAndAttributes
+    title <- pDocumentTitle
+    pure (title, case titleAttr of
+                   Attr [] kv | M.null kv -> Nothing
+                   _ -> Just titleAttr)
+  authors <- if null title
+                then pure []
+                else option [] pDocumentAuthors
+  revision <- if null title
+                 then pure Nothing
+                 else optional pDocumentRevision
+  attrs <- foldl' handleAttr topattrs <$> many pDocAttribute
+  pure $ Meta{ docTitle = title
+             , docTitleAttributes = titleAttr
+             , docAuthors = authors
+             , docRevision = revision
+             , docAttributes = attrs }
+
+pDocumentTitle :: P [Inline]
+pDocumentTitle = do
+  (vchar '=' <|> vchar '#') <* some (char ' ')
+  pLine >>= parseInlines
+
+pDocumentAuthors :: P [Author]
+pDocumentAuthors = do
+  mbc <- peekChar
+  case mbc of
+    Just c | isSpace c || c == ':' -> mzero
+    _ -> parseAuthors <$> pLine
+
+parseAuthors :: Text -> [Author]
+parseAuthors =
+  map (parseAuthor . T.strip) . T.split (== ';')
+
+parseAuthor :: Text -> Author
+parseAuthor t =
+  Author { authorName = T.strip name
+         , authorEmail = email }
+ where
+  (name, rest) = T.break (== '<') t
+  email = case T.uncons rest of
+            Just ('<', rest') -> Just $ T.takeWhile (/='>') rest'
+            _ -> Nothing
+
+pDocumentRevision :: P Revision
+pDocumentRevision = do
+  vprefix <- option False (True <$ vchar 'v')
+  version <- takeWhile1 (\c -> not (isEndOfLine c) && c /= ',')
+  date <- optional (T.strip <$> (vchar ',' *> space
+               *> takeWhile1 (\c -> not (isEndOfLine c) && c /= ':')))
+  remark <- optional
+            (T.strip <$> (vchar ':' *>  space *> takeWhile (not . isEndOfLine)))
+  endOfLine
+  when (isNothing date && isNothing remark) $ guard vprefix
+  pure  Revision { revVersion = version
+                 , revDate = date
+                 , revRemark = remark
+                 }
+
+pLine :: P Text
+pLine = takeWhile (not . isEndOfLine) <* (endOfLine <|> endOfInput)
+
+
+-- Left key unsets key
+-- Right (key, val) sets key
+pDocAttribute :: P (Either Text (Text, Text))
+pDocAttribute = do
+  vchar ':'
+  unset <- option False $ True <$ vchar '!'
+  k <- pDocAttributeName
+  vchar ':'
+  v <- pLineWithEscapes
+  pure $ if unset
+            then Left k
+            else Right (k,v)
+
+pDocAttributeName :: P Text
+pDocAttributeName = do
+  c <- satisfy (\d -> isAscii d && (isAlphaNum d || d == '_'))
+  cs <- many $
+          satisfy (\d -> isAscii d && (isAlphaNum d || d == '_' || d == '-'))
+  pure $ T.pack (c:cs)
+
+pLineWithEscapes :: P Text
+pLineWithEscapes = do
+  _ <- takeWhile (== ' ')
+  t <- takeWhile isLineEndChar
+  endOfLine
+  case T.stripSuffix "\\" t of
+    Nothing -> pure t
+    Just t' -> do
+      case T.stripSuffix " +" t' of
+        Nothing -> (t' <>) <$> pLineWithEscapes
+        Just t'' -> ((t'' <> "\n") <>) <$> pLineWithEscapes
+
+isLineEndChar :: Char -> Bool
+isLineEndChar '\r' = False
+isLineEndChar '\n' = False
+isLineEndChar _ = True
+
+skipBlankLines :: P ()
+skipBlankLines = do
+  contexts <- asks blockContexts
+  case contexts of
+    ListContext{} : _ -> void $ many $ vchar '+' *> pBlankLine
+    _ -> void $ many pBlankLine
+
+pBlankLine :: P ()
+pBlankLine = takeWhile (\c -> c == ' ' || c == '\t') *> (pLineComment <|> endOfLine)
+
+parseWith :: P a -> Text -> P a
+parseWith p t = do
+  cfg <- ask
+  st <- get
+  let result = parse' cfg st ((,) <$> p <*> get) t
+  case result of
+    Left e -> fail $ errorMessage e
+    Right (x, newst) -> do
+      put newst
+      pure x
+
+parseBlocks :: Text -> P [Block]
+parseBlocks = parseWith (many pBlock) . (<> "\n") . T.strip
+
+parseAsciidoc :: Text -> P Document
+parseAsciidoc = parseWith pDocument . (<> "\n") . T.strip
+
+parseParagraphs :: Text -> P [Block]
+parseParagraphs = parseWith (many pParagraph) . (<> "\n") . T.strip
+ where
+  pParagraph = do
+    skipBlankLines
+    (mbtitle, attr@(Attr _ kvs)) <- pTitlesAndAttributes
+    let hardbreaks = M.lookup "options" kvs == Just "hardbreaks"
+    skipMany (pCommentBlock attr)
+    (if hardbreaks then withHardBreaks else id) $ Block attr mbtitle <$> pPara
+
+parseInlines :: Text -> P [Inline]
+parseInlines = parseWith pInlines . T.strip
+
+pBlock :: P Block
+pBlock = do
+  contexts <- asks blockContexts
+  skipBlankLines
+  (mbtitle, attr) <- pTitlesAndAttributes
+  case contexts of
+    ListContext{} : _ -> skipWhile (== ' ')
+    _ -> pure ()
+  skipMany (pCommentBlock attr)
+  let hardbreaks =
+       case attr of
+          Attr _ kvs
+            | Just opts <- M.lookup "options" kvs
+              -> "hardbreaks" `T.isInfixOf` opts
+          _ -> False
+  (if hardbreaks then withHardBreaks else id) $
+        pBlockMacro mbtitle attr
+    <|> pDiscreteHeading mbtitle attr
+    <|> pExampleBlock mbtitle attr
+    <|> pSidebar mbtitle attr
+    <|> pLiteralBlock mbtitle attr
+    <|> pListing mbtitle attr
+    <|> pFenced mbtitle attr
+    <|> pVerse mbtitle attr
+    <|> pQuoteBlock mbtitle attr
+    <|> pPassBlock mbtitle attr
+    <|> pOpenBlock mbtitle attr
+    <|> pTable mbtitle attr
+    <|> Block attr mbtitle <$>
+          choice
+            [ pSection
+            , pThematicBreak
+            , pPageBreak
+            , pList
+            , pDefinitionList
+            , pIndentedLiteral
+            , pPara
+            ]
+
+
+pIndentedLiteral :: P BlockType
+pIndentedLiteral = do
+  xs <- some pIndentedLine
+  let minIndent = minimum (map fst xs)
+  let xs' = map (first (\x -> x - minIndent)) xs
+  let t = T.unlines $ map (\(ind, x) -> T.replicate ind " " <> x) xs'
+  pure $ LiteralBlock t
+
+pIndentedLine :: P (Int, Text)
+pIndentedLine = do
+  ind <- length <$> some (vchar ' ')
+  t <- pLine
+  pure (ind, t)
+
+pPageBreak :: P BlockType
+pPageBreak = PageBreak <$ (string "<<<" <* pBlankLine)
+
+pThematicBreak :: P BlockType
+pThematicBreak = ThematicBreak <$
+  (pThematicBreakAsciidoc <|> pThematicBreakMarkdown '-' <|> pThematicBreakMarkdown '*')
+ where
+   pThematicBreakAsciidoc = string "'''" *> pBlankLine
+   pThematicBreakMarkdown c = count 3 (vchar c *> many (vchar ' ')) *> pBlankLine
+
+pCommentBlock :: Attr -> P ()
+pCommentBlock attr = pDelimitedCommentBlock <|> pAlternateCommentBlock
+ where
+  pDelimitedCommentBlock = void $ pDelimitedLiteralBlock '/' 4
+  pAlternateCommentBlock = do
+    case attr of
+      Attr ["comment"] _ ->
+        void (pDelimitedLiteralBlock '-' 2) <|>
+                void (match (withBlockContext (SectionContext (-1)) pPara))
+      _ -> mzero
+
+pBlockMacro :: Maybe BlockTitle -> Attr -> P Block
+pBlockMacro mbtitle attr = do
+  (name, target) <- pBlockMacro'
+  handleBlockMacro mbtitle attr name target
+
+pBlockMacro' :: P (Text, Text)
+pBlockMacro' = do
+  name <- choice (map (\n -> string n <* string "::") (M.keys blockMacros))
+  let targetChars = mconcat <$> some
+        (takeWhile1 (\c -> not (isSpace c) && c /= '[' && c /= '+')
+         <|>
+         (vchar '\\' *> (T.singleton <$> satisfy (\c -> c == '[' || c == '+')))
+         <|>
+         (do Inline _ (Str t) <- pInMatched False '+' mempty (pure . Str)
+             pure t))
+  target <- mconcat <$> many targetChars
+  pure (name, target)
+
+handleBlockMacro :: Maybe BlockTitle -> Attr -> Text -> Text -> P Block
+handleBlockMacro mbtitle attr name target =
+  case M.lookup name blockMacros of
+    Nothing -> mzero
+    Just f -> f mbtitle attr target
+
+blockMacros :: M.Map Text (Maybe BlockTitle -> Attr -> Text -> P Block)
+blockMacros = M.fromList
+  [ ("image", \mbtitle attr target -> do
+        (Attr ps kvs) <- pAttributes
+        let (mbalt, mbw, mbh) =
+              case ps of
+                (x:y:z:_) -> (Just (AltText x),
+                              Width <$> readDecimal y, Height <$> readDecimal z)
+                [x,y] -> (Just (AltText x), Width <$> readDecimal y, Nothing)
+                [x] -> (Just (AltText x), Nothing, Nothing)
+                [] -> (Nothing, Nothing, Nothing)
+        pure $ Block (Attr mempty kvs <> attr) mbtitle
+             $ BlockImage (Target target) mbalt mbw mbh)
+  , ("video", \mbtitle attr target -> do
+        attr' <- pAttributes
+        pure $ Block (attr' <> attr) mbtitle
+             $ BlockVideo (Target target))
+  , ("audio", \mbtitle attr target -> do
+        attr' <- pAttributes
+        pure $ Block (attr' <> attr) mbtitle
+             $ BlockAudio (Target target))
+  , ("toc", \mbtitle attr _target -> do
+        attr' <- pAttributes
+        pure $ Block (attr' <> attr) mbtitle TOC)
+  , ("include", \mbtitle attr target -> do
+        attr' <- pAttributes
+        fp <- asks filePath
+        let path = resolvePath fp (T.unpack target)
+        pure $ Block (attr' <> attr) mbtitle $ Include path Nothing)
+  ]
+
+pSection :: P BlockType
+pSection = do
+  contexts <- asks blockContexts
+  case contexts of
+    SectionContext sectionLevel : _ -> do
+      lev <- (\x -> length x - 1) <$> (some (vchar '=') <|> some (vchar '#'))
+      guard (lev > sectionLevel && lev >= 0 && lev <= 5)
+      vchar ' '
+      title <- pLine >>= parseInlines
+      contents <- withBlockContext (SectionContext lev) $ many pBlock
+      -- note: we use sectionLevel, not lev, so in improperly nested content, e.g.,
+      -- == foo
+      -- ==== bar
+      -- ==== baz
+      -- bar is a level-3 section and will contain baz!
+      pure $ Section (Level (sectionLevel + 1)) title contents
+    _ -> mzero
+
+pDiscreteHeading :: Maybe BlockTitle -> Attr -> P Block
+pDiscreteHeading mbtitle attr = do
+  let (Attr ps kvs) = attr
+  guard $ case ps of
+            ("discrete":_) -> True
+            _ -> False
+  lev <- (\x -> length x - 1) <$> (some (vchar '=') <|> some (vchar '#'))
+  guard (lev >= 0 && lev <= 5)
+  vchar ' '
+  title <- pLine >>= parseInlines
+  pure $ Block (Attr (drop 1 ps) kvs) mbtitle $ DiscreteHeading (Level lev) title
+
+pTitlesAndAttributes :: P (Maybe BlockTitle, Attr)
+pTitlesAndAttributes = do
+  items <- many pTitleOrAttribute
+  let title = listToMaybe $ lefts items
+  let attr = mconcat $ rights items
+  pure (title, attr)
+
+pTitleOrAttribute :: P (Either BlockTitle Attr)
+pTitleOrAttribute =
+  ((Left <$> pTitle)
+    <|> (Right <$> (pAnchor <* endOfLine))
+    <|> (Right <$> (pAttributes <* endOfLine))
+  ) <* skipMany pBlankLine
+
+pAnchor :: P Attr
+pAnchor = do
+  void $ string "[["  -- [[anchor]] can set id
+  anchor <- takeWhile1 (\c -> not (isEndOfLine c || c == ']' || isSpace c))
+  void $ string "]]"
+  pure (Attr mempty (M.singleton "id" anchor))
+
+pTitle :: P BlockTitle
+pTitle = BlockTitle <$>
+           (do vchar '.'
+               mbc <- peekChar
+               guard $ case mbc of
+                         Just ' ' -> False
+                         Just '.' -> False
+                         _ -> True
+               pLineWithEscapes >>= parseInlines)
+
+pDefinitionList :: P BlockType
+pDefinitionList =
+  DefinitionList <$> some pDefinitionListItem
+
+pDefinitionListItem :: P ([Inline],[Block])
+pDefinitionListItem = do
+  contexts <- asks blockContexts
+  let marker = (do t <- takeWhile1 (== ':')
+                   case contexts of
+                       ListContext ':' n : _ -> guard (T.length t == n + 2)
+                       _ -> guard (T.length t == 2))
+  skipWhile (== ' ')
+  term <- manyTill (takeWhile1 (\c -> not (isEndOfLine c || c == ':'))
+                              <|> takeWhile1 (==':')) marker
+                    >>= parseInlines . mconcat
+  skipWhile (== ' ')
+  option () endOfLine
+  skipWhile (== ' ')
+  let newContext = case contexts of
+                      ListContext ':' n : _ -> ListContext ':' (n + 1)
+                      _ -> ListContext ':' 1
+  defn <- withBlockContext newContext (many pBlock)
+  void $ many pBlankLine
+  pure (term, defn)
+
+pList :: P BlockType
+pList = do
+  (c, lev, mbStart, mbCheckboxState) <- pAnyListItemStart
+  let guardContext ctx =
+       case ctx of
+         ListContext c' lev' -> guard $ c /= c' || lev > lev'
+         _ -> pure ()
+  asks blockContexts >>= mapM_ guardContext
+  ListItem _ bs <- withBlockContext (ListContext c lev) pListItem
+  let x = ListItem mbCheckboxState bs
+  xs <- many (pListItemStart c lev *> withBlockContext (ListContext c lev) pListItem)
+  let listType
+        | c == '-'
+        , Just _ <- mbCheckboxState
+          = CheckList
+        | c == '.' || c == '1' = OrderedList (Level lev) mbStart
+        | c == '<' = CalloutList
+        | otherwise = BulletList (Level lev)
+  pure $ List listType (x:xs)
+
+pAnyListItemStart :: P (Char, Int, Maybe Int, Maybe CheckboxState)
+pAnyListItemStart = (do
+  skipWhile (== ' ')
+  c <- satisfy (\c -> c == '*' || c == '.' || c == '-' || c == '<')
+  lev <- if c == '<'
+            then pure 1
+            else (+ 1) . T.length <$> takeWhile (== c)
+  when (c == '<') $ do  -- callout list <1> or <.>
+    void $ string "." <|> takeWhile1 isDigit
+    vchar '>'
+  vchar ' '
+  mbCheck <- if c == '-' || c == '*'
+                then optional pCheckbox
+                else pure Nothing
+  pure (c, lev, Nothing, mbCheck))
+ <|> (do d <- decimal
+         vchar '.'
+         vchar ' '
+         pure ('1', 1, Just d, Nothing))
+
+
+pCheckbox :: P CheckboxState
+pCheckbox = do
+  skipWhile (==' ')
+  vchar '['
+  c <- char ' ' <|> char 'x' <|> char '*'
+  vchar ']'
+  vchar ' '
+  pure $ if c == ' '
+            then Unchecked
+            else Checked
+
+pListItemStart :: Char -> Int -> P ()
+pListItemStart c lev = do
+  skipWhile (== ' ')
+  case c of
+    '<' -> vchar '<' *> (string "." <|> takeWhile1 isDigit) *> vchar '>'
+    '1' -> do guard (lev == 1)
+              void (decimal :: P Int)
+              vchar '.'
+    _ -> void $ count lev (vchar c)
+  vchar ' '
+
+pListItem :: P ListItem
+pListItem = do
+  mbCheckboxState <- optional pCheckbox
+  skipWhile (==' ')
+  bs <- many pBlock
+  pure $ ListItem mbCheckboxState bs
+
+pDelimitedLiteralBlock :: Char -> Int -> P [T.Text]
+pDelimitedLiteralBlock c minimumNumber = do
+  len <- length <$> some (vchar c) <* pBlankLine
+  guard $ len >= minimumNumber
+  let endFence = count len (vchar c) *> pBlankLine
+  manyTill pLine endFence
+
+pDelimitedBlock :: Char -> Int -> P [Block]
+pDelimitedBlock c minimumNumber = do
+  len <- length <$> some (vchar c) <* pBlankLine
+  guard $ len >= minimumNumber
+  let endFence = count len (vchar c) *> pBlankLine
+  withBlockContext (DelimitedContext c len) $
+    manyTill pBlock endFence
+
+pPassBlock :: Maybe BlockTitle -> Attr -> P Block
+pPassBlock mbtitle attr = do
+  t <- T.unlines <$> pDelimitedLiteralBlock '+' 4
+  case attr of
+    Attr ("stem":ps) kvs ->
+      pure $ Block (Attr ps kvs) mbtitle $ MathBlock Nothing t
+    Attr ("asciimath":ps) kvs ->
+      pure $ Block (Attr ps kvs) mbtitle $ MathBlock (Just AsciiMath) t
+    Attr ("latexmath":ps) kvs ->
+      pure $ Block (Attr ps kvs) mbtitle $ MathBlock (Just LaTeXMath) t
+    _ -> pure $ Block attr mbtitle $ PassthroughBlock t
+
+pLiteralBlock :: Maybe BlockTitle -> Attr -> P Block
+pLiteralBlock mbtitle attr =
+  (Block attr mbtitle . LiteralBlock . T.unlines <$> pDelimitedLiteralBlock '.' 4)
+  <|>
+  case attr of
+    Attr ("literal":ps) kvs -> do
+      t <- T.unlines <$> manyTill pLine (pBlankLine <|> endOfInput)
+      pure $ Block (Attr ps kvs) mbtitle $ LiteralBlock t
+    _ -> mzero
+
+pFenced :: Maybe BlockTitle -> Attr -> P Block
+pFenced mbtitle attr = do
+  ticks <- takeWhile1 (== '`')
+  guard $ T.length ticks >= 3
+  lang' <- pLine
+  let mblang = case T.strip lang' of
+                 "" -> Nothing
+                 l -> Just (Language l)
+  lns <- toSourceLines <$> manyTill pLine (string ticks)
+  pure $ Block attr mbtitle $ Listing mblang lns
+
+pListing :: Maybe BlockTitle -> Attr -> P Block
+pListing mbtitle attr = (do
+  let (mbLang, attr') =
+        case attr of
+          Attr (_:lang:ps) kvs -> (Just (Language lang), Attr ps kvs)
+          Attr ["source"] kvs -> (Nothing, Attr [] kvs)
+          _ -> (Nothing, attr)
+  lns <- toSourceLines <$> pDelimitedLiteralBlock '-' 4
+  fp <- asks filePath
+  pure $ Block attr' mbtitle $
+    case lns of
+      [SourceLine x []] | "include::" `T.isPrefixOf` x
+          , Right ("include", target) <- parse pBlockMacro' fp x
+          -> IncludeListing mbLang (resolvePath fp (T.unpack target)) Nothing
+      _ -> Listing mbLang lns)
+ <|>
+  (case attr of
+    Attr ("listing":ps) kvs -> do
+      lns <- toSourceLines <$> manyTill pLine (pBlankLine <|> endOfInput)
+      pure $ Block (Attr ps kvs) mbtitle $ Listing Nothing lns
+    Attr ("source":lang:ps) kvs -> do
+      lns <- toSourceLines <$> manyTill pLine (pBlankLine <|> endOfInput)
+      pure $ Block (Attr ps kvs) mbtitle
+           $ Listing (Just (Language lang)) lns
+    Attr ["source"] kvs -> do
+      lns <- toSourceLines <$> manyTill pLine (pBlankLine <|> endOfInput)
+      pure $ Block (Attr [] kvs) mbtitle $ Listing Nothing lns
+    _ -> mzero)
+
+-- parse out callouts
+toSourceLines :: [T.Text] -> [SourceLine]
+toSourceLines = go 1
+ where
+   go _ [] = []
+   go nextnum (t:ts) =
+     let (t', callouts) = getCallouts [] t
+         (nextnum'', callouts') =
+                    foldl' (\(nextnum', cs) c ->
+                               case c of
+                                 Nothing -> (nextnum' + 1, Callout nextnum' : cs)
+                                 Just i -> (i + 1, Callout i : cs))
+                       (nextnum, []) callouts
+     in SourceLine t' (reverse callouts') : go nextnum'' ts
+   getCallouts callouts t =
+    case T.breakOnAll "<" t of
+      [] -> (t, callouts)
+      xs@(_:_) ->
+        let (t', rest) = last xs
+            (ds, rest') = T.span (\c -> isDigit c || c == '.') (T.drop 1 rest)
+         in if T.strip rest' == ">" && (T.all isDigit ds || ds == ".")
+               then
+                 if ds == "."
+                    then getCallouts (Nothing : callouts) (T.stripEnd t')
+                    else case readDecimal ds of
+                           Just num -> getCallouts (Just num : callouts) (T.stripEnd t')
+                           Nothing -> (t, callouts)
+               else (t, callouts)
+
+pExampleBlock :: Maybe BlockTitle -> Attr -> P Block
+pExampleBlock mbtitle attr = do
+  bs <- pDelimitedBlock '=' 4
+  pure $ case attr of
+    Attr (p:ps) kvs |
+      Just adm <- parseAdmonitionType p ->
+        Block (Attr ps kvs) mbtitle $ Admonition adm bs
+    _ -> Block attr mbtitle $ ExampleBlock bs
+
+pSidebar :: Maybe BlockTitle -> Attr -> P Block
+pSidebar mbtitle attr =
+  Block attr mbtitle . Sidebar <$> pDelimitedBlock '*' 4
+
+pVerse :: Maybe BlockTitle -> Attr -> P Block
+pVerse mbtitle (Attr ("verse":xs) kvs) = do
+  let attribution = T.intercalate ", " xs
+  let mbAttribution = if T.null attribution
+                         then Nothing
+                         else Just (Attribution attribution)
+  bs <- withHardBreaks $
+           pDelimitedBlock '-' 2
+       <|> pDelimitedBlock '_' 4
+       <|> ((:[]) . Block mempty Nothing <$> pPara)
+  pure $ Block (Attr [] kvs) mbtitle $ Verse mbAttribution bs
+pVerse _ _ = mzero
+
+pQuoteBlock :: Maybe BlockTitle -> Attr -> P Block
+pQuoteBlock mbtitle (Attr ("quote":xs) kvs) = do
+  let attribution = T.intercalate ", " xs
+  let mbAttribution = if T.null attribution
+                         then Nothing
+                         else Just (Attribution attribution)
+  bs <-    pDelimitedBlock '_' 4
+       <|> pDelimitedBlock '-' 2
+       <|> ((:[]) . Block mempty Nothing <$> pPara)
+  pure $ Block (Attr [] kvs) mbtitle $ QuoteBlock mbAttribution bs
+pQuoteBlock _ _ = mzero
+
+pOpenBlock :: Maybe BlockTitle -> Attr -> P Block
+pOpenBlock mbtitle attr = Block attr mbtitle <$>
+  ((OpenBlock <$> pDelimitedBlock '-' 2)
+   <|>
+  (QuoteBlock Nothing <$>
+     (pDelimitedBlock '-' 2 <|> pDelimitedBlock '_' 4)))
+
+parseAdmonitionType :: T.Text -> Maybe AdmonitionType
+parseAdmonitionType t =
+  case t of
+    "NOTE" -> Just Note
+    "TIP" -> Just Tip
+    "IMPORTANT" -> Just Important
+    "CAUTION" -> Just Caution
+    "WARNING" -> Just Warning
+    _ -> Nothing
+
+pPara :: P BlockType
+pPara = do
+  t' <- pNormalLine
+  contexts <- asks blockContexts
+  case contexts of
+    SectionContext{} : _ | not (T.null t') -> do
+      case T.head t' of
+        c | c == '=' || c == '#' -> do
+          let eqs = T.length $ T.takeWhile (==c) t'
+          let after = T.take 1 $ T.dropWhile (==c) t'
+          guard $ eqs < 1 || eqs > 6 || after /= " " -- section heading
+        _ -> pure ()
+    _ -> pure ()
+  let (a,b) = T.break (== ':') t'
+  let (t, mbAdmonition)
+        = if ": " `T.isPrefixOf` b
+          then
+            let newt = T.drop 2 b
+            in  case parseAdmonitionType a of
+                 Just adm -> (newt, Just adm)
+                 Nothing -> (t', Nothing)
+          else (t', Nothing)
+  ts <- many pNormalLine
+  hardbreaks <- asks hardBreaks
+  ils <- (if hardbreaks
+             then newlinesToHardbreaks
+             else id) <$> parseInlines (T.unlines (t:ts))
+  pure $ case mbAdmonition of
+           Nothing -> Paragraph ils
+           Just admonType -> Admonition admonType
+                  [Block mempty Nothing (Paragraph ils)]
+
+newlinesToHardbreaks :: [Inline] -> [Inline]
+newlinesToHardbreaks [] = []
+newlinesToHardbreaks (Inline attr (Str t) : xs) | T.any (=='\n') t =
+  intersperse (Inline attr HardBreak)
+    (map (Inline attr . Str) (T.lines t)) ++ newlinesToHardbreaks xs
+newlinesToHardbreaks (x : xs) = x : newlinesToHardbreaks xs
+
+pNormalLine :: P Text
+pNormalLine = do
+  t <- pLine
+  fp <- asks filePath
+  guard $ not $ T.all (\c -> c == ' ' || c == '\t') t
+  guard $ T.take 1 t /= "[" ||
+          case parse (pAttributes *> skipWhile isSpace *> endOfInput)
+                     fp t of
+                Left _ -> True
+                _ -> False
+  let t' = T.stripEnd t
+  contexts <- asks blockContexts
+  let delims = [(c, num) | DelimitedContext c num <- contexts]
+  mapM_ (\(c, num) -> guard (not (T.all (== c) t' && T.length t' == num)))
+        delims
+  case contexts of
+    ListContext{} : _ -> do
+      guard $ t' /= "+"
+      guard $ not $ "::" `T.isInfixOf` t'
+      guard $ case parse pAnyListItemStart fp (T.strip t) of
+                Left _ -> True
+                _ -> False
+    _ -> pure ()
+  pure t
+
+
+--- Table parsing:
+
+pTableBorder :: P TableSyntax
+pTableBorder = do
+  syntax <- (PSV <$ vchar '|') <|> (DSV <$ vchar ':') <|> (CSV <$ vchar ',')
+  void $ string "==="
+  skipWhile (=='=')
+  pBlankLine
+  skipMany pBlankLine
+  pure syntax
+
+pTable :: Maybe BlockTitle -> Attr -> P Block
+pTable mbtitle (Attr ps kvs) = do
+  syntax' <- pTableBorder
+  mbcolspecs <- maybe (pure Nothing) (fmap Just . parseColspecs)
+                  (M.lookup "cols" kvs)
+  let options = maybe [] T.words $ M.lookup "options" kvs
+  let syntax = case M.lookup "format" kvs of
+                 Just "psv" -> PSV
+                 Just "csv" -> CSV
+                 Just "dsv" -> DSV
+                 Just "tsv" -> TSV
+                 _ -> syntax'
+  let mbsep = case M.lookup "separator" kvs of
+                 Just sep ->
+                   case T.uncons sep of
+                     Just (c,_) -> Just c
+                     _ -> Nothing
+                 _ -> Nothing
+  let tableOpts = TableOpts { tableSyntax = syntax
+                            , tableSeparator = mbsep
+                            , tableHeader = "header" `elem` options ||
+                                "noheader" `notElem` options
+                            , tableFooter = "footer" `elem` options ||
+                                "nofooter" `notElem` options
+                            }
+  let getRows mbspecs rowspans = (([],[]) <$ pTableBorder) <|>
+         do -- for this row, we modify the specs based on rowspans
+            -- if there are rowspans from rows above, we need to skip some:
+            let mbspecs' = case mbspecs of
+                             Nothing -> Nothing
+                             Just specs' -> Just [s | (s,0) <- zip specs' rowspans]
+            row@(TableRow cells) <- pTableRow tableOpts mbspecs'
+            let numcols = sum (map cellColspan cells)
+            let specs = fromMaybe (replicate numcols defaultColumnSpec) mbspecs
+            -- now, update rowspans in light of new row
+            let updateRowspans [] rs = rs
+                updateRowspans (c:cs) rs =
+                  map (+ (cellRowspan c - 1)) (take (cellColspan c) rs)
+                  ++ updateRowspans cs (drop (cellColspan c) rs)
+            let rowspans' = updateRowspans cells rowspans
+            (\(rows, colspecs') -> (row:rows, case rows of
+                                                 [] -> specs
+                                                 _ -> colspecs'))
+                                     <$> getRows (Just specs) rowspans'
+  (rows, colspecs') <- getRows mbcolspecs (repeat (0 :: Int))
+  let attr' = Attr ps $ M.delete "format" .
+                        M.delete "separator" .
+                        M.delete "cols" .
+                        M.delete "options" $ kvs
+  let (mbHead, rest)
+        | tableHeader tableOpts = (Just (take 1 rows), drop 1 rows)
+        | otherwise = (Nothing, rows)
+  let (mbFoot, bodyRows)
+        | tableFooter tableOpts
+        , not (null rest) = (Just (drop (length rest - 1) rest),
+                             take (length rest - 1) rest)
+        | otherwise = (Nothing, rest)
+  pure $ Block attr' mbtitle $ Table colspecs' mbHead bodyRows mbFoot
+
+parseColspecs :: T.Text -> P [ColumnSpec]
+parseColspecs t = do
+  fp <- asks filePath
+  case parse pColspecs fp t of
+    Left e -> fail $ errorMessage e
+    Right cs -> pure cs
+
+pColspecs :: P [ColumnSpec]
+pColspecs = mconcat <$> sepBy pColspecPart pComma <* option () pComma
+
+pColspecPart :: P [ColumnSpec]
+pColspecPart = do
+  multiplier <- option 1 pMultiplier
+  replicate multiplier <$> pColspec
+
+pMultiplier :: P Int
+pMultiplier = decimal <* vchar '*'
+
+pColspec :: P ColumnSpec
+pColspec = ColumnSpec <$> optional pHorizAlign
+                      <*> optional pVertAlign
+                      <*> (pWidth <|> pure Nothing)
+                      <*> (toCellStyle <$> satisfy (A.inClass "adehlms")
+                             <|> pure Nothing)
+
+pHorizAlign :: P HorizAlign
+pHorizAlign =
+  (AlignLeft <$ vchar '<') <|> (AlignCenter <$ vchar '^') <|> (AlignRight <$ vchar '>')
+
+pVertAlign :: P VertAlign
+pVertAlign = do
+  vchar '.'
+  (AlignTop <$ vchar '<') <|> (AlignMiddle <$ vchar '^') <|> (AlignBottom <$ vchar '>')
+
+pWidth :: P (Maybe Int)
+pWidth = (Just <$> (decimal <* option () (vchar '%'))) <|> (Nothing <$ vchar '~')
+
+data TableSyntax =
+    PSV
+  | CSV
+  | TSV
+  | DSV
+  deriving (Show)
+
+data TableOpts =
+  TableOpts { tableSyntax :: TableSyntax
+            , tableSeparator :: Maybe Char
+            , tableHeader :: Bool
+            , tableFooter :: Bool
+            }
+  deriving (Show)
+
+pTableRow :: TableOpts -> Maybe [ColumnSpec] -> P TableRow
+pTableRow opts mbcolspecs = TableRow <$>
+  case tableSyntax opts of
+       PSV
+         | Just colspecs <- mbcolspecs  ->
+             let getCell :: [ColumnSpec] -> P [TableCell]
+                 getCell [] = pure []
+                 getCell colspecs' = do
+                   xs <- pTableCellPSV (tableSeparator opts) True colspecs'
+                   skipMany pBlankLine
+                   (xs ++) <$> getCell (drop (sum (map cellColspan xs)) colspecs')
+             in  getCell colspecs
+         | otherwise -> mconcat <$>
+               some (pTableCellPSV (tableSeparator opts)
+                       False (repeat defaultColumnSpec))
+                     <* skipMany pBlankLine
+       CSV -> pCSVTableRow (fromMaybe ',' $ tableSeparator opts) mbcolspecs
+       TSV -> pCSVTableRow (fromMaybe '\t' $ tableSeparator opts) mbcolspecs
+       DSV -> pDSVTableRow (fromMaybe ':' $ tableSeparator opts) mbcolspecs
+
+defaultColumnSpec :: ColumnSpec
+defaultColumnSpec = ColumnSpec Nothing Nothing Nothing Nothing
+
+-- Note: AsciiDoc weirdly gobbles cells for rows even across CSV
+-- row boundaries. We're not going to do that.
+
+-- allows "; escape this as ""; delim can't be escaped
+pCSVTableRow :: Char -> Maybe [ColumnSpec] -> P [TableCell]
+pCSVTableRow delim mbcolspecs = do
+  let colspecs = fromMaybe [] mbcolspecs
+  as <- sepBy (pCSVCell delim) (vchar delim)
+  pBlankLine *> skipMany pBlankLine
+  zipWithM toBasicCell as (colspecs ++ repeat defaultColumnSpec)
+
+pCSVCell :: Char -> P T.Text
+pCSVCell delim = do
+  skipWhile (== ' ')
+  mbc <- peekChar
+  case mbc of
+    Just '"'
+      -> vchar '"' *>
+          (T.pack <$>
+            manyTill (satisfy (/='"') <|> ('"' <$ string "\"\"")) (vchar '"'))
+    _ -> T.strip . T.replace "\"\"" "\"" <$>
+           takeWhile1 (\c -> c /= delim && not (isEndOfLine c))
+
+-- no "; escape delim with backslash
+pDSVTableRow:: Char -> Maybe [ColumnSpec] -> P [TableCell]
+pDSVTableRow delim mbcolspecs = do
+  let colspecs = fromMaybe [] mbcolspecs
+  as <- sepBy (pDSVCell delim) (vchar delim)
+  pBlankLine *> skipMany pBlankLine
+  zipWithM toBasicCell as (colspecs ++ repeat defaultColumnSpec)
+
+pDSVCell :: Char -> P T.Text
+pDSVCell delim =
+  T.strip . mconcat <$>
+    many (takeWhile1 (\c -> c /= delim && c /= '\\' && not (isEndOfLine c))
+       <|> (vchar '\\' *> ((\c -> "\\" <> T.singleton c) <$> anyChar)))
+
+toBasicCell :: T.Text -> ColumnSpec -> P TableCell
+toBasicCell t colspec = do
+  bs <- parseCellContents (fromMaybe DefaultStyle (colStyle colspec)) t
+  pure TableCell
+         { cellContent = bs
+         , cellHorizAlign = Nothing
+         , cellVertAlign = Nothing
+         , cellColspan = 1
+         , cellRowspan = 1
+         }
+
+
+pTableCellPSV :: Maybe Char -> Bool -> [ColumnSpec] -> P [TableCell]
+pTableCellPSV mbsep allowNewlines colspecs = do
+  let sep = fromMaybe '|' mbsep
+  cellData <- pCellSep sep
+  t <- T.pack <$>
+         many
+          (notFollowedBy (void (pCellSep sep) <|> void pTableBorder) *>
+           ((vchar '\\' *> char sep)
+             <|> satisfy (not . isEndOfLine)
+             <|> if allowNewlines
+                    then satisfy isEndOfLine
+                    else satisfy isEndOfLine <* notFollowedBy (pCellSep sep)))
+  let cell' = TableCell
+               { cellContent = []
+               , cellHorizAlign = cHorizAlign cellData
+               , cellVertAlign = cVertAlign cellData
+               , cellColspan = fromMaybe 1 $ cColspan cellData
+               , cellRowspan = fromMaybe 1 $ cRowspan cellData
+               }
+  let rawcells = replicate (cDuplicate cellData) (cell', t)
+  reverse . fst <$> foldM (\(cells, specs) (cell, rawtext) -> do
+                        let defsty = case specs of
+                                       spec:_ -> colStyle spec
+                                       _ -> Nothing
+                        let sty = fromMaybe DefaultStyle $ cStyle cellData <|> defsty
+                        bs <- parseCellContents sty rawtext
+                        pure (cell{ cellContent = bs } : cells,
+                              drop (cellColspan cell) specs))
+                ([],colspecs)
+                rawcells
+
+
+parseCellContents :: CellStyle -> T.Text -> P [Block]
+parseCellContents sty t =
+  case sty of
+    AsciiDocStyle -> docBlocks <$> parseAsciidoc t
+    DefaultStyle -> parseParagraphs t
+    LiteralStyle -> pure [Block mempty Nothing $ LiteralBlock t]
+    EmphasisStyle -> map (surroundPara Italic) <$> parseBlocks t
+    StrongStyle -> map (surroundPara Bold) <$> parseBlocks t
+    MonospaceStyle -> map (surroundPara Monospace) <$> parseBlocks t
+    HeaderStyle -> parseBlocks t
+ where
+   surroundPara :: ([Inline] -> InlineType) -> Block -> Block
+   surroundPara bt (Block attr mbtitle (Paragraph ils)) =
+     Block attr mbtitle (Paragraph [Inline mempty $ bt ils])
+   surroundPara _ b = b
+
+
+data CellData =
+  CellData
+  { cDuplicate :: Int
+  , cHorizAlign :: Maybe HorizAlign
+  , cVertAlign :: Maybe VertAlign
+  , cColspan :: Maybe Int
+  , cRowspan :: Maybe Int
+  , cStyle :: Maybe CellStyle }
+  deriving (Show)
+
+toCellStyle :: Char -> Maybe CellStyle
+toCellStyle 'a' = Just AsciiDocStyle
+toCellStyle 'd' = Just DefaultStyle
+toCellStyle 'e' = Just EmphasisStyle
+toCellStyle 'h' = Just HeaderStyle
+toCellStyle 'l' = Just LiteralStyle
+toCellStyle 'm' = Just MonospaceStyle
+toCellStyle 's' = Just StrongStyle
+toCellStyle _   = Nothing
+
+-- 2+| colspan 2
+-- 3.+| rowspan 3
+-- 2.3+| colspan 2, rowspan 3
+-- 2*| duplicate cell twice
+-- 2*.3+^.>s| duplicate 2x, rowspan 3, top align, right align, s style
+pCellSep :: Char -> P CellData
+pCellSep sep = do
+  mult <- option 1 pMultiplier
+  (colspan, rowspan) <- option (Nothing, Nothing) $ do
+    a <- optional decimal
+    b <- optional $ vchar '.' *> decimal
+    guard $ not (isNothing a && isNothing b)
+    vchar '+'
+    pure (a, b)
+  halign <- optional pHorizAlign
+  valign <- optional pVertAlign
+  sty <- (toCellStyle <$> satisfy (A.inClass "adehlms")) <|> pure Nothing
+  notFollowedBy pTableBorder <* vchar sep
+  pure $ CellData
+    { cDuplicate = mult
+    , cHorizAlign = halign
+    , cVertAlign = valign
+    , cColspan = colspan
+    , cRowspan = rowspan
+    , cStyle = sty
+    }
+
+
+--- Inline parsing:
+
+pInlines :: P [Inline]
+pInlines = pInlines' []
+
+pComma :: P ()
+pComma = vchar ',' <* skipWhile isSpace
+
+pFormattedTextAttributes :: P Attr
+pFormattedTextAttributes = do
+  vchar '['
+  as <- pShorthandAttributes
+  ps <- option []
+         (do unless (as == mempty) pComma
+             sepBy1 pAttributeValue pComma <* option () pComma)
+  vchar ']'
+  if as == mempty
+     then
+       case ps of
+         [] -> pure mempty
+         (x:_) -> pure $ Attr [] (M.fromList [("role",x)])
+     else pure as
+
+pAttributes :: P Attr
+pAttributes = do
+  vchar '['
+  (xs, as) <- option ([], mempty) $ do
+    x <- takeWhile (\c -> isAlphaNum c || c == '-' || c == '_')
+    as <- pShorthandAttributes
+    case as of
+       Attr [] m | M.null m -> mzero
+       _ -> pure ([x | not (T.null x)] , as)
+  bs <- option []
+         (do unless (as == mempty) pComma
+             sepBy pAttribute pComma <* option () pComma)
+  vchar ']'
+  let positional = xs ++ lefts bs
+  let kvs = rights bs
+  pure $ as <> Attr positional (M.fromList kvs)
+
+pAttribute :: P (Either Text (Text,Text))
+pAttribute = (Right <$> pKeyValue) <|> (Left <$> pPositional)
+
+pKeyValue :: P (Text, Text)
+pKeyValue = do
+  k <- takeWhile1 (\c -> c /= ',' && c /= ']' && c /= '=')
+  vchar '=' *> ((k,) <$> pAttributeValue)
+
+pPositional :: P Text
+pPositional = do
+  v <- pAttributeValue
+  mbc <- peekChar
+  case mbc of
+    Just ',' -> pure ()
+    _ -> guard $ not $ T.null v
+  pure v
+
+pAttributeValue :: P Text
+pAttributeValue = pQuotedAttr <|> pBareAttributeValue
+ where
+   pBareAttributeValue =
+     T.strip <$> takeWhile (\c -> c /= ',' && c /= ']')
+   pQuotedAttr = do
+     vchar '"'
+     result <- many (satisfy (/='"') <|> (vchar '\\' *> satisfy (/='"')))
+     vchar '"'
+     pure $ T.pack result
+
+pInlines' :: [Char] -> P [Inline]
+pInlines' cs =
+  (do il' <- pInline cs
+      let il = case il' of
+                 Inline (Attr ps kvs) (Span ils)
+                   | Nothing <- M.lookup "role" kvs
+                   -> Inline (Attr ps kvs) (Highlight ils)
+                 _ -> il'
+      addStr . (il:) <$> pInlines' [])
+  <|> (do c <- anyChar
+          pInlines' (c:cs))
+  <|> (addStr [] <$ endOfInput)
+ where
+  addStr = case cs of
+              [] -> id
+              _  -> (Inline mempty (Str (T.pack (replaceChars $ reverse cs))):)
+
+replaceChars :: [Char] -> [Char]
+replaceChars [] = []
+replaceChars ('(':'C':')':cs) = '\169':replaceChars cs
+replaceChars ('(':'R':')':cs) = '\174':replaceChars cs
+replaceChars ('(':'T':'M':')':cs) = '\8482':replaceChars cs
+replaceChars (x:'-':'-':y:cs)
+  | x == ' ', y == ' ' = '\8201':'\8212':'\8201':replaceChars cs
+  | isAlphaNum x, isAlphaNum y = x:'\8212':'\8203':replaceChars (y:cs)
+  | otherwise = x:'-':'-':replaceChars (y:cs)
+replaceChars ('.':'.':'.':cs) = '\8230':replaceChars cs
+replaceChars ('-':'>':cs) = '\8594':replaceChars cs
+replaceChars ('=':'>':cs) = '\8658':replaceChars cs
+replaceChars ('<':'-':cs) = '\8592':replaceChars cs
+replaceChars ('<':'=':cs) = '\8656':replaceChars cs
+replaceChars ('\'':cs) = '\8217':replaceChars cs
+replaceChars (c:cs) = c:replaceChars cs
+
+pShorthandAttributes :: P Attr
+pShorthandAttributes = do
+  attr <- mconcat <$>
+          many (skipWhile isSpace *>
+                (Attr [] . uncurry M.singleton <$> pShorthandAttribute))
+  skipWhile isSpace
+  pure attr
+
+pShorthandAttribute :: P (Text,Text)
+pShorthandAttribute = do
+  let isSpecial c = c == '.' || c == '#' || c == '%' || c == ']' || c ==','
+  c <- satisfy (\c -> c == '.' || c == '#' || c == '%')
+  val <- T.strip <$> takeWhile (not . isSpecial)
+  key <- case c of
+           '.' -> pure "role"
+           '#' -> pure "id"
+           '%' -> pure "options"
+           _ -> mzero
+  pure (key, val)
+
+pInline :: [Char] -> P Inline
+pInline prevChars = do
+  let maybeUnconstrained = case prevChars of
+                              (d:_) -> isSpace d || isPunctuation d
+                              [] -> True
+  let inMatched = pInMatched maybeUnconstrained
+  skipMany pLineComment
+  (do attr <- pFormattedTextAttributes <|> pure mempty
+      c <- peekChar'
+      case c of
+        '*' -> inMatched '*' attr (fmap Bold . parseInlines)
+        '_' -> inMatched '_' attr (fmap Italic . parseInlines)
+        '`' -> inMatched '`' attr (fmap Monospace . parseInlines)
+        '#' -> inMatched '#' attr (fmap Span . parseInlines)
+        '~' -> pInSingleMatched '~' attr (fmap Subscript . parseInlines)
+        '^' -> pInSingleMatched '^' attr (fmap Superscript . parseInlines)
+        '+' -> pTriplePassthrough <|> inMatched '+' attr (pure . Str)
+        '"' -> pQuoted '"' attr DoubleQuoted
+        '\'' -> pQuoted '\'' attr SingleQuoted
+        '(' -> pIndexEntry attr
+        _ -> mzero)
+     <|> (do c <- peekChar'
+             case c of
+               '\'' -> pApostrophe '\''
+               '+' -> pHardBreak
+               '{' -> pCounter <|> pAttributeReference
+               '\\' -> pEscape
+               '<' -> pBracedAutolink <|> pCrossReference
+               '&' -> pCharacterReference
+               '[' -> pBibAnchor <|> pInlineAnchor
+               _ | isLetter c -> pInlineMacro <|> pAutolink <|> pEmailAutolink
+                 | otherwise -> mzero)
+
+pIndexEntry :: Attr -> P Inline
+pIndexEntry attr = do
+  void $ string "(("
+  concealed <- option False $ True <$ vchar '('
+  terms <- takeWhile1 (/= ')')
+  Inline attr <$>
+    if concealed
+       then IndexEntry (TermConcealed (map T.strip (T.split (==',') terms)))
+                         <$ string ")))"
+       else IndexEntry (TermInText terms) <$ string "))"
+
+pTriplePassthrough :: P Inline
+pTriplePassthrough = Inline mempty . Passthrough . T.pack
+    <$> (string "+++" *> manyTill anyChar (string "+++"))
+
+pLineComment :: P ()
+pLineComment = string "//" *> skipWhile (== ' ') *> void pLine
+
+pCrossReference :: P Inline
+pCrossReference = do
+  void $ string "<<"
+  t <- T.pack <$> manyTill (satisfy (not . isEndOfLine)) (void (string ">>"))
+  let ts = T.split (==',') t
+  case ts of
+    [] -> mzero
+    [x] -> pure $ Inline mempty $ CrossReference x Nothing
+    (x:xs) -> Inline mempty . CrossReference x . Just
+                       <$> parseInlines (T.intercalate "," xs)
+
+data MatchState = Backslash | OneDelim | Regular
+  deriving Show
+
+-- used for super/subscript, which can't accept spaces but take single delims
+pInSingleMatched :: Char -> Attr -> (Text -> P InlineType) -> P Inline
+pInSingleMatched delim attr toInlineType = do
+  vchar delim
+  cs <- manyTill (satisfy (not . isSpace)) (vchar delim)
+  guard $ not $ null cs
+  Inline attr <$> toInlineType (T.pack cs)
+
+pInMatched :: Bool -> Char -> Attr -> (Text -> P InlineType) -> P Inline
+pInMatched maybeUnconstrained delim attr toInlineType = do
+  vchar delim
+  isDoubled <- option False (True <$ vchar delim)
+  followedBySpace <- maybe True isSpace <$> peekChar
+  guard $ isDoubled || (maybeUnconstrained && not followedBySpace)
+  cs <- manyTill ( (vchar '\\' *> char delim) <|> anyChar )
+                   (if isDoubled
+                       then vchar delim *> vchar delim
+                       else vchar delim)
+  guard $ not $ null cs
+  when (not isDoubled && maybeUnconstrained) $ do
+    mbc <- peekChar
+    case mbc of
+      Nothing -> pure ()
+      Just c -> guard $ isSpace c || isPunctuation c
+  Inline attr <$> toInlineType (T.pack cs)
+
+pInlineAnchor :: P Inline
+pInlineAnchor = do
+  void $ string "[["
+  contents <- T.pack <$> manyTill anyChar (string "]]")
+  let (anchorId, xrefLabel) =
+        case T.split (==',') contents of
+          [] -> (mempty, mempty)
+          (x:ys) -> (x, mconcat ys)
+  Inline mempty . InlineAnchor anchorId <$> parseInlines xrefLabel
+
+pBibAnchor :: P Inline
+pBibAnchor = do
+  void $ string "[[["
+  contents <- T.pack <$> manyTill anyChar (string "]]]")
+  let (anchorId, xrefLabel) =
+        case T.split (==',') contents of
+          [] -> (mempty, mempty)
+          (x:ys) -> (x, mconcat ys)
+  skipWhile (== ' ')
+  Inline mempty . BibliographyAnchor anchorId <$> parseInlines xrefLabel
+
+pCharacterReference :: P Inline
+pCharacterReference =
+  vchar '&' *> (pNumericCharacterReference <|> pCharacterEntityReference)
+
+pNumericCharacterReference :: P Inline
+pNumericCharacterReference =
+  vchar '#' *> (((vchar 'x' <|> vchar 'X') *> pHexReference) <|> pDecimalReference)
+ where
+  pHexReference =
+    Inline mempty . Str . T.singleton . chr <$> (liftP A.hexadecimal <* vchar ';')
+  pDecimalReference =
+    Inline mempty . Str . T.singleton . chr <$> (decimal <* vchar ';')
+
+pCharacterEntityReference :: P Inline
+pCharacterEntityReference = do
+  xs <- manyTill (satisfy isAlphaNum) (char ';' <|> space)
+  case lookupNamedEntity xs of
+    Just s -> pure $ Inline mempty (Str (T.pack s))
+    Nothing -> mzero
+
+pQuoted :: Char -> Attr -> ([Inline] -> InlineType) -> P Inline
+pQuoted c attr constructor = do
+  vchar c
+  result <- pInMatched True '`' attr (fmap constructor . parseInlines)
+  vchar c
+  return result
+
+pApostrophe :: Char -> P Inline
+pApostrophe '`' = Inline mempty (Str "’") <$ string "`'"
+pApostrophe _ = mzero
+
+pInlineMacro :: P Inline
+pInlineMacro = do
+  name <- choice (map (\n -> string n <* vchar ':') (M.keys inlineMacros))
+  let targetChars = mconcat <$> some
+       ( (string "pass:" *> vchar '[' *> takeWhile1 (/=']') <* vchar ']')
+         <|>
+         takeWhile1 (\c -> not (isSpace c) && c /= '[' && c /= '+')
+         <|>
+         (vchar '\\' *> (T.singleton <$> satisfy (\c -> c == '[' || c == '+')))
+         <|>
+        (do Inline _ (Str t) <- pInMatched False '+' mempty (pure . Str)
+            pure t)
+       )
+  target <- mconcat <$> many targetChars
+  handleInlineMacro name target
+
+handleInlineMacro :: Text -> Text -> P Inline
+handleInlineMacro name target =
+  case M.lookup name inlineMacros of
+    Nothing -> mzero
+    Just f -> f target
+
+inlineMacros :: M.Map Text (Text -> P Inline)
+inlineMacros = M.fromList
+  [ ("kbd", \_ -> do
+       attr <- pAttributes
+       let (description, attr') = extractDescription attr
+       pure $ Inline attr' $ Kbd (map T.strip (T.split (=='+') description)))
+  , ("menu", \target -> do
+       attr <- pAttributes
+       let (description, attr') = extractDescription attr
+       pure $ Inline attr' $ Menu (target : filter (not . T.null)
+                                    (map T.strip (T.split (=='>') description))))
+  , ("btn", \_ -> do
+       attr <- pAttributes
+       let (description, attr') = extractDescription attr
+       pure $ Inline attr' $ Button description)
+  , ("icon", \target -> do
+        attr <- pAttributes
+        pure $ Inline attr $ Icon target)
+  , ("anchor", \target -> do
+        attr <- pAttributes
+        let (anchorId, xrefLabel) =
+              case T.split (==',') target of
+                [] -> (mempty, mempty)
+                (x:ys) -> (x, mconcat ys)
+        Inline attr . InlineAnchor anchorId <$> parseInlines xrefLabel)
+  , ("pass", \_ -> do
+       attr <- pAttributes
+       let (description, attr') = extractDescription attr
+       pure $ Inline attr' $ Passthrough description)
+  , ("link", \target -> do
+      attr <- pAttributes
+      let (description, attr') = extractDescription attr
+      Inline attr' . Link URLLink (Target target)
+          <$> (if T.null description
+                  then pure [Inline mempty (Str target)]
+                  else parseInlines description))
+  , ("mailto", \target -> do
+      attr <- pAttributes
+      let (description, attr') = extractDescription attr
+      Inline attr' . Link EmailLink (Target target)
+             <$> if T.null description
+                    then pure [Inline mempty (Str target)]
+                    else parseInlines description)
+  , ("footnote", \target -> do
+      attr <- pAttributes
+      let (contents, attr') = extractDescription attr
+          fnid = if target == mempty
+                    then Nothing
+                    else Just (FootnoteId target)
+      Inline attr' . Footnote fnid <$> parseInlines contents)
+  , ("footnoteref", \_ -> do
+      (Attr ps kvs) <- pAttributes
+      (target, contents) <- case ps of
+                                 (t:c:_) -> pure (t,c)
+                                 [t] -> pure (t,mempty)
+                                 _ -> mzero
+      let fnid = if target == mempty
+                  then Nothing
+                  else Just (FootnoteId target)
+      Inline (Attr mempty kvs) . Footnote fnid <$> parseInlines contents)
+  , ("xref", \target -> do
+        ils <- pBracketedText >>= parseInlines
+        let mbtext = if null ils then Nothing else Just ils
+        pure $ Inline mempty $ CrossReference target mbtext)
+  , ("image", \target -> do
+        (Attr ps kvs) <- pAttributes
+        let (mbalt, mbw, mbh) =
+              case ps of
+                (x:y:z:_) -> (Just (AltText x), Width <$> readDecimal y,
+                              Height <$> readDecimal z)
+                [x,y] -> (Just (AltText x), Width <$> readDecimal y, Nothing)
+                [x] -> (Just (AltText x), Nothing, Nothing)
+                [] -> (Nothing, Nothing, Nothing)
+        pure $ Inline (Attr mempty kvs) $ InlineImage (Target target) mbalt mbw mbh)
+  , ("latexmath", \_ ->
+      Inline mempty . Math (Just LaTeXMath) <$> pBracketedText)
+  , ("asciimath", \_ ->
+      Inline mempty . Math (Just AsciiMath) <$> pBracketedText)
+  , ("stem", \_ ->
+      Inline mempty . Math Nothing <$> pBracketedText)
+  , ("indexterm", \_ ->
+      Inline mempty . IndexEntry . TermConcealed .
+        map T.strip . T.split (==',') <$> pBracketedText)
+  , ("indexterm2", \_ ->
+      Inline mempty . IndexEntry . TermInText <$> pBracketedText)
+  ]
+
+pBracketedText :: P Text
+pBracketedText =
+  vchar '[' *>
+    (mconcat <$> many
+         (T.pack <$> some ((vchar '\\' *> char ']') <|>
+                 satisfy (\c -> c /= ']' && not (isEndOfLine c)) <|>
+                 (' ' <$ (vchar '\\' <* endOfLine)))
+          <|> ((\x -> "[" <> x <> "]") <$> pBracketedText)))
+    <* vchar ']'
+
+extractDescription :: Attr -> (Text, Attr)
+extractDescription (Attr ps kvs) =
+  let description = case ps of
+                      (x:_) -> x
+                      _ -> ""
+  in (description, Attr (drop 1 ps) kvs)
+
+
+pEmailAutolink :: P Inline
+pEmailAutolink = do
+  a <- takeWhile1 (\c -> isAlphaNum c || c == '_' || c == '.' || c == '+')
+  vchar '@'
+  b <- takeWhile1 isLetter
+  vchar '.'
+  c <- takeWhile1 isLetter
+  guard $ let lc = T.length c in lc >= 2 && lc <= 5
+  let email = a <> "@" <> b <> "." <> c
+  attr <- pAttributes <|> pure mempty
+  let (description, attr') = extractDescription attr
+  Inline attr' . Link EmailLink (Target email)
+           <$> if T.null description
+                  then pure [Inline mempty (Str email)]
+                  else parseInlines description
+
+pAutolink :: P Inline
+pAutolink = do
+  scheme <- choice (map string
+               ["http:", "https:", "irc:", "ftp:", "mailto:"])
+  let isSpecialPunct ',' = True
+      isSpecialPunct '.' = True
+      isSpecialPunct '?' = True
+      isSpecialPunct '!' = True
+      isSpecialPunct ':' = True
+      isSpecialPunct ';' = True
+      isSpecialPunct ')' = True
+      isSpecialPunct _ = False
+  let urlChunk = T.pack <$>
+        some (satisfy (\c -> not (isSpace c) && c /= '[' && c /= '>'
+                               && not (isSpecialPunct c))
+             <|> (do c <- satisfy isSpecialPunct
+                     mbd <- peekChar
+                     case mbd of
+                       Nothing -> mzero
+                       Just d | isSpace d || isSpecialPunct d -> mzero
+                       _ -> pure c))
+  url <- (scheme <>) . mconcat <$> some
+          (urlChunk <|> (do Inline _ (Str t) <- pInMatched False '+' mempty (pure . Str)
+                            pure t))
+  attr <- pAttributes <|> pure mempty
+  let (description, attr') = extractDescription attr
+  Inline attr' . Link URLLink (Target url)
+             <$> if T.null description
+                    then pure [Inline mempty (Str url)]
+                    else parseInlines description
+
+pBracedAutolink :: P Inline
+pBracedAutolink = vchar '<' *> pAutolink <* vchar '>'
+
+pEscape :: P Inline
+pEscape =
+  -- we allow letters to be escaped to handle escapes of macros
+  -- though this also leads to differences from asciidoc
+  vchar '\\' *>
+   (Inline mempty . Str . T.singleton <$>
+      satisfy (\c -> isPunctuation c || isLetter c))
+
+pCounter :: P Inline
+pCounter = do
+  vchar '{' <* string "counter:"
+  name <- pDocAttributeName
+  mbvalue <- optional (vchar ':' *> pCounterValue)
+  vchar '}'
+  cmap <- gets counterMap
+  let (ctype, val) =
+        case M.lookup name cmap of
+          Just (ctype', val') -> (ctype', val' + 1)
+          Nothing ->
+            case mbvalue of
+              Nothing -> (DecimalCounter, 1)
+              Just (ctype', val') -> (ctype', val')
+  modify $ \st -> st{ counterMap =
+                       M.insert name (ctype, val) (counterMap st) }
+  pure $ Inline mempty $ Counter name ctype val
+
+pCounterValue :: P (CounterType, Int)
+pCounterValue = pUpperValue <|> pLowerValue <|> pDecimalValue
+ where
+   pUpperValue = do
+     c <- satisfy (\c -> isAscii c && isUpper c)
+     pure (UpperAlphaCounter, 1 + (ord c - ord 'A'))
+   pLowerValue = do
+     c <- satisfy (\c -> isAscii c && isLower c)
+     pure (UpperAlphaCounter, 1 + (ord c - ord 'a'))
+   pDecimalValue = do
+     n <- decimal
+     pure (DecimalCounter, n)
+
+pAttributeReference :: P Inline
+pAttributeReference = do
+  vchar '{'
+  name <- pDocAttributeName
+  vchar '}'
+  case M.lookup name replacements of
+    Just r -> pure $ Inline mempty (Str r)
+    Nothing -> pure $ Inline mempty $ AttributeReference (AttributeName name)
+
+replacements :: M.Map Text Text
+replacements = M.fromList
+  [ ("blank", "")
+  , ("empty", "")
+  , ("sp", " ")
+  , ("nbsp", "\160")
+  , ("zwsp", "\8203")
+  , ("wj", "\8288")
+  , ("apos", "\39")
+  , ("lsquo", "\8216")
+  , ("rsquo", "\8217")
+  , ("ldquo", "\8220")
+  , ("rdquo", "\8221")
+  , ("deg", "\176")
+  , ("plus", "+")
+  , ("brvbar", "\166")
+  , ("vbar", "|")
+  , ("amp", "&")
+  , ("lt", "<")
+  , ("gt", ">")
+  , ("startsb", "[")
+  , ("endsb", "]")
+  , ("caret", "^")
+  , ("asterisk", "*")
+  , ("tilde", "~")
+  , ("backslash", "\\")
+  , ("backtick", "`")
+  , ("two-colons", "::")
+  , ("two-semicolons", ";;")
+  , ("cpp", "C++")
+  , ("cxx", "C++")
+  , ("pp", "++")
+  ]
+
+pHardBreak :: P Inline
+pHardBreak = do
+  vchar '+'
+  _ <- takeWhile1 (\c -> c == '\r' || c == '\n')
+  pure $ Inline mempty HardBreak
+
+--- Utility functions:
+
+readDecimal :: Text -> Maybe Int
+readDecimal t =
+  case TR.decimal t of
+    Left _ -> Nothing
+    Right (x,_) -> Just x
+
+notFollowedBy :: P a -> P ()
+notFollowedBy p = optional p >>= guard . isNothing
+
+-- Generate auto-identifiers for sections.
+
+addIdentifiers :: Document -> Document
+addIdentifiers doc =
+  case M.lookup "sectids" docattr of
+    Just _ -> evalState (mapBlocks (addIdentifier prefix idsep) doc) mempty
+    Nothing -> doc
+ where
+  docattr = docAttributes (docMeta doc)
+  prefix = fromMaybe "_" $ M.lookup "idprefix" docattr
+  idsep = fromMaybe "_" $ M.lookup "idseparator" docattr
+
+addIdentifier :: Text -> Text -> Block -> State (M.Map Text Int) Block
+addIdentifier prefix idsep (Block (Attr ps kvs) mbtitle (Section lev ils bs))
+  | Nothing <- M.lookup "id" kvs
+  = do
+      usedIds <- get
+      let (ident, usedIds') = generateIdentifier prefix idsep usedIds ils
+      put usedIds'
+      pure $ Block (Attr ps (M.insert "id" ident kvs)) mbtitle
+                     (Section lev ils bs)
+addIdentifier _ _ x = pure x
+
+generateIdentifier :: Text -> Text -> M.Map Text Int -> [Inline]
+                   -> (Text, M.Map Text Int)
+generateIdentifier prefix idsep usedIds ils =
+  case M.lookup s usedIds of
+    Nothing -> (s, M.insert s 1 usedIds)
+    Just n -> (s <> idsep <> T.pack (show (n + 1)), M.insert s (n + 1) usedIds)
+ where
+  s = prefix <> makeSeps (T.toLower (toString ils))
+  makeSeps = T.intercalate idsep . T.words .
+               T.map (\case
+                       '.' -> ' '
+                       '-' -> ' '
+                       c | isSpace c -> ' '
+                         | otherwise -> c)
+  toString = foldInlines getStr
+  getStr (Inline _ (Str t)) = t
+  getStr _ = ""
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,196 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+import Test.Tasty
+import Test.Tasty.Golden
+import Test.Tasty.HUnit
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.IO as TIO
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Lazy.Encoding as TEL
+import qualified Data.ByteString.Lazy as BL
+import Data.List (sort)
+import System.Directory (listDirectory)
+import System.FilePath ((</>), takeBaseName, takeExtension)
+import qualified Data.ByteString.Char8 as B
+import AsciiDoc
+import Text.Show.Pretty (ppShow)
+
+main :: IO ()
+main = do
+  asciidoctorTests <- goldenTests "asciidoctor"
+  featureTests <- goldenTests "feature"
+  defaultMain $ testGroup "Tests"
+    [ testGroup "Asciidoctor" asciidoctorTests
+    , testGroup "Feature" featureTests
+    , testGroup "Generic"
+       [ foldInlineTest
+       , foldBlockTest
+       , mapInlineTest
+       , mapBlockTest
+       ]
+    ]
+
+goldenTests :: FilePath -> IO [TestTree]
+goldenTests fp = do
+  files <- findTestFiles ("test" </> fp)
+  pure $ map (\(category, fs) ->
+          testGroup category (map toGoldenTest fs)) files
+
+-- Find all .test files in a directory
+findTestFiles :: FilePath -> IO [(FilePath, [FilePath])]
+findTestFiles baseDir = do
+  categories <- listDirectory baseDir
+  let go f = do
+        xs <- map ((baseDir </> f) </>) <$> listDirectory (baseDir </> f)
+        return (f, sort $ filter ((== ".test") . takeExtension) xs)
+  mapM go categories
+
+toGoldenTest :: FilePath -> TestTree
+toGoldenTest fp =
+  goldenVsStringDiff (takeBaseName fp) diff fp getTested
+ where
+  diff ref new = ["diff", "-u", ref, new]
+
+  constructGoldenTest :: (T.Text, T.Text) -> IO BL.ByteString
+  constructGoldenTest (inText, outText) =
+    return $ TEL.encodeUtf8 $ TL.fromStrict $
+      inText <> ">>>" <>
+      "\n" <> ensureFinalNewline outText
+
+  getTested = do
+    (inText,_) <- readGoldenTest fp
+    result <- convert inText
+    constructGoldenTest (inText, result)
+
+  raiseError path pos msg =
+    error $ "Parse error at " <> show path <>
+             " position " <> show pos <> ": " <> msg
+
+  convert inText =
+    T.pack . ppShow <$> parseDocument TIO.readFile raiseError fp inText
+
+  readGoldenTest :: FilePath -> IO (T.Text, T.Text)
+  readGoldenTest fp' = do
+    lns <- B.lines <$> B.readFile fp'
+    case break (B.isPrefixOf ">>>") lns of
+      (inlines,outlines) -> return (TE.decodeUtf8 (B.unlines inlines),
+                                    TE.decodeUtf8 (B.unlines $ drop 1outlines))
+
+
+ensureFinalNewline :: T.Text -> T.Text
+ensureFinalNewline xs = case T.unsnoc xs of
+  Nothing        -> xs
+  Just (_, '\n') -> xs
+  _              -> xs <> "\n"
+
+
+testDoc :: Document
+testDoc = Document
+  { docMeta = Meta [] Nothing [] Nothing mempty
+  , docBlocks =
+      [ Block mempty Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem Nothing
+                 [ Block mempty Nothing
+                     (Paragraph
+                        [ Inline mempty (Str "nested ")
+                        , Inline mempty
+                            (Bold
+                               [ Inline mempty (Str "inline ")
+                               , Inline mempty (Italic [ Inline mempty (Str "and block") ])
+                               ])
+                        ])
+                 , Block mempty Nothing
+                     (List
+                        (OrderedList (Level 2) Nothing)
+                        [ ListItem Nothing
+                            [ Block mempty (Just (BlockTitle [Inline mempty (Str "The title")]))
+                                (Paragraph
+                                   [ Inline mempty (Bold [ Inline mempty (Str "contents") ]) ])
+                            ]
+                        ])
+                 ]
+             ])
+      ]
+  }
+
+foldInlineTest :: TestTree
+foldInlineTest = testCase "foldInline" $ do
+  foldInlines (\case
+                  (Inline _ (Str s)) -> s
+                  _ -> "") testDoc
+    @?= "nested inline and blockThe titlecontents"
+
+foldBlockTest :: TestTree
+foldBlockTest = testCase "foldBlock" $ do
+  foldBlocks (\case
+                  Block _ (Just t) _ -> [t]
+                  _ -> []) testDoc
+    @?= [BlockTitle [Inline mempty (Str "The title")]]
+
+mapInlineTest :: TestTree
+mapInlineTest = testCase "mapInlines" $ do
+  d <- mapInlines
+              (\case
+                 Inline _ (Str _) -> pure $ Inline mempty (Str "X")
+                 x -> pure x) testDoc
+  d @?= Document
+      { docMeta = Meta [] Nothing [] Nothing mempty
+      , docBlocks =
+          [ Block mempty Nothing
+              (List
+                 (OrderedList (Level 1) Nothing)
+                 [ ListItem Nothing
+                     [ Block mempty Nothing
+                         (Paragraph
+                            [ Inline mempty (Str "X")
+                            , Inline mempty
+                                (Bold
+                                   [ Inline mempty (Str "X")
+                                   , Inline mempty (Italic [ Inline mempty (Str "X") ])
+                                   ])
+                            ])
+                     , Block mempty Nothing
+                         (List
+                            (OrderedList (Level 2) Nothing)
+                            [ ListItem Nothing
+                                [ Block mempty (Just (BlockTitle [Inline mempty (Str "X")]))
+                                    (Paragraph
+                                       [ Inline mempty (Bold [ Inline mempty (Str "X") ]) ])
+                                ]
+                            ])
+                     ]
+                 ])
+          ]
+      }
+
+
+mapBlockTest :: TestTree
+mapBlockTest = testCase "mapBlocks" $ do
+  d <- mapBlocks
+            (\case
+               Block attr mbtitle (Paragraph _) ->
+                 pure $ Block attr mbtitle ThematicBreak
+               x -> pure x) testDoc
+  d @?= Document
+      { docMeta = Meta [] Nothing [] Nothing mempty
+      , docBlocks =
+          [ Block mempty Nothing
+              (List
+                 (OrderedList (Level 1) Nothing)
+                 [ ListItem Nothing
+                     [ Block mempty Nothing ThematicBreak
+                     , Block mempty Nothing
+                         (List
+                            (OrderedList (Level 2) Nothing)
+                            [ ListItem Nothing
+                                [ Block mempty (Just (BlockTitle [Inline mempty (Str "The title")]))
+                                    ThematicBreak
+                                ]
+                            ])
+                     ]
+                 ])
+          ]
+      }
diff --git a/test/asciidoctor/admonition/caution-block.test b/test/asciidoctor/admonition/caution-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/caution-block.test
@@ -0,0 +1,43 @@
+[CAUTION]
+====
+This is a caution with complex content.
+
+* It contains a list.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Caution
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a caution with complex content.") ])
+             , Block
+                 mempty
+                 Nothing
+                 (List
+                    (BulletList (Level 1))
+                    [ ListItem
+                        Nothing
+                        [ Block
+                            mempty
+                            Nothing
+                            (Paragraph [ Inline mempty (Str "It contains a list.") ])
+                        ]
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/caution-with-id-and-role.test b/test/asciidoctor/admonition/caution-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/caution-with-id-and-role.test
@@ -0,0 +1,27 @@
+[#caution-1.red]
+CAUTION: This is a caution with id and role.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "caution-1" ) , ( "role" , "red" ) ] )
+          Nothing
+          (Admonition
+             Caution
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a caution with id and role.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/caution-with-title.test b/test/asciidoctor/admonition/caution-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/caution-with-title.test
@@ -0,0 +1,25 @@
+.Title of caution
+CAUTION: This is a caution with title.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Title of caution") ]))
+          (Admonition
+             Caution
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a caution with title.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/caution.test b/test/asciidoctor/admonition/caution.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/caution.test
@@ -0,0 +1,24 @@
+CAUTION: This is a caution.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Caution
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a caution.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/important-block.test b/test/asciidoctor/admonition/important-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/important-block.test
@@ -0,0 +1,45 @@
+[IMPORTANT]
+====
+This is an important notice with complex content.
+
+* It contains a list.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Important
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty (Str "This is an important notice with complex content.")
+                    ])
+             , Block
+                 mempty
+                 Nothing
+                 (List
+                    (BulletList (Level 1))
+                    [ ListItem
+                        Nothing
+                        [ Block
+                            mempty
+                            Nothing
+                            (Paragraph [ Inline mempty (Str "It contains a list.") ])
+                        ]
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/important-with-id-and-role.test b/test/asciidoctor/admonition/important-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/important-with-id-and-role.test
@@ -0,0 +1,29 @@
+[#important-1.red]
+IMPORTANT: This is an important notice with id and role.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "important-1" ) , ( "role" , "red" ) ] )
+          Nothing
+          (Admonition
+             Important
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty (Str "This is an important notice with id and role.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/important-with-title.test b/test/asciidoctor/admonition/important-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/important-with-title.test
@@ -0,0 +1,27 @@
+.Title of important notice
+IMPORTANT: This is an important notice with title.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just
+             (BlockTitle [ Inline mempty (Str "Title of important notice") ]))
+          (Admonition
+             Important
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is an important notice with title.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/important.test b/test/asciidoctor/admonition/important.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/important.test
@@ -0,0 +1,24 @@
+IMPORTANT: This is an important notice.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Important
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is an important notice.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/note-block.test b/test/asciidoctor/admonition/note-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/note-block.test
@@ -0,0 +1,43 @@
+[NOTE]
+====
+This is a note with complex content.
+
+* It contains a list.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Note
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a note with complex content.") ])
+             , Block
+                 mempty
+                 Nothing
+                 (List
+                    (BulletList (Level 1))
+                    [ ListItem
+                        Nothing
+                        [ Block
+                            mempty
+                            Nothing
+                            (Paragraph [ Inline mempty (Str "It contains a list.") ])
+                        ]
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/note-with-id-and-role.test b/test/asciidoctor/admonition/note-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/note-with-id-and-role.test
@@ -0,0 +1,27 @@
+[#note-1.yellow]
+NOTE: This is a note with id and role.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "note-1" ) , ( "role" , "yellow" ) ] )
+          Nothing
+          (Admonition
+             Note
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a note with id and role.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/note-with-title.test b/test/asciidoctor/admonition/note-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/note-with-title.test
@@ -0,0 +1,25 @@
+.Title of note
+NOTE: This is a note with title.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Title of note") ]))
+          (Admonition
+             Note
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a note with title.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/note.test b/test/asciidoctor/admonition/note.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/note.test
@@ -0,0 +1,24 @@
+NOTE: This is a note.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Note
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a note.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/tip-block.test b/test/asciidoctor/admonition/tip-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/tip-block.test
@@ -0,0 +1,43 @@
+[TIP]
+====
+This is a tip with complex content.
+
+* It contains a list.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Tip
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a tip with complex content.") ])
+             , Block
+                 mempty
+                 Nothing
+                 (List
+                    (BulletList (Level 1))
+                    [ ListItem
+                        Nothing
+                        [ Block
+                            mempty
+                            Nothing
+                            (Paragraph [ Inline mempty (Str "It contains a list.") ])
+                        ]
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/tip-with-id-and-role.test b/test/asciidoctor/admonition/tip-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/tip-with-id-and-role.test
@@ -0,0 +1,27 @@
+[#tip-1.blue]
+TIP: This is a tip with id and role.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "tip-1" ) , ( "role" , "blue" ) ] )
+          Nothing
+          (Admonition
+             Tip
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a tip with id and role.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/tip-with-title.test b/test/asciidoctor/admonition/tip-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/tip-with-title.test
@@ -0,0 +1,25 @@
+.Title of tip
+TIP: This is a tip with title.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Title of tip") ]))
+          (Admonition
+             Tip
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a tip with title.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/tip.test b/test/asciidoctor/admonition/tip.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/tip.test
@@ -0,0 +1,22 @@
+TIP: This is a tip.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Tip
+             [ Block
+                 mempty Nothing (Paragraph [ Inline mempty (Str "This is a tip.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/warning-block.test b/test/asciidoctor/admonition/warning-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/warning-block.test
@@ -0,0 +1,43 @@
+[WARNING]
+====
+This is a warning with complex content.
+
+* It contains a list.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Warning
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a warning with complex content.") ])
+             , Block
+                 mempty
+                 Nothing
+                 (List
+                    (BulletList (Level 1))
+                    [ ListItem
+                        Nothing
+                        [ Block
+                            mempty
+                            Nothing
+                            (Paragraph [ Inline mempty (Str "It contains a list.") ])
+                        ]
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/warning-with-id-and-role.test b/test/asciidoctor/admonition/warning-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/warning-with-id-and-role.test
@@ -0,0 +1,27 @@
+[#warning-1.red]
+WARNING: This is a warning with id and role.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "warning-1" ) , ( "role" , "red" ) ] )
+          Nothing
+          (Admonition
+             Warning
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is a warning with id and role.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/warning-with-title.test b/test/asciidoctor/admonition/warning-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/warning-with-title.test
@@ -0,0 +1,25 @@
+.Title of warning
+WARNING: This is a warning with title.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Title of warning") ]))
+          (Admonition
+             Warning
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a warning with title.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/admonition/warning.test b/test/asciidoctor/admonition/warning.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/admonition/warning.test
@@ -0,0 +1,24 @@
+WARNING: This is a warning.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Admonition
+             Warning
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "This is a warning.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/audio/basic.test b/test/asciidoctor/audio/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/audio/basic.test
@@ -0,0 +1,14 @@
+audio::ocean_waves.mp3[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (BlockAudio (Target "ocean_waves.mp3")) ]
+  }
diff --git a/test/asciidoctor/audio/with-all-options.test b/test/asciidoctor/audio/with-all-options.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/audio/with-all-options.test
@@ -0,0 +1,19 @@
+audio::ocean_waves.mp3[options="autoplay, nocontrols, loop"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "autoplay, nocontrols, loop" ) ] )
+          Nothing
+          (BlockAudio (Target "ocean_waves.mp3"))
+      ]
+  }
diff --git a/test/asciidoctor/audio/with-id-and-role.test b/test/asciidoctor/audio/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/audio/with-id-and-role.test
@@ -0,0 +1,20 @@
+[#ocean.wave]
+audio::ocean_waves.mp3[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "ocean" ) , ( "role" , "wave" ) ] )
+          Nothing
+          (BlockAudio (Target "ocean_waves.mp3"))
+      ]
+  }
diff --git a/test/asciidoctor/audio/with-title.test b/test/asciidoctor/audio/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/audio/with-title.test
@@ -0,0 +1,19 @@
+.Waves!
+audio::ocean_waves.mp3[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Waves!") ]))
+          (BlockAudio (Target "ocean_waves.mp3"))
+      ]
+  }
diff --git a/test/asciidoctor/colist/basic.test b/test/asciidoctor/colist/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/colist/basic.test
@@ -0,0 +1,60 @@
+// This example should assert only callouts list below the code listing.
+// For callouts inside the listing is responsible inline_callout.
+[source, ruby]
+----
+require 'sinatra' // <1>
+
+get '/hi' do  # <2>
+  "Hello World!" ;; <3>
+end
+----
+<1> Library import
+<2> URL mapping
+<3> Content for response
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby"))
+             [ SourceLine "require 'sinatra' //" [ Callout 1 ]
+             , SourceLine "" []
+             , SourceLine "get '/hi' do  #" [ Callout 2 ]
+             , SourceLine "  \"Hello World!\" ;;" [ Callout 3 ]
+             , SourceLine "end" []
+             ])
+      , Block
+          mempty
+          Nothing
+          (List
+             CalloutList
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Library import") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "URL mapping") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Content for response") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/colist/with-id-and-role.test b/test/asciidoctor/colist/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/colist/with-id-and-role.test
@@ -0,0 +1,62 @@
+// This example should assert only callouts list below the code listing.
+// For callouts inside the listing is responsible inline_callout.
+[source, ruby]
+----
+require 'sinatra' // <1>
+
+get '/hi' do  # <2>
+  "Hello World!" ;; <3>
+end
+----
+[#call.sinatra]
+<1> Library import
+<2> URL mapping
+<3> Content for response
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby"))
+             [ SourceLine "require 'sinatra' //" [ Callout 1 ]
+             , SourceLine "" []
+             , SourceLine "get '/hi' do  #" [ Callout 2 ]
+             , SourceLine "  \"Hello World!\" ;;" [ Callout 3 ]
+             , SourceLine "end" []
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "call" ) , ( "role" , "sinatra" ) ] )
+          Nothing
+          (List
+             CalloutList
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Library import") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "URL mapping") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Content for response") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/colist/with-title.test b/test/asciidoctor/colist/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/colist/with-title.test
@@ -0,0 +1,61 @@
+// This example should assert only callouts list below the code listing.
+// For callouts inside the listing is responsible inline_callout.
+[source, ruby]
+----
+require 'sinatra' // <1>
+
+get '/hi' do  # <2>
+  "Hello World!" ;; <3>
+end
+----
+.Description
+<1> Library import
+<2> URL mapping
+<3> Content for response
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby"))
+             [ SourceLine "require 'sinatra' //" [ Callout 1 ]
+             , SourceLine "" []
+             , SourceLine "get '/hi' do  #" [ Callout 2 ]
+             , SourceLine "  \"Hello World!\" ;;" [ Callout 3 ]
+             , SourceLine "end" []
+             ])
+      , Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Description") ]))
+          (List
+             CalloutList
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Library import") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "URL mapping") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Content for response") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/basic-block.test b/test/asciidoctor/dlist/basic-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/basic-block.test
@@ -0,0 +1,74 @@
+About::
+* An implementation of the AsciiDoc processor in Ruby.
+* Fast text processor and publishing toolchain.
+
+Authors::
+Asciidoctor is lead by Dan Allen and Sarah White and has received contributions
+from many other individuals in Asciidoctor’s awesome community.
++
+AsciiDoc was started by Stuart Rackham.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "About") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (List
+                        (BulletList (Level 1))
+                        [ ListItem
+                            Nothing
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Str "An implementation of the AsciiDoc processor in Ruby.")
+                                   ])
+                            ]
+                        , ListItem
+                            Nothing
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty (Str "Fast text processor and publishing toolchain.")
+                                   ])
+                            ]
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "Authors") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Asciidoctor is lead by Dan Allen and Sarah White and has received contributions\nfrom many other individuals in Asciidoctor\8217s awesome community.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline mempty (Str "AsciiDoc was started by Stuart Rackham.") ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/basic-missing-description.test b/test/asciidoctor/dlist/basic-missing-description.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/basic-missing-description.test
@@ -0,0 +1,22 @@
+Definition without a description::
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Definition without a description") ]
+               , []
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/basic-with-id-and-role.test b/test/asciidoctor/dlist/basic-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/basic-with-id-and-role.test
@@ -0,0 +1,25 @@
+[#licenses.open]
+License:: MIT
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "licenses" ) , ( "role" , "open" ) ] )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "License") ]
+               , [ Block mempty Nothing (Paragraph [ Inline mempty (Str "MIT") ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/basic-with-title.test b/test/asciidoctor/dlist/basic-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/basic-with-title.test
@@ -0,0 +1,24 @@
+.Asciidoctor
+License:: MIT
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Asciidoctor") ]))
+          (DefinitionList
+             [ ( [ Inline mempty (Str "License") ]
+               , [ Block mempty Nothing (Paragraph [ Inline mempty (Str "MIT") ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/basic.test b/test/asciidoctor/dlist/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/basic.test
@@ -0,0 +1,44 @@
+Asciidoctor:: An implementation of the AsciiDoc processor in Ruby.
+Asciidoc::
+  A text document format for writing notes, documentation, articles, books,
+  ebooks, slideshows, web pages, man pages and blogs.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Asciidoctor") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty (Str "An implementation of the AsciiDoc processor in Ruby.")
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "Asciidoc") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "A text document format for writing notes, documentation, articles, books,\n  ebooks, slideshows, web pages, man pages and blogs.")
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/horizontal-with-dimensions.test b/test/asciidoctor/dlist/horizontal-with-dimensions.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/horizontal-with-dimensions.test
@@ -0,0 +1,47 @@
+[horizontal, labelwidth="20", itemwidth="50%"]
+Hard drive:: Permanent storage for operating system and/or user files.
+RAM:: Temporarily stores information the CPU uses during operation.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "horizontal" ]
+          , fromList [ ( "itemwidth" , "50%" ) , ( "labelwidth" , "20" ) ]
+          )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Hard drive") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "Permanent storage for operating system and/or user files.")
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "RAM") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Temporarily stores information the CPU uses during operation.")
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/horizontal-with-id-and-role.test b/test/asciidoctor/dlist/horizontal-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/horizontal-with-id-and-role.test
@@ -0,0 +1,47 @@
+[horizontal, id=computer, role=terms]
+Hard drive:: Permanent storage for operating system and/or user files.
+RAM:: Temporarily stores information the CPU uses during operation.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "horizontal" ]
+          , fromList [ ( "id" , "computer" ) , ( "role" , "terms" ) ]
+          )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Hard drive") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "Permanent storage for operating system and/or user files.")
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "RAM") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Temporarily stores information the CPU uses during operation.")
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/horizontal-with-title.test b/test/asciidoctor/dlist/horizontal-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/horizontal-with-title.test
@@ -0,0 +1,48 @@
+[horizontal]
+.Computer terminology for noobs
+Hard drive:: Permanent storage for operating system and/or user files.
+RAM:: Temporarily stores information the CPU uses during operation.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "horizontal" ] , fromList [] )
+          (Just
+             (BlockTitle
+                [ Inline mempty (Str "Computer terminology for noobs") ]))
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Hard drive") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "Permanent storage for operating system and/or user files.")
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "RAM") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Temporarily stores information the CPU uses during operation.")
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/horizontal.test b/test/asciidoctor/dlist/horizontal.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/horizontal.test
@@ -0,0 +1,45 @@
+[horizontal]
+Hard drive:: Permanent storage for operating system and/or user files.
+RAM:: Temporarily stores information the CPU uses during operation.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "horizontal" ] , fromList [] )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Hard drive") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "Permanent storage for operating system and/or user files.")
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "RAM") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Temporarily stores information the CPU uses during operation.")
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/mixed.test b/test/asciidoctor/dlist/mixed.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/mixed.test
@@ -0,0 +1,177 @@
+Operating Systems::
+  Linux:::
+    . Fedora
+      * Desktop
+    . Ubuntu
+      * Desktop
+      * Server
+  BSD:::
+    . FreeBSD
+    . NetBSD
+
+Cloud Providers::
+  PaaS:::
+    . OpenShift
+    . CloudBees
+  IaaS:::
+    . Amazon EC2
+    . Rackspace
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Operating Systems") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (DefinitionList
+                        [ ( [ Inline mempty (Str "Linux") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (List
+                                   (OrderedList (Level 1) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "Fedora") ])
+                                       , Block
+                                           mempty
+                                           Nothing
+                                           (List
+                                              (BulletList (Level 1))
+                                              [ ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "Desktop") ])
+                                                  ]
+                                              ])
+                                       ]
+                                   , ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "Ubuntu") ])
+                                       , Block
+                                           mempty
+                                           Nothing
+                                           (List
+                                              (BulletList (Level 1))
+                                              [ ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "Desktop") ])
+                                                  ]
+                                              , ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "Server") ])
+                                                  ]
+                                              ])
+                                       ]
+                                   ])
+                            ]
+                          )
+                        , ( [ Inline mempty (Str "BSD") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (List
+                                   (OrderedList (Level 1) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "FreeBSD") ])
+                                       ]
+                                   , ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "NetBSD") ])
+                                       ]
+                                   ])
+                            ]
+                          )
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "Cloud Providers") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (DefinitionList
+                        [ ( [ Inline mempty (Str "PaaS") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (List
+                                   (OrderedList (Level 1) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "OpenShift") ])
+                                       ]
+                                   , ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "CloudBees") ])
+                                       ]
+                                   ])
+                            ]
+                          )
+                        , ( [ Inline mempty (Str "IaaS") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (List
+                                   (OrderedList (Level 1) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "Amazon EC2") ])
+                                       ]
+                                   , ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "Rackspace") ])
+                                       ]
+                                   ])
+                            ]
+                          )
+                        ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/qanda-block.test b/test/asciidoctor/dlist/qanda-block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/qanda-block.test
@@ -0,0 +1,76 @@
+[qanda]
+What is Asciidoctor?::
+* An implementation of the AsciiDoc processor in Ruby.
+* Fast text processor and publishing toolchain.
+
+Who is behind Asciidoctor?::
+Asciidoctor is lead by Dan Allen and Sarah White and has received contributions
+from many other individuals in Asciidoctor’s awesome community.
++
+AsciiDoc was started by Stuart Rackham.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "qanda" ] , fromList [] )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "What is Asciidoctor?") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (List
+                        (BulletList (Level 1))
+                        [ ListItem
+                            Nothing
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Str "An implementation of the AsciiDoc processor in Ruby.")
+                                   ])
+                            ]
+                        , ListItem
+                            Nothing
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty (Str "Fast text processor and publishing toolchain.")
+                                   ])
+                            ]
+                        ])
+                 ]
+               )
+             , ( [ Inline mempty (Str "Who is behind Asciidoctor?") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Asciidoctor is lead by Dan Allen and Sarah White and has received contributions\nfrom many other individuals in Asciidoctor\8217s awesome community.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline mempty (Str "AsciiDoc was started by Stuart Rackham.") ])
+                 ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/qanda-missing-answer.test b/test/asciidoctor/dlist/qanda-missing-answer.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/qanda-missing-answer.test
@@ -0,0 +1,21 @@
+[qanda]
+Who knows the answer?::
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "qanda" ] , fromList [] )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "Who knows the answer?") ] , [] ) ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/qanda-with-id-and-role.test b/test/asciidoctor/dlist/qanda-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/qanda-with-id-and-role.test
@@ -0,0 +1,28 @@
+[qanda, id=faq, role=galaxy]
+What is the answer to the Ultimate Question?:: 42
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "qanda" ]
+          , fromList [ ( "id" , "faq" ) , ( "role" , "galaxy" ) ]
+          )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline
+                     mempty (Str "What is the answer to the Ultimate Question?")
+                 ]
+               , [ Block mempty Nothing (Paragraph [ Inline mempty (Str "42") ]) ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/qanda-with-title.test b/test/asciidoctor/dlist/qanda-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/qanda-with-title.test
@@ -0,0 +1,29 @@
+[qanda]
+.The most important questions
+What is the answer to the Ultimate Question?:: 42
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "qanda" ] , fromList [] )
+          (Just
+             (BlockTitle
+                [ Inline mempty (Str "The most important questions") ]))
+          (DefinitionList
+             [ ( [ Inline
+                     mempty (Str "What is the answer to the Ultimate Question?")
+                 ]
+               , [ Block mempty Nothing (Paragraph [ Inline mempty (Str "42") ]) ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/dlist/qanda.test b/test/asciidoctor/dlist/qanda.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/dlist/qanda.test
@@ -0,0 +1,38 @@
+[qanda]
+What is Asciidoctor?::
+  An implementation of the AsciiDoc processor in Ruby.
+What is the answer to the Ultimate Question?:: 42
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "qanda" ] , fromList [] )
+          Nothing
+          (DefinitionList
+             [ ( [ Inline mempty (Str "What is Asciidoctor?") ]
+               , [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty (Str "An implementation of the AsciiDoc processor in Ruby.")
+                        ])
+                 ]
+               )
+             , ( [ Inline
+                     mempty (Str "What is the answer to the Ultimate Question?")
+                 ]
+               , [ Block mempty Nothing (Paragraph [ Inline mempty (Str "42") ]) ]
+               )
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/document/footnotes.test b/test/asciidoctor/document/footnotes.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/footnotes.test
@@ -0,0 +1,48 @@
+The hail-and-rainbow protocol can be initiated at five levels: double, tertiary, supernumerary,
+supermassive, and apocalyptic party.footnote:[The double hail-and-rainbow level makes my toes tingle.]
+A bold statement.footnoteref:[disclaimer,Opinions are my own.]
+
+Another outrageous statement.footnoteref:[disclaimer]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "The hail-and-rainbow protocol can be initiated at five levels: double, tertiary, supernumerary,\nsupermassive, and apocalyptic party.")
+             , Inline
+                 mempty
+                 (Footnote
+                    Nothing
+                    [ Inline
+                        mempty
+                        (Str "The double hail-and-rainbow level makes my toes tingle.")
+                    ])
+             , Inline mempty (Str "\nA bold statement.")
+             , Inline
+                 mempty
+                 (Footnote
+                    (Just (FootnoteId "disclaimer"))
+                    [ Inline mempty (Str "Opinions are my own.") ])
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Another outrageous statement.")
+             , Inline mempty (Footnote (Just (FootnoteId "disclaimer")) [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/document/title-with-author-no-email.test b/test/asciidoctor/document/title-with-author-no-email.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-author-no-email.test
@@ -0,0 +1,20 @@
+= The Dangerous and Thrilling Documentation Chronicles
+Kismet Rainbow Chameleon
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Rainbow Chameleon" , authorEmail = Nothing }
+            ]
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title-with-author.test b/test/asciidoctor/document/title-with-author.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-author.test
@@ -0,0 +1,22 @@
+= The Dangerous and Thrilling Documentation Chronicles
+Kismet Rainbow Chameleon <kismet@asciidoctor.org>
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Rainbow Chameleon"
+                , authorEmail = Just "kismet@asciidoctor.org"
+                }
+            ]
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title-with-multiple-authors.test b/test/asciidoctor/document/title-with-multiple-authors.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-multiple-authors.test
@@ -0,0 +1,26 @@
+= The Dangerous and Thrilling Documentation Chronicles
+Kismet Rainbow Chameleon <kismet@asciidoctor.org>; Lazarus het_Draeke <lazarus@asciidoctor.org>
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Rainbow Chameleon"
+                , authorEmail = Just "kismet@asciidoctor.org"
+                }
+            , Author
+                { authorName = "Lazarus het_Draeke"
+                , authorEmail = Just "lazarus@asciidoctor.org"
+                }
+            ]
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title-with-revdate.test b/test/asciidoctor/document/title-with-revdate.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-revdate.test
@@ -0,0 +1,24 @@
+= Document Title
+Kismet Chameleon
+v1.0, 2013-10-02
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Chameleon" , authorEmail = Nothing }
+            ]
+        , docRevision =
+            Just
+              Revision
+                { revVersion = "1.0"
+                , revDate = Just "2013-10-02"
+                , revRemark = Nothing
+                }
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title-with-revnumber.test b/test/asciidoctor/document/title-with-revnumber.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-revnumber.test
@@ -0,0 +1,21 @@
+= Document Title
+Kismet Chameleon
+v1.0
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Chameleon" , authorEmail = Nothing }
+            ]
+        , docRevision =
+            Just
+              Revision
+                { revVersion = "1.0" , revDate = Nothing , revRemark = Nothing }
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title-with-revremark.test b/test/asciidoctor/document/title-with-revremark.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title-with-revremark.test
@@ -0,0 +1,24 @@
+= Document Title
+Kismet Chameleon
+v1.0, October 2, 2013: First incarnation
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author
+                { authorName = "Kismet Chameleon" , authorEmail = Nothing }
+            ]
+        , docRevision =
+            Just
+              Revision
+                { revVersion = "1.0"
+                , revDate = Just "October 2, 2013"
+                , revRemark = Just "First incarnation"
+                }
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/title.test b/test/asciidoctor/document/title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/title.test
@@ -0,0 +1,17 @@
+= The Dangerous and Thrilling Documentation Chronicles
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author { authorName = "" , authorEmail = Nothing } ]
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/document/toc-title.test b/test/asciidoctor/document/toc-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/toc-title.test
@@ -0,0 +1,28 @@
+= Document Title
+:toc:
+:toc-title: Table of Adventures
+
+== Cavern Glow
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "sectids" , "" )
+              , ( "toc" , "" )
+              , ( "toc-title" , "Table of Adventures" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Cavern Glow") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/document/toc.test b/test/asciidoctor/document/toc.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/document/toc.test
@@ -0,0 +1,22 @@
+= Document Title
+:toc:
+
+== Cavern Glow
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) , ( "toc" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Cavern Glow") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/embedded/footnotes.test b/test/asciidoctor/embedded/footnotes.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/embedded/footnotes.test
@@ -0,0 +1,48 @@
+The hail-and-rainbow protocol can be initiated at five levels: double, tertiary, supernumerary,
+supermassive, and apocalyptic party.footnote:[The double hail-and-rainbow level makes my toes tingle.]
+A bold statement.footnoteref:[disclaimer,Opinions are my own.]
+
+Another outrageous statement.footnoteref:[disclaimer]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "The hail-and-rainbow protocol can be initiated at five levels: double, tertiary, supernumerary,\nsupermassive, and apocalyptic party.")
+             , Inline
+                 mempty
+                 (Footnote
+                    Nothing
+                    [ Inline
+                        mempty
+                        (Str "The double hail-and-rainbow level makes my toes tingle.")
+                    ])
+             , Inline mempty (Str "\nA bold statement.")
+             , Inline
+                 mempty
+                 (Footnote
+                    (Just (FootnoteId "disclaimer"))
+                    [ Inline mempty (Str "Opinions are my own.") ])
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Another outrageous statement.")
+             , Inline mempty (Footnote (Just (FootnoteId "disclaimer")) [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/embedded/title.test b/test/asciidoctor/embedded/title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/embedded/title.test
@@ -0,0 +1,19 @@
+:showtitle:
+= The Dangerous and Thrilling Documentation Chronicles
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors =
+            [ Author { authorName = "" , authorEmail = Nothing } ]
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "showtitle" , "" ) ]
+        }
+  , docBlocks = []
+  }
diff --git a/test/asciidoctor/embedded/toc-title.test b/test/asciidoctor/embedded/toc-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/embedded/toc-title.test
@@ -0,0 +1,28 @@
+= Document Title
+:toc:
+:toc-title: Table of Adventures
+
+== Cavern Glow
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "sectids" , "" )
+              , ( "toc" , "" )
+              , ( "toc-title" , "Table of Adventures" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Cavern Glow") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/embedded/toc.test b/test/asciidoctor/embedded/toc.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/embedded/toc.test
@@ -0,0 +1,24 @@
+// Actual TOC content is rendered in the outline template, this template
+// usually renders just a "border".
+= Document Title
+:toc:
+
+== Cavern Glow
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) , ( "toc" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Cavern Glow") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/example/basic.test b/test/asciidoctor/example/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/example/basic.test
@@ -0,0 +1,31 @@
+====
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (ExampleBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/example/with-id-and-role.test b/test/asciidoctor/example/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/example/with-id-and-role.test
@@ -0,0 +1,33 @@
+[#lorem.ipsum]
+====
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "lorem" ) , ( "role" , "ipsum" ) ] )
+          Nothing
+          (ExampleBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/example/with-title.test b/test/asciidoctor/example/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/example/with-title.test
@@ -0,0 +1,41 @@
+.Sample document
+====
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua.
+
+The document header is useful, but not required.
+====
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Sample document") ]))
+          (ExampleBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.")
+                    ])
+             , Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty (Str "The document header is useful, but not required.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/fenced/basic.test b/test/asciidoctor/fenced/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/fenced/basic.test
@@ -0,0 +1,21 @@
+```ruby
+test = 3 <1>
+```
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby")) [ SourceLine "test = 3" [ Callout 1 ] ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/level1.test b/test/asciidoctor/floating_title/level1.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/level1.test
@@ -0,0 +1,20 @@
+[discrete]
+== Discrete Title Level 1
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DiscreteHeading
+             (Level 1) [ Inline mempty (Str "Discrete Title Level 1") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/level2.test b/test/asciidoctor/floating_title/level2.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/level2.test
@@ -0,0 +1,20 @@
+[discrete]
+=== Discrete Title Level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DiscreteHeading
+             (Level 2) [ Inline mempty (Str "Discrete Title Level 2") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/level3.test b/test/asciidoctor/floating_title/level3.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/level3.test
@@ -0,0 +1,20 @@
+[discrete]
+==== Discrete Title Level 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DiscreteHeading
+             (Level 3) [ Inline mempty (Str "Discrete Title Level 3") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/level4.test b/test/asciidoctor/floating_title/level4.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/level4.test
@@ -0,0 +1,20 @@
+[discrete]
+===== Discrete Title Level 4
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DiscreteHeading
+             (Level 4) [ Inline mempty (Str "Discrete Title Level 4") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/level5.test b/test/asciidoctor/floating_title/level5.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/level5.test
@@ -0,0 +1,20 @@
+[discrete]
+====== Discrete Title Level 5
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (DiscreteHeading
+             (Level 5) [ Inline mempty (Str "Discrete Title Level 5") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/with-custom-id.test b/test/asciidoctor/floating_title/with-custom-id.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/with-custom-id.test
@@ -0,0 +1,21 @@
+[discrete, id=flying]
+== Discrete Title Level 1
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "flying" ) ] )
+          Nothing
+          (DiscreteHeading
+             (Level 1) [ Inline mempty (Str "Discrete Title Level 1") ])
+      ]
+  }
diff --git a/test/asciidoctor/floating_title/with-roles.test b/test/asciidoctor/floating_title/with-roles.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/floating_title/with-roles.test
@@ -0,0 +1,21 @@
+[discrete.flying.circus]
+=== Discrete Title Level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "role" , "flying circus" ) ] )
+          Nothing
+          (DiscreteHeading
+             (Level 2) [ Inline mempty (Str "Discrete Title Level 2") ])
+      ]
+  }
diff --git a/test/asciidoctor/image/basic.test b/test/asciidoctor/image/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/basic.test
@@ -0,0 +1,18 @@
+image::sunset.jpg[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-align.test b/test/asciidoctor/image/with-align.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-align.test
@@ -0,0 +1,19 @@
+image::sunset.jpg[align="center"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "align" , "center" ) ] )
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-alt-text.test b/test/asciidoctor/image/with-alt-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-alt-text.test
@@ -0,0 +1,22 @@
+image::sunset.jpg[Shining sun]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (BlockImage
+             (Target "sunset.jpg")
+             (Just (AltText "Shining sun"))
+             Nothing
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-dimensions.test b/test/asciidoctor/image/with-dimensions.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-dimensions.test
@@ -0,0 +1,22 @@
+image::sunset.jpg[Shining sun, 300, 200]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (BlockImage
+             (Target "sunset.jpg")
+             (Just (AltText "Shining sun"))
+             (Just (Width 300))
+             (Just (Height 200)))
+      ]
+  }
diff --git a/test/asciidoctor/image/with-float.test b/test/asciidoctor/image/with-float.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-float.test
@@ -0,0 +1,19 @@
+image::sunset.jpg[float="right"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "float" , "right" ) ] )
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-id.test b/test/asciidoctor/image/with-id.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-id.test
@@ -0,0 +1,20 @@
+[[img-sunset]]
+image::sunset.jpg[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "img-sunset" ) ] )
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-link.test b/test/asciidoctor/image/with-link.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-link.test
@@ -0,0 +1,22 @@
+image::sunset.jpg[link="http://www.flickr.com/photos/javh/5448336655"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( []
+          , fromList
+              [ ( "link" , "http://www.flickr.com/photos/javh/5448336655" ) ]
+          )
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-roles.test b/test/asciidoctor/image/with-roles.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-roles.test
@@ -0,0 +1,19 @@
+image::sunset.jpg[role="right text-center"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "role" , "right text-center" ) ] )
+          Nothing
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/image/with-title.test b/test/asciidoctor/image/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/image/with-title.test
@@ -0,0 +1,19 @@
+.A mountain sunset
+image::sunset.jpg[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "A mountain sunset") ]))
+          (BlockImage (Target "sunset.jpg") Nothing Nothing Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/bibref-with-text.test b/test/asciidoctor/inline_anchor/bibref-with-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/bibref-with-text.test
@@ -0,0 +1,36 @@
+// Supported since Asciidoctor 1.5.6.
+// This is an item (anchor) in the bibliography, not a link to it.
+[bibliography]
+* [[[prag, 1]]] Andy Hunt & Dave Thomas. The Pragmatic Programmer
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "bibliography" ] , fromList [] )
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty (BibliographyAnchor "prag" [ Inline mempty (Str "1") ])
+                        , Inline
+                            mempty (Str "Andy Hunt & Dave Thomas. The Pragmatic Programmer")
+                        ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/bibref.test b/test/asciidoctor/inline_anchor/bibref.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/bibref.test
@@ -0,0 +1,34 @@
+// This is an item (anchor) in the bibliography, not a link to it.
+[bibliography]
+* [[[prag]]] Andy Hunt & Dave Thomas. The Pragmatic Programmer
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "bibliography" ] , fromList [] )
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline mempty (BibliographyAnchor "prag" [])
+                        , Inline
+                            mempty (Str "Andy Hunt & Dave Thomas. The Pragmatic Programmer")
+                        ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/link-with-role.test b/test/asciidoctor/inline_anchor/link-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/link-with-role.test
@@ -0,0 +1,28 @@
+:linkattrs:
+http://discuss.asciidoctor.org/[*mailing list*, role="green"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "linkattrs" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "green" ) ] )
+                 (Link
+                    URLLink
+                    (Target "http://discuss.asciidoctor.org/")
+                    [ Inline mempty (Bold [ Inline mempty (Str "mailing list") ]) ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/link-with-target-blank.test b/test/asciidoctor/inline_anchor/link-with-target-blank.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/link-with-target-blank.test
@@ -0,0 +1,25 @@
+link:view-source:asciidoctor.org[Asciidoctor homepage^]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Link
+                    URLLink
+                    (Target "view-source:asciidoctor.org")
+                    [ Inline mempty (Str "Asciidoctor homepage^") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/link-with-text.test b/test/asciidoctor/inline_anchor/link-with-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/link-with-text.test
@@ -0,0 +1,25 @@
+irc://irc.freenode.org/#asciidoctor[Asciidoctor IRC channel]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Link
+                    URLLink
+                    (Target "irc://irc.freenode.org/#asciidoctor")
+                    [ Inline mempty (Str "Asciidoctor IRC channel") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/link.test b/test/asciidoctor/inline_anchor/link.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/link.test
@@ -0,0 +1,25 @@
+http://www.asciidoctor.org
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Link
+                    URLLink
+                    (Target "http://www.asciidoctor.org")
+                    [ Inline mempty (Str "http://www.asciidoctor.org") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/ref.test b/test/asciidoctor/inline_anchor/ref.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/ref.test
@@ -0,0 +1,23 @@
+[[bookmark-a]] Inline anchors make arbitrary content referenceable.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (InlineAnchor "bookmark-a" [])
+             , Inline
+                 mempty
+                 (Str " Inline anchors make arbitrary content referenceable.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref-interdoc.test b/test/asciidoctor/inline_anchor/xref-interdoc.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref-interdoc.test
@@ -0,0 +1,22 @@
+The section <<manual.adoc#page-break>> describes how to add a page break.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "The section ")
+             , Inline mempty (CrossReference "manual.adoc#page-break" Nothing)
+             , Inline mempty (Str " describes how to add a page break.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref-reftext.test b/test/asciidoctor/inline_anchor/xref-reftext.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref-reftext.test
@@ -0,0 +1,36 @@
+Refer to <<install>>.
+
+[#install, reftext="Installation Procedure"]
+== Installation
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Refer to ")
+             , Inline
+                 mempty
+                 (CrossReference
+                    "install" (Just [ Inline mempty (Str "Installation") ]))
+             , Inline mempty (Str ".")
+             ])
+      , Block
+          Attr
+          ( []
+          , fromList
+              [ ( "id" , "install" ) , ( "reftext" , "Installation Procedure" ) ]
+          )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Installation") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref-resolved-text.test b/test/asciidoctor/inline_anchor/xref-resolved-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref-resolved-text.test
@@ -0,0 +1,29 @@
+Refer to <<Section A>>.
+
+== Section A
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Refer to ")
+             , Inline mempty (CrossReference "Section A" Nothing)
+             , Inline mempty (Str ".")
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_a" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section A") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref-with-text.test b/test/asciidoctor/inline_anchor/xref-with-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref-with-text.test
@@ -0,0 +1,25 @@
+The section <<page-break, Page break>> describes how to add a page break.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "The section ")
+             , Inline
+                 mempty
+                 (CrossReference
+                    "page-break" (Just [ Inline mempty (Str "Page break") ]))
+             , Inline mempty (Str " describes how to add a page break.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref-xrefstyle.test b/test/asciidoctor/inline_anchor/xref-xrefstyle.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref-xrefstyle.test
@@ -0,0 +1,43 @@
+// Supported since Asciidoctor 1.5.6.
+:sectnums:
+:section-refsig: Sec.
+:xrefstyle: short
+Refer to <<install>>.
+
+[[install]]
+== Installation
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "sectids" , "" )
+              , ( "section-refsig" , "Sec." )
+              , ( "sectnums" , "" )
+              , ( "xrefstyle" , "short" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Refer to ")
+             , Inline
+                 mempty
+                 (CrossReference
+                    "install" (Just [ Inline mempty (Str "Installation") ]))
+             , Inline mempty (Str ".")
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "install" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Installation") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/inline_anchor/xref.test b/test/asciidoctor/inline_anchor/xref.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_anchor/xref.test
@@ -0,0 +1,22 @@
+The section <<page-break>> describes how to add a page break.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "The section ")
+             , Inline mempty (CrossReference "page-break" Nothing)
+             , Inline mempty (Str " describes how to add a page break.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_break/hardbreaks.test b/test/asciidoctor/inline_break/hardbreaks.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_break/hardbreaks.test
@@ -0,0 +1,25 @@
+[%hardbreaks]
+Ruby is red.
+Java is black.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "hardbreaks" ) ] )
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Ruby is red.")
+             , Inline mempty HardBreak
+             , Inline mempty (Str "Java is black.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_break/plus-sign.test b/test/asciidoctor/inline_break/plus-sign.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_break/plus-sign.test
@@ -0,0 +1,23 @@
+Rubies are red, +
+Topazes are blue.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Rubies are red, ")
+             , Inline mempty HardBreak
+             , Inline mempty (Str "Topazes are blue.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_button/basic.test b/test/asciidoctor/inline_button/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_button/basic.test
@@ -0,0 +1,17 @@
+:experimental:
+btn:[OK]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (Paragraph [ Inline mempty (Button "OK") ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_callout/basic.test b/test/asciidoctor/inline_callout/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_callout/basic.test
@@ -0,0 +1,22 @@
+[source]
+----
+"Hello world!" // <1>
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             Nothing [ SourceLine "\"Hello world!\" //" [ Callout 1 ] ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_footnote/basic.test b/test/asciidoctor/inline_footnote/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_footnote/basic.test
@@ -0,0 +1,28 @@
+Apocalyptic party.footnote:[The double hail-and-rainbow level makes my toes tingle.]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "Apocalyptic party.")
+             , Inline
+                 mempty
+                 (Footnote
+                    Nothing
+                    [ Inline
+                        mempty
+                        (Str "The double hail-and-rainbow level makes my toes tingle.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_footnote/xref-unresolved.test b/test/asciidoctor/inline_footnote/xref-unresolved.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_footnote/xref-unresolved.test
@@ -0,0 +1,21 @@
+A bold statement.footnoteref:[foobar]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "A bold statement.")
+             , Inline mempty (Footnote (Just (FootnoteId "foobar")) [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_footnote/xref.test b/test/asciidoctor/inline_footnote/xref.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_footnote/xref.test
@@ -0,0 +1,28 @@
+A bold statement.footnoteref:[disclaimer,Opinions are my own.]
+Another outrageous statement.footnoteref:[disclaimer]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "A bold statement.")
+             , Inline
+                 mempty
+                 (Footnote
+                    (Just (FootnoteId "disclaimer"))
+                    [ Inline mempty (Str "Opinions are my own.") ])
+             , Inline mempty (Str "\nAnother outrageous statement.")
+             , Inline mempty (Footnote (Just (FootnoteId "disclaimer")) [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-no-icons.test b/test/asciidoctor/inline_image/icon-no-icons.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-no-icons.test
@@ -0,0 +1,15 @@
+icon:tags[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (Paragraph [ Inline mempty (Icon "tags") ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-with-dimensions.test b/test/asciidoctor/inline_image/icon-with-dimensions.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-with-dimensions.test
@@ -0,0 +1,25 @@
+:icons:
+icon:tags[height=25, width=35]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "height" , "25" ) , ( "width" , "35" ) ] )
+                 (Icon "tags")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-with-float.test b/test/asciidoctor/inline_image/icon-with-float.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-with-float.test
@@ -0,0 +1,23 @@
+:icons:
+icon:heart[float="right"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr ( [] , fromList [ ( "float" , "right" ) ] ) (Icon "heart")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-with-link.test b/test/asciidoctor/inline_image/icon-with-link.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-with-link.test
@@ -0,0 +1,31 @@
+:icons:
+icon:download[link="http://rubygems.org/downloads/asciidoctor-1.5.2.gem"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( []
+                 , fromList
+                     [ ( "link"
+                       , "http://rubygems.org/downloads/asciidoctor-1.5.2.gem"
+                       )
+                     ]
+                 )
+                 (Icon "download")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-with-role.test b/test/asciidoctor/inline_image/icon-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-with-role.test
@@ -0,0 +1,23 @@
+:icons:
+icon:tags[role="blue"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr ( [] , fromList [ ( "role" , "blue" ) ] ) (Icon "tags")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon-with-title.test b/test/asciidoctor/inline_image/icon-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon-with-title.test
@@ -0,0 +1,25 @@
+:icons:
+icon:heart[title="I <3 Asciidoctor"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "title" , "I <3 Asciidoctor" ) ] )
+                 (Icon "heart")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/icon.test b/test/asciidoctor/inline_image/icon.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/icon.test
@@ -0,0 +1,17 @@
+:icons:
+icon:tags[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "icons" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (Paragraph [ Inline mempty (Icon "tags") ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image-with-alt-text.test b/test/asciidoctor/inline_image/image-with-alt-text.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image-with-alt-text.test
@@ -0,0 +1,23 @@
+image:linux.svg[Tux]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (InlineImage
+                    (Target "linux.svg") (Just (AltText "Tux")) Nothing Nothing)
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image-with-dimensions.test b/test/asciidoctor/inline_image/image-with-dimensions.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image-with-dimensions.test
@@ -0,0 +1,26 @@
+image:linux.svg[Tux, 25, 35]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (InlineImage
+                    (Target "linux.svg")
+                    (Just (AltText "Tux"))
+                    (Just (Width 25))
+                    (Just (Height 35)))
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image-with-float.test b/test/asciidoctor/inline_image/image-with-float.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image-with-float.test
@@ -0,0 +1,23 @@
+image:linux.svg[float="right"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "float" , "right" ) ] )
+                 (InlineImage (Target "linux.svg") Nothing Nothing Nothing)
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image-with-link.test b/test/asciidoctor/inline_image/image-with-link.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image-with-link.test
@@ -0,0 +1,26 @@
+image:linux.svg[link="http://inkscape.org/doc/examples/tux.svg"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( []
+                 , fromList
+                     [ ( "link" , "http://inkscape.org/doc/examples/tux.svg" ) ]
+                 )
+                 (InlineImage (Target "linux.svg") Nothing Nothing Nothing)
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image-with-role.test b/test/asciidoctor/inline_image/image-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image-with-role.test
@@ -0,0 +1,23 @@
+image:linux.svg[role="black"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "black" ) ] )
+                 (InlineImage (Target "linux.svg") Nothing Nothing Nothing)
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_image/image.test b/test/asciidoctor/inline_image/image.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_image/image.test
@@ -0,0 +1,21 @@
+image:linux.svg[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty (InlineImage (Target "linux.svg") Nothing Nothing Nothing)
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_kbd/basic.test b/test/asciidoctor/inline_kbd/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_kbd/basic.test
@@ -0,0 +1,18 @@
+:experimental:
+kbd:[F11]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty Nothing (Paragraph [ Inline mempty (Kbd [ "F11" ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_kbd/keyseq.test b/test/asciidoctor/inline_kbd/keyseq.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_kbd/keyseq.test
@@ -0,0 +1,20 @@
+:experimental:
+kbd:[Ctrl+Shift+N]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph [ Inline mempty (Kbd [ "Ctrl" , "Shift" , "N" ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_menu/menu.test b/test/asciidoctor/inline_menu/menu.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_menu/menu.test
@@ -0,0 +1,18 @@
+:experimental:
+menu:File[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty Nothing (Paragraph [ Inline mempty (Menu [ "File" ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_menu/menuitem.test b/test/asciidoctor/inline_menu/menuitem.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_menu/menuitem.test
@@ -0,0 +1,20 @@
+:experimental:
+menu:File[Save]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph [ Inline mempty (Menu [ "File" , "Save" ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_menu/submenu.test b/test/asciidoctor/inline_menu/submenu.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_menu/submenu.test
@@ -0,0 +1,20 @@
+:experimental:
+menu:View[Zoom > Reset]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "experimental" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph [ Inline mempty (Menu [ "View" , "Zoom" , "Reset" ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/asciimath.test b/test/asciidoctor/inline_quoted/asciimath.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/asciimath.test
@@ -0,0 +1,18 @@
+asciimath:[sqrt(4) = 2]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph [ Inline mempty (Math (Just AsciiMath) "sqrt(4) = 2") ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/basic.test b/test/asciidoctor/inline_quoted/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/basic.test
@@ -0,0 +1,23 @@
+[why]#chunky bacon#
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Span [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/double-with-role.test b/test/asciidoctor/inline_quoted/double-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/double-with-role.test
@@ -0,0 +1,23 @@
+[why]"`chunky bacon`"
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (DoubleQuoted [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/double.test b/test/asciidoctor/inline_quoted/double.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/double.test
@@ -0,0 +1,21 @@
+"`chunky bacon`"
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty (DoubleQuoted [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/emphasis-with-role.test b/test/asciidoctor/inline_quoted/emphasis-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/emphasis-with-role.test
@@ -0,0 +1,23 @@
+[why]_chunky bacon_
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Italic [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/emphasis.test b/test/asciidoctor/inline_quoted/emphasis.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/emphasis.test
@@ -0,0 +1,19 @@
+_chunky bacon_
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Italic [ Inline mempty (Str "chunky bacon") ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/latexmath.test b/test/asciidoctor/inline_quoted/latexmath.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/latexmath.test
@@ -0,0 +1,23 @@
+latexmath:[C = \alpha + \beta Y^{\gamma} + \epsilon]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Math
+                    (Just LaTeXMath) "C = \\alpha + \\beta Y^{\\gamma} + \\epsilon")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/mark.test b/test/asciidoctor/inline_quoted/mark.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/mark.test
@@ -0,0 +1,20 @@
+#chunky bacon#
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Highlight [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/mixed-monospace-bold-italic.test b/test/asciidoctor/inline_quoted/mixed-monospace-bold-italic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/mixed-monospace-bold-italic.test
@@ -0,0 +1,39 @@
+`*_monospace bold italic phrase_*` and le``**__tt__**``ers
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Monospace
+                    [ Inline
+                        mempty
+                        (Bold
+                           [ Inline
+                               mempty
+                               (Italic [ Inline mempty (Str "monospace bold italic phrase") ])
+                           ])
+                    ])
+             , Inline mempty (Str " and le")
+             , Inline
+                 mempty
+                 (Monospace
+                    [ Inline
+                        mempty
+                        (Bold [ Inline mempty (Italic [ Inline mempty (Str "tt") ]) ])
+                    ])
+             , Inline mempty (Str "ers")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/monospaced-with-role.test b/test/asciidoctor/inline_quoted/monospaced-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/monospaced-with-role.test
@@ -0,0 +1,23 @@
+[why]`hello world!`
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Monospace [ Inline mempty (Str "hello world!") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/monospaced.test b/test/asciidoctor/inline_quoted/monospaced.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/monospaced.test
@@ -0,0 +1,20 @@
+`hello world!`
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Monospace [ Inline mempty (Str "hello world!") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/single-with-role.test b/test/asciidoctor/inline_quoted/single-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/single-with-role.test
@@ -0,0 +1,23 @@
+[why]'`chunky bacon`'
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (SingleQuoted [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/single.test b/test/asciidoctor/inline_quoted/single.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/single.test
@@ -0,0 +1,21 @@
+'`chunky bacon`'
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty (SingleQuoted [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/strong-with-role.test b/test/asciidoctor/inline_quoted/strong-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/strong-with-role.test
@@ -0,0 +1,23 @@
+[why]*chunky bacon*
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Bold [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/strong.test b/test/asciidoctor/inline_quoted/strong.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/strong.test
@@ -0,0 +1,19 @@
+*chunky bacon*
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Bold [ Inline mempty (Str "chunky bacon") ]) ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/subscript-with-role.test b/test/asciidoctor/inline_quoted/subscript-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/subscript-with-role.test
@@ -0,0 +1,24 @@
+[why]~sub~chunky bacon
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Subscript [ Inline mempty (Str "sub") ])
+             , Inline mempty (Str "chunky bacon")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/subscript.test b/test/asciidoctor/inline_quoted/subscript.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/subscript.test
@@ -0,0 +1,21 @@
+~sub~chunky bacon
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Subscript [ Inline mempty (Str "sub") ])
+             , Inline mempty (Str "chunky bacon")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/superscript-with-role.test b/test/asciidoctor/inline_quoted/superscript-with-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/superscript-with-role.test
@@ -0,0 +1,24 @@
+[why]^super^chunky bacon
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "why" ) ] )
+                 (Superscript [ Inline mempty (Str "super") ])
+             , Inline mempty (Str "chunky bacon")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/superscript.test b/test/asciidoctor/inline_quoted/superscript.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/superscript.test
@@ -0,0 +1,21 @@
+^super^chunky bacon
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Superscript [ Inline mempty (Str "super") ])
+             , Inline mempty (Str "chunky bacon")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/inline_quoted/with-id.test b/test/asciidoctor/inline_quoted/with-id.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/inline_quoted/with-id.test
@@ -0,0 +1,23 @@
+[#why]_chunky bacon_
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "id" , "why" ) ] )
+                 (Italic [ Inline mempty (Str "chunky bacon") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/basic-nowrap.test b/test/asciidoctor/listing/basic-nowrap.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/basic-nowrap.test
@@ -0,0 +1,27 @@
+[options="nowrap"]
+----
+ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "nowrap" ) ] )
+          Nothing
+          (Listing
+             Nothing
+             [ SourceLine
+                 "ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\""
+                 []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/basic-with-id-and-role.test b/test/asciidoctor/listing/basic-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/basic-with-id-and-role.test
@@ -0,0 +1,29 @@
+[#code.example]
+----
+echo -n "Please enter your name: "
+read name
+echo "Hello, $name!"
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "code" ) , ( "role" , "example" ) ] )
+          Nothing
+          (Listing
+             Nothing
+             [ SourceLine "echo -n \"Please enter your name: \"" []
+             , SourceLine "read name" []
+             , SourceLine "echo \"Hello, $name!\"" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/basic-with-title.test b/test/asciidoctor/listing/basic-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/basic-with-title.test
@@ -0,0 +1,28 @@
+.Reading user input
+----
+echo -n "Please enter your name: "
+read name
+echo "Hello, $name!"
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Reading user input") ]))
+          (Listing
+             Nothing
+             [ SourceLine "echo -n \"Please enter your name: \"" []
+             , SourceLine "read name" []
+             , SourceLine "echo \"Hello, $name!\"" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/basic.test b/test/asciidoctor/listing/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/basic.test
@@ -0,0 +1,27 @@
+----
+echo -n "Please enter your name: "
+read name
+echo "Hello, $name!"
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             Nothing
+             [ SourceLine "echo -n \"Please enter your name: \"" []
+             , SourceLine "read name" []
+             , SourceLine "echo \"Hello, $name!\"" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/source-nowrap.test b/test/asciidoctor/listing/source-nowrap.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/source-nowrap.test
@@ -0,0 +1,51 @@
+[source, java, options="nowrap"]
+----
+public class ApplicationConfigurationProvider extends HttpConfigurationProvider {
+
+   public Configuration getConfiguration(ServletContext context) {
+      return ConfigurationBuilder.begin()
+               .addRule()
+               .when(Direction.isInbound().and(Path.matches("/{path}")))
+               .perform(Log.message(Level.INFO, "Client requested path: {path}"))
+               .where("path").matches(".*");
+   }
+}
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "nowrap" ) ] )
+          Nothing
+          (Listing
+             (Just (Language "java"))
+             [ SourceLine
+                 "public class ApplicationConfigurationProvider extends HttpConfigurationProvider {"
+                 []
+             , SourceLine "" []
+             , SourceLine
+                 "   public Configuration getConfiguration(ServletContext context) {"
+                 []
+             , SourceLine "      return ConfigurationBuilder.begin()" []
+             , SourceLine "               .addRule()" []
+             , SourceLine
+                 "               .when(Direction.isInbound().and(Path.matches(\"/{path}\")))"
+                 []
+             , SourceLine
+                 "               .perform(Log.message(Level.INFO, \"Client requested path: {path}\"))"
+                 []
+             , SourceLine "               .where(\"path\").matches(\".*\");" []
+             , SourceLine "   }" []
+             , SourceLine "}" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/source-with-language.test b/test/asciidoctor/listing/source-with-language.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/source-with-language.test
@@ -0,0 +1,28 @@
+[source, ruby]
+----
+5.times do
+  print "Odelay!"
+end
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby"))
+             [ SourceLine "5.times do" []
+             , SourceLine "  print \"Odelay!\"" []
+             , SourceLine "end" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/source-with-title.test b/test/asciidoctor/listing/source-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/source-with-title.test
@@ -0,0 +1,29 @@
+[source]
+.Odelay!
+----
+5.times do
+  print "Odelay!"
+end
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Odelay!") ]))
+          (Listing
+             Nothing
+             [ SourceLine "5.times do" []
+             , SourceLine "  print \"Odelay!\"" []
+             , SourceLine "end" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/listing/source.test b/test/asciidoctor/listing/source.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/listing/source.test
@@ -0,0 +1,28 @@
+[source]
+----
+5.times do
+  print "Odelay!"
+end
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             Nothing
+             [ SourceLine "5.times do" []
+             , SourceLine "  print \"Odelay!\"" []
+             , SourceLine "end" []
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/literal/basic.test b/test/asciidoctor/literal/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/basic.test
@@ -0,0 +1,23 @@
+....
+error: The requested operation returned error: 1954 Forbidden search for defensive operations manual
+absolutely fatal: operation initiation lost in the dodecahedron of doom
+would you like to die again? y/n
+....
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (LiteralBlock
+             "error: The requested operation returned error: 1954 Forbidden search for defensive operations manual\nabsolutely fatal: operation initiation lost in the dodecahedron of doom\nwould you like to die again? y/n\n")
+      ]
+  }
diff --git a/test/asciidoctor/literal/indented.test b/test/asciidoctor/literal/indented.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/indented.test
@@ -0,0 +1,19 @@
+ $ gem install foo
+ $ gem --version
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (LiteralBlock "$ gem install foo\n$ gem --version\n")
+      ]
+  }
diff --git a/test/asciidoctor/literal/labeled.test b/test/asciidoctor/literal/labeled.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/labeled.test
@@ -0,0 +1,18 @@
+[literal]
+gem install foobar
+gem --version
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty Nothing (LiteralBlock "gem install foobar\ngem --version\n")
+      ]
+  }
diff --git a/test/asciidoctor/literal/nowrap.test b/test/asciidoctor/literal/nowrap.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/nowrap.test
@@ -0,0 +1,25 @@
+[options="nowrap"]
+....
+error: The requested operation returned error: 1954 Forbidden search for defensive operations manual
+absolutely fatal: operation initiation lost in the dodecahedron of doom
+would you like to die again? y/n
+....
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "nowrap" ) ] )
+          Nothing
+          (LiteralBlock
+             "error: The requested operation returned error: 1954 Forbidden search for defensive operations manual\nabsolutely fatal: operation initiation lost in the dodecahedron of doom\nwould you like to die again? y/n\n")
+      ]
+  }
diff --git a/test/asciidoctor/literal/with-id-and-role.test b/test/asciidoctor/literal/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/with-id-and-role.test
@@ -0,0 +1,25 @@
+[#error.fatal]
+....
+error: The requested operation returned error: 1954 Forbidden search for defensive operations manual
+absolutely fatal: operation initiation lost in the dodecahedron of doom
+would you like to die again? y/n
+....
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "error" ) , ( "role" , "fatal" ) ] )
+          Nothing
+          (LiteralBlock
+             "error: The requested operation returned error: 1954 Forbidden search for defensive operations manual\nabsolutely fatal: operation initiation lost in the dodecahedron of doom\nwould you like to die again? y/n\n")
+      ]
+  }
diff --git a/test/asciidoctor/literal/with-title.test b/test/asciidoctor/literal/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/literal/with-title.test
@@ -0,0 +1,24 @@
+.Die again?
+....
+error: The requested operation returned error: 1954 Forbidden search for defensive operations manual
+absolutely fatal: operation initiation lost in the dodecahedron of doom
+would you like to die again? y/n
+....
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Die again?") ]))
+          (LiteralBlock
+             "error: The requested operation returned error: 1954 Forbidden search for defensive operations manual\nabsolutely fatal: operation initiation lost in the dodecahedron of doom\nwould you like to die again? y/n\n")
+      ]
+  }
diff --git a/test/asciidoctor/olist/basic.test b/test/asciidoctor/olist/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/basic.test
@@ -0,0 +1,34 @@
+. Step 1
+. Step 2
+. Step 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 1") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 2") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 3") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/complex-content.test b/test/asciidoctor/olist/complex-content.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/complex-content.test
@@ -0,0 +1,79 @@
+. Every list item has at least one paragraph of content,
+  which may be wrapped, even using a hanging indent.
++
+Additional paragraphs or blocks are adjoined by putting
+a list continuation on a line adjacent to both blocks.
++
+list continuation:: a plus sign (`{plus}`) on a line by itself
+
+. A literal paragraph does not require a list continuation.
+
+ $ gem install asciidoctor
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Every list item has at least one paragraph of content,\n  which may be wrapped, even using a hanging indent.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Additional paragraphs or blocks are adjoined by putting\na list continuation on a line adjacent to both blocks.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (DefinitionList
+                        [ ( [ Inline mempty (Str "list continuation") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline mempty (Str "a plus sign (")
+                                   , Inline mempty (Monospace [ Inline mempty (Str "+") ])
+                                   , Inline mempty (Str ") on a line by itself")
+                                   ])
+                            ]
+                          )
+                        ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "A literal paragraph does not require a list continuation.")
+                        ])
+                 ]
+             ])
+      , Block mempty Nothing (LiteralBlock "$ gem install asciidoctor\n")
+      ]
+  }
diff --git a/test/asciidoctor/olist/max-nesting.test b/test/asciidoctor/olist/max-nesting.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/max-nesting.test
@@ -0,0 +1,87 @@
+. level 1
+.. level 2
+... level 3
+.... level 4
+..... level 5
+.. level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "level 1") ])
+                 , Block
+                     mempty
+                     Nothing
+                     (List
+                        (OrderedList (Level 2) Nothing)
+                        [ ListItem
+                            Nothing
+                            [ Block
+                                mempty Nothing (Paragraph [ Inline mempty (Str "level 2") ])
+                            , Block
+                                mempty
+                                Nothing
+                                (List
+                                   (OrderedList (Level 3) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "level 3") ])
+                                       , Block
+                                           mempty
+                                           Nothing
+                                           (List
+                                              (OrderedList (Level 4) Nothing)
+                                              [ ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "level 4") ])
+                                                  , Block
+                                                      mempty
+                                                      Nothing
+                                                      (List
+                                                         (OrderedList (Level 5) Nothing)
+                                                         [ ListItem
+                                                             Nothing
+                                                             [ Block
+                                                                 mempty
+                                                                 Nothing
+                                                                 (Paragraph
+                                                                    [ Inline mempty (Str "level 5")
+                                                                    ])
+                                                             ]
+                                                         ])
+                                                  ]
+                                              ])
+                                       ]
+                                   ])
+                            ]
+                        , ListItem
+                            Nothing
+                            [ Block
+                                mempty Nothing (Paragraph [ Inline mempty (Str "level 2") ])
+                            ]
+                        ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/with-id-and-role.test b/test/asciidoctor/olist/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/with-id-and-role.test
@@ -0,0 +1,36 @@
+[#steps.green]
+. Step 1
+. Step 2
+. Step 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "steps" ) , ( "role" , "green" ) ] )
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 1") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 2") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 3") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/with-numeration-styles.test b/test/asciidoctor/olist/with-numeration-styles.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/with-numeration-styles.test
@@ -0,0 +1,91 @@
+[decimal]
+. level 1
+[upperalpha]
+.. level 2
+[loweralpha]
+... level 3
+[lowerroman]
+.... level 4
+[lowergreek]
+..... level 5
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "decimal" ] , fromList [] )
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "level 1") ])
+                 , Block
+                     Attr
+                     ( [ "upperalpha" ] , fromList [] )
+                     Nothing
+                     (List
+                        (OrderedList (Level 2) Nothing)
+                        [ ListItem
+                            Nothing
+                            [ Block
+                                mempty Nothing (Paragraph [ Inline mempty (Str "level 2") ])
+                            , Block
+                                Attr
+                                ( [ "loweralpha" ] , fromList [] )
+                                Nothing
+                                (List
+                                   (OrderedList (Level 3) Nothing)
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "level 3") ])
+                                       , Block
+                                           Attr
+                                           ( [ "lowerroman" ] , fromList [] )
+                                           Nothing
+                                           (List
+                                              (OrderedList (Level 4) Nothing)
+                                              [ ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "level 4") ])
+                                                  , Block
+                                                      Attr
+                                                      ( [ "lowergreek" ] , fromList [] )
+                                                      Nothing
+                                                      (List
+                                                         (OrderedList (Level 5) Nothing)
+                                                         [ ListItem
+                                                             Nothing
+                                                             [ Block
+                                                                 mempty
+                                                                 Nothing
+                                                                 (Paragraph
+                                                                    [ Inline mempty (Str "level 5")
+                                                                    ])
+                                                             ]
+                                                         ])
+                                                  ]
+                                              ])
+                                       ]
+                                   ])
+                            ]
+                        ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/with-reversed.test b/test/asciidoctor/olist/with-reversed.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/with-reversed.test
@@ -0,0 +1,36 @@
+[%reversed]
+. Step 1
+. Step 2
+. Step 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "reversed" ) ] )
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 1") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 2") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 3") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/with-start.test b/test/asciidoctor/olist/with-start.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/with-start.test
@@ -0,0 +1,36 @@
+[start=6]
+. Step 1
+. Step 2
+. Step 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "start" , "6" ) ] )
+          Nothing
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 1") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 2") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 3") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/olist/with-title.test b/test/asciidoctor/olist/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/olist/with-title.test
@@ -0,0 +1,35 @@
+.Steps
+. Step 1
+. Step 2
+. Step 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Steps") ]))
+          (List
+             (OrderedList (Level 1) Nothing)
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 1") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 2") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "Step 3") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/abstract-with-id-and-role.test b/test/asciidoctor/open/abstract-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/abstract-with-id-and-role.test
@@ -0,0 +1,35 @@
+[abstract, id="open", role="example"]
+--
+This is an abstract quote block.
+Who knows what it really means?
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "abstract" ]
+          , fromList [ ( "id" , "open" ) , ( "role" , "example" ) ]
+          )
+          Nothing
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "This is an abstract quote block.\nWho knows what it really means?")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/abstract-with-title.test b/test/asciidoctor/open/abstract-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/abstract-with-title.test
@@ -0,0 +1,35 @@
+[abstract]
+.Abstract title is abstract
+--
+This is an abstract quote block.
+Who knows what it really means?
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "abstract" ] , fromList [] )
+          (Just
+             (BlockTitle [ Inline mempty (Str "Abstract title is abstract") ]))
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "This is an abstract quote block.\nWho knows what it really means?")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/abstract.test b/test/asciidoctor/open/abstract.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/abstract.test
@@ -0,0 +1,28 @@
+[abstract]
+--
+This is an abstract quote block.
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [ "abstract" ] , fromList [] )
+          Nothing
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "This is an abstract quote block.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/basic-with-id-and-role.test b/test/asciidoctor/open/basic-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/basic-with-id-and-role.test
@@ -0,0 +1,33 @@
+[#open.example]
+--
+An open block can be an anonymous container,
+or it can masquerade as any other block.
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "open" ) , ( "role" , "example" ) ] )
+          Nothing
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "An open block can be an anonymous container,\nor it can masquerade as any other block.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/basic-with-title.test b/test/asciidoctor/open/basic-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/basic-with-title.test
@@ -0,0 +1,32 @@
+.An open block
+--
+An open block can be an anonymous container,
+or it can masquerade as any other block.
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "An open block") ]))
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "An open block can be an anonymous container,\nor it can masquerade as any other block.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/open/basic.test b/test/asciidoctor/open/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/open/basic.test
@@ -0,0 +1,31 @@
+--
+An open block can be an anonymous container,
+or it can masquerade as any other block.
+--
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (OpenBlock
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "An open block can be an anonymous container,\nor it can masquerade as any other block.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/outline/basic.test b/test/asciidoctor/outline/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/outline/basic.test
@@ -0,0 +1,56 @@
+= Document Title
+:toc:
+
+== Section 1
+
+== Section 2
+
+=== Section 2.1
+
+==== Section 2.1.1
+
+== Section 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) , ( "toc" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_1" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section 1") ] [])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_2" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section 2") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_2_1" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Section 2.1") ]
+                    [ Block
+                        Attr
+                        ( [] , fromList [ ( "id" , "_section_2_1_1" ) ] )
+                        Nothing
+                        (Section (Level 3) [ Inline mempty (Str "Section 2.1.1") ] [])
+                    ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_3" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section 3") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/outline/numbered.test b/test/asciidoctor/outline/numbered.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/outline/numbered.test
@@ -0,0 +1,70 @@
+= Document Title
+:toc:
+:numbered:
+
+== Section 1
+
+:numbered!:
+
+== Unnumbered Section
+
+:numbered:
+
+== Section 2
+
+=== Section 2.1
+
+== Section 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "numbered" , "" ) , ( "sectids" , "" ) , ( "toc" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_1" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section 1") ]
+             [ Block
+                 mempty Nothing (Paragraph [ Inline mempty (Str ":numbered!:") ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_unnumbered_section" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Unnumbered Section") ]
+             [ Block
+                 mempty Nothing (Paragraph [ Inline mempty (Str ":numbered:") ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_2" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section 2") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_2_1" ) ] )
+                 Nothing
+                 (Section (Level 2) [ Inline mempty (Str "Section 2.1") ] [])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_3" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section 3") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/outline/sectnumlevels.test b/test/asciidoctor/outline/sectnumlevels.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/outline/sectnumlevels.test
@@ -0,0 +1,59 @@
+= Document Title
+:toc:
+:toclevels: 3
+:numbered:
+:sectnumlevels: 1
+
+== Section 1
+
+=== Section 1.1
+
+==== Section 1.1.1
+
+== Section 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "numbered" , "" )
+              , ( "sectids" , "" )
+              , ( "sectnumlevels" , "1" )
+              , ( "toc" , "" )
+              , ( "toclevels" , "3" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_1" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section 1") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_1_1" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Section 1.1") ]
+                    [ Block
+                        Attr
+                        ( [] , fromList [ ( "id" , "_section_1_1_1" ) ] )
+                        Nothing
+                        (Section (Level 3) [ Inline mempty (Str "Section 1.1.1") ] [])
+                    ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_2" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section 2") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/outline/toclevels.test b/test/asciidoctor/outline/toclevels.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/outline/toclevels.test
@@ -0,0 +1,52 @@
+= Document Title
+:toc:
+:toclevels: 1
+
+== Section 1
+
+=== Section 1.1
+
+==== Section 1.1.1
+
+== Section 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "sectids" , "" ) , ( "toc" , "" ) , ( "toclevels" , "1" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_1" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section 1") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_1_1" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Section 1.1") ]
+                    [ Block
+                        Attr
+                        ( [] , fromList [ ( "id" , "_section_1_1_1" ) ] )
+                        Nothing
+                        (Section (Level 3) [ Inline mempty (Str "Section 1.1.1") ] [])
+                    ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_2" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section 2") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/page_break/basic.test b/test/asciidoctor/page_break/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/page_break/basic.test
@@ -0,0 +1,25 @@
+Text on the first page
+
+<<<
+
+was breaked!
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph [ Inline mempty (Str "Text on the first page") ])
+      , Block mempty Nothing PageBreak
+      , Block
+          mempty Nothing (Paragraph [ Inline mempty (Str "was breaked!") ])
+      ]
+  }
diff --git a/test/asciidoctor/paragraph/basic.test b/test/asciidoctor/paragraph/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/paragraph/basic.test
@@ -0,0 +1,35 @@
+Paragraphs don't require any special markup in AsciiDoc.
+A paragraph is just one or more lines of consecutive text.
+
+To begin a new paragraph, separate it by at least one blank line.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "Paragraphs don\8217t require any special markup in AsciiDoc.\nA paragraph is just one or more lines of consecutive text.")
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "To begin a new paragraph, separate it by at least one blank line.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/paragraph/with-id-and-role.test b/test/asciidoctor/paragraph/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/paragraph/with-id-and-role.test
@@ -0,0 +1,26 @@
+[#para-1.red]
+Paragraphs don't require any special markup in AsciiDoc.
+A paragraph is just one or more lines of consecutive text.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "para-1" ) , ( "role" , "red" ) ] )
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "Paragraphs don\8217t require any special markup in AsciiDoc.\nA paragraph is just one or more lines of consecutive text.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/paragraph/with-title.test b/test/asciidoctor/paragraph/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/paragraph/with-title.test
@@ -0,0 +1,36 @@
+.Paragraphs
+Paragraphs don't require any special markup in AsciiDoc.
+A paragraph is just one or more lines of consecutive text.
+
+To begin a new paragraph, separate it by at least one blank line.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Paragraphs") ]))
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "Paragraphs don\8217t require any special markup in AsciiDoc.\nA paragraph is just one or more lines of consecutive text.")
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "To begin a new paragraph, separate it by at least one blank line.")
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/pass/basic.test b/test/asciidoctor/pass/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/pass/basic.test
@@ -0,0 +1,21 @@
+++++
+<p>Allons-y!</p>
+image:tiger.png[]
+++++
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (PassthroughBlock "<p>Allons-y!</p>\nimage:tiger.png[]\n")
+      ]
+  }
diff --git a/test/asciidoctor/preamble/basic.test b/test/asciidoctor/preamble/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/preamble/basic.test
@@ -0,0 +1,48 @@
+= The Dangerous and Thrilling Documentation Chronicles
+
+This journey begins on a bleary Monday morning.
+Our intrepid team is in desperate need of double shot mochas, but the milk expired eight days ago.
+
+== Cavern Glow
+The river rages through the cavern, rattling its content.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "This journey begins on a bleary Monday morning.\nOur intrepid team is in desperate need of double shot mochas, but the milk expired eight days ago.")
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Cavern Glow") ]
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str "The river rages through the cavern, rattling its content.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/preamble/toc-placement-preamble.test b/test/asciidoctor/preamble/toc-placement-preamble.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/preamble/toc-placement-preamble.test
@@ -0,0 +1,50 @@
+= The Dangerous and Thrilling Documentation Chronicles
+:toc: preamble
+
+This journey begins on a bleary Monday morning.
+Our intrepid team is in desperate need of double shot mochas, but the milk expired eight days ago.
+
+== Cavern Glow
+The river rages through the cavern, rattling its content.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle =
+            [ Inline
+                mempty (Str "The Dangerous and Thrilling Documentation Chronicles")
+            ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "preamble" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Str
+                    "This journey begins on a bleary Monday morning.\nOur intrepid team is in desperate need of double shot mochas, but the milk expired eight days ago.")
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_cavern_glow" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Cavern Glow") ]
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str "The river rages through the cavern, rattling its content.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/basic.test b/test/asciidoctor/quote/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/basic.test
@@ -0,0 +1,31 @@
+[quote]
+Four score and seven years ago our fathers brought forth
+on this continent a new nation...
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (QuoteBlock
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Four score and seven years ago our fathers brought forth\non this continent a new nation\8230")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/block.test b/test/asciidoctor/quote/block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/block.test
@@ -0,0 +1,48 @@
+____
+Dennis: Come and see the violence inherent in the system. Help! Help! I'm being repressed!
+
+King Arthur: Bloody peasant!
+
+Dennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That's what I'm on about! Did you see him repressing me? You saw him, Didn't you?
+____
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (QuoteBlock
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Dennis: Come and see the violence inherent in the system. Help! Help! I\8217m being repressed!")
+                    ])
+             , Block
+                 mempty
+                 Nothing
+                 (Paragraph [ Inline mempty (Str "King Arthur: Bloody peasant!") ])
+             , Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "Dennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That\8217s what I\8217m on about! Did you see him repressing me? You saw him, Didn\8217t you?")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/with-attribution-and-citetitle.test b/test/asciidoctor/quote/with-attribution-and-citetitle.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/with-attribution-and-citetitle.test
@@ -0,0 +1,28 @@
+[quote, Captain James T. Kirk, Star Trek IV: The Voyage Home]
+Everybody remember where we parked.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (QuoteBlock
+             (Just
+                (Attribution
+                   "Captain James T. Kirk, Star Trek IV: The Voyage Home"))
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "Everybody remember where we parked.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/with-attribution.test b/test/asciidoctor/quote/with-attribution.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/with-attribution.test
@@ -0,0 +1,29 @@
+[quote, Albert Einstein]
+A person who never made a mistake never tried anything new.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (QuoteBlock
+             (Just (Attribution "Albert Einstein"))
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str "A person who never made a mistake never tried anything new.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/with-id-and-role.test b/test/asciidoctor/quote/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/with-id-and-role.test
@@ -0,0 +1,29 @@
+[quote, id="parking", role="startrek"]
+Everybody remember where we parked.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( []
+          , fromList [ ( "id" , "parking" ) , ( "role" , "startrek" ) ]
+          )
+          Nothing
+          (QuoteBlock
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "Everybody remember where we parked.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/quote/with-title.test b/test/asciidoctor/quote/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/quote/with-title.test
@@ -0,0 +1,33 @@
+.After landing the cloaked Klingon bird of prey in Golden Gate park:
+[quote]
+Everybody remember where we parked.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just
+             (BlockTitle
+                [ Inline
+                    mempty
+                    (Str
+                       "After landing the cloaked Klingon bird of prey in Golden Gate park:")
+                ]))
+          (QuoteBlock
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "Everybody remember where we parked.") ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/section/book-part-title.test b/test/asciidoctor/section/book-part-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/book-part-title.test
@@ -0,0 +1,34 @@
+// Subsequent level-0 titles are allowed only for doctype book.
+= Document Title
+:doctype: book
+
+= Part Title
+
+== Section Level 1
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "doctype" , "book" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_part_title" ) ] )
+          Nothing
+          (Section
+             (Level 0)
+             [ Inline mempty (Str "Part Title") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_level_1" ) ] )
+                 Nothing
+                 (Section (Level 1) [ Inline mempty (Str "Section Level 1") ] [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/section/level1.test b/test/asciidoctor/section/level1.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/level1.test
@@ -0,0 +1,19 @@
+== Section Level 1
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_1" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Level 1") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/level2.test b/test/asciidoctor/section/level2.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/level2.test
@@ -0,0 +1,19 @@
+=== Section Level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_2" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Level 2") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/level3.test b/test/asciidoctor/section/level3.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/level3.test
@@ -0,0 +1,19 @@
+==== Section Level 3
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_3" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Level 3") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/level4.test b/test/asciidoctor/section/level4.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/level4.test
@@ -0,0 +1,19 @@
+===== Section Level 4
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_4" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Level 4") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/level5.test b/test/asciidoctor/section/level5.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/level5.test
@@ -0,0 +1,19 @@
+====== Section Level 5
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_5" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Level 5") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/max-nesting.test b/test/asciidoctor/section/max-nesting.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/max-nesting.test
@@ -0,0 +1,67 @@
+== Section Level 1
+
+=== Section Level 2
+
+==== Section Level 3
+
+===== Section Level 4
+
+====== Section Level 5
+
+=== Section Level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_section_level_1" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Section Level 1") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_level_2" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Section Level 2") ]
+                    [ Block
+                        Attr
+                        ( [] , fromList [ ( "id" , "_section_level_3" ) ] )
+                        Nothing
+                        (Section
+                           (Level 3)
+                           [ Inline mempty (Str "Section Level 3") ]
+                           [ Block
+                               Attr
+                               ( [] , fromList [ ( "id" , "_section_level_4" ) ] )
+                               Nothing
+                               (Section
+                                  (Level 4)
+                                  [ Inline mempty (Str "Section Level 4") ]
+                                  [ Block
+                                      Attr
+                                      ( [] , fromList [ ( "id" , "_section_level_5" ) ] )
+                                      Nothing
+                                      (Section
+                                         (Level 5) [ Inline mempty (Str "Section Level 5") ] [])
+                                  ])
+                           ])
+                    ])
+             , Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_section_level_2_2" ) ] )
+                 Nothing
+                 (Section (Level 2) [ Inline mempty (Str "Section Level 2") ] [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/section/numbered-sectnumlevels-1.test b/test/asciidoctor/section/numbered-sectnumlevels-1.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/numbered-sectnumlevels-1.test
@@ -0,0 +1,73 @@
+:numbered:
+:sectnumlevels: 1
+== Introduction to Asciidoctor
+
+== Quick Starts
+
+=== Usage
+
+==== Using the Command Line Interface
+
+=== Syntax
+
+== Terms and Concepts
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "numbered" , "" )
+              , ( "sectids" , "" )
+              , ( "sectnumlevels" , "1" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction_to_asciidoctor" ) ] )
+          Nothing
+          (Section
+             (Level 1) [ Inline mempty (Str "Introduction to Asciidoctor") ] [])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_quick_starts" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Quick Starts") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_usage" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Usage") ]
+                    [ Block
+                        Attr
+                        ( []
+                        , fromList [ ( "id" , "_using_the_command_line_interface" ) ]
+                        )
+                        Nothing
+                        (Section
+                           (Level 3)
+                           [ Inline mempty (Str "Using the Command Line Interface") ]
+                           [])
+                    ])
+             , Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_syntax" ) ] )
+                 Nothing
+                 (Section (Level 2) [ Inline mempty (Str "Syntax") ] [])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_terms_and_concepts" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Terms and Concepts") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/numbered.test b/test/asciidoctor/section/numbered.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/numbered.test
@@ -0,0 +1,76 @@
+:numbered:
+== Introduction to Asciidoctor
+
+== Quick Starts
+
+=== Usage
+
+==== Using the Command Line Interface
+
+===== Processing Your Content
+
+=== Syntax
+
+== Terms and Concepts
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "numbered" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction_to_asciidoctor" ) ] )
+          Nothing
+          (Section
+             (Level 1) [ Inline mempty (Str "Introduction to Asciidoctor") ] [])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_quick_starts" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Quick Starts") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_usage" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Usage") ]
+                    [ Block
+                        Attr
+                        ( []
+                        , fromList [ ( "id" , "_using_the_command_line_interface" ) ]
+                        )
+                        Nothing
+                        (Section
+                           (Level 3)
+                           [ Inline mempty (Str "Using the Command Line Interface") ]
+                           [ Block
+                               Attr
+                               ( [] , fromList [ ( "id" , "_processing_your_content" ) ] )
+                               Nothing
+                               (Section
+                                  (Level 4) [ Inline mempty (Str "Processing Your Content") ] [])
+                           ])
+                    ])
+             , Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_syntax" ) ] )
+                 Nothing
+                 (Section (Level 2) [ Inline mempty (Str "Syntax") ] [])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_terms_and_concepts" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Terms and Concepts") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/sectanchors-and-sectlinks.test b/test/asciidoctor/section/sectanchors-and-sectlinks.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/sectanchors-and-sectlinks.test
@@ -0,0 +1,27 @@
+:sectanchors:
+:sectlinks:
+== Linked title with anchor
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList
+              [ ( "sectanchors" , "" )
+              , ( "sectids" , "" )
+              , ( "sectlinks" , "" )
+              ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_linked_title_with_anchor" ) ] )
+          Nothing
+          (Section
+             (Level 1) [ Inline mempty (Str "Linked title with anchor") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/sectanchors.test b/test/asciidoctor/section/sectanchors.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/sectanchors.test
@@ -0,0 +1,21 @@
+:sectanchors:
+== Title with anchor
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectanchors" , "" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_title_with_anchor" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Title with anchor") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/sectlinks.test b/test/asciidoctor/section/sectlinks.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/sectlinks.test
@@ -0,0 +1,21 @@
+:sectlinks:
+== Linked title
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "sectlinks" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_linked_title" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Linked title") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/with-custom-id.test b/test/asciidoctor/section/with-custom-id.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/with-custom-id.test
@@ -0,0 +1,20 @@
+[#foo]
+== Section Title
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "foo" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Title") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/section/with-roles.test b/test/asciidoctor/section/with-roles.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/section/with-roles.test
@@ -0,0 +1,23 @@
+[.center.red]
+== Section Title
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( []
+          , fromList
+              [ ( "id" , "_section_title" ) , ( "role" , "center red" ) ]
+          )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Section Title") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/sidebar/basic.test b/test/asciidoctor/sidebar/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/sidebar/basic.test
@@ -0,0 +1,32 @@
+****
+AsciiDoc was first released in Nov 2002 by Stuart Rackham.
+It was designed from the start to be a shorthand syntax
+for producing professional documents like DocBook and LaTeX.
+****
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Sidebar
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "AsciiDoc was first released in Nov 2002 by Stuart Rackham.\nIt was designed from the start to be a shorthand syntax\nfor producing professional documents like DocBook and LaTeX.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/sidebar/with-id-and-role.test b/test/asciidoctor/sidebar/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/sidebar/with-id-and-role.test
@@ -0,0 +1,34 @@
+[#origin.center]
+****
+AsciiDoc was first released in Nov 2002 by Stuart Rackham.
+It was designed from the start to be a shorthand syntax
+for producing professional documents like DocBook and LaTeX.
+****
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "origin" ) , ( "role" , "center" ) ] )
+          Nothing
+          (Sidebar
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "AsciiDoc was first released in Nov 2002 by Stuart Rackham.\nIt was designed from the start to be a shorthand syntax\nfor producing professional documents like DocBook and LaTeX.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/sidebar/with-title.test b/test/asciidoctor/sidebar/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/sidebar/with-title.test
@@ -0,0 +1,33 @@
+.AsciiDoc history
+****
+AsciiDoc was first released in Nov 2002 by Stuart Rackham.
+It was designed from the start to be a shorthand syntax
+for producing professional documents like DocBook and LaTeX.
+****
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "AsciiDoc history") ]))
+          (Sidebar
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty
+                        (Str
+                           "AsciiDoc was first released in Nov 2002 by Stuart Rackham.\nIt was designed from the start to be a shorthand syntax\nfor producing professional documents like DocBook and LaTeX.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/stem/asciimath.test b/test/asciidoctor/stem/asciimath.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/stem/asciimath.test
@@ -0,0 +1,19 @@
+:stem: asciimath
+[stem]
+++++
+sqrt(4) = 2
+++++
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "stem" , "asciimath" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (MathBlock Nothing "sqrt(4) = 2\n") ]
+  }
diff --git a/test/asciidoctor/stem/latexmath.test b/test/asciidoctor/stem/latexmath.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/stem/latexmath.test
@@ -0,0 +1,24 @@
+:stem: latexmath
+[stem]
+++++
+C = \alpha + \beta Y^{\gamma} + \epsilon
+++++
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "stem" , "latexmath" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (MathBlock
+             Nothing "C = \\alpha + \\beta Y^{\\gamma} + \\epsilon\n")
+      ]
+  }
diff --git a/test/asciidoctor/stem/with-id-and-role.test b/test/asciidoctor/stem/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/stem/with-id-and-role.test
@@ -0,0 +1,23 @@
+:stem:
+[stem, id="sqrt", role="right"]
+++++
+sqrt(4) = 2
+++++
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) , ( "stem" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "sqrt" ) , ( "role" , "right" ) ] )
+          Nothing
+          (MathBlock Nothing "sqrt(4) = 2\n")
+      ]
+  }
diff --git a/test/asciidoctor/stem/with-title.test b/test/asciidoctor/stem/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/stem/with-title.test
@@ -0,0 +1,23 @@
+:stem:
+[stem]
+.Equation
+++++
+sqrt(4) = 2
+++++
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) , ( "stem" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Equation") ]))
+          (MathBlock Nothing "sqrt(4) = 2\n")
+      ]
+  }
diff --git a/test/asciidoctor/table/aligns-per-cell.test b/test/asciidoctor/table/aligns-per-cell.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/aligns-per-cell.test
@@ -0,0 +1,195 @@
+[cols="3"]
+|===
+^|Prefix the +{vbar}+ with +{caret}+ to center content horizontally
+<|Prefix the +{vbar}+ with +<+ to align the content to the left horizontally
+>|Prefix the +{vbar}+ with +>+ to align the content to the right horizontally
+
+.^|Prefix the +{vbar}+ with a +.+ and +{caret}+ to center the content in the cell vertically
+.<|Prefix the +{vbar}+ with a +.+ and +<+ to align the content to the top of the cell
+.>|Prefix the +{vbar}+ with a +.+ and +>+ to align the content to the bottom of the cell
+
+3+^.^|This content spans three columns (+3{plus}+) and is centered horizontally (+{caret}+) and vertically (+.{caret}+) within the cell.
+
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Just 3
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline mempty (Str "Prefix the ")
+                                   , Inline mempty (Str "{vbar}")
+                                   , Inline mempty (Str " with ")
+                                   , Inline mempty (Str "{caret}")
+                                   , Inline mempty (Str " to center content horizontally")
+                                   ])
+                            ]
+                        , cellHorizAlign = Just AlignCenter
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             [ TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline mempty (Str "Prefix the ")
+                                , Inline mempty (Str "{vbar}")
+                                , Inline mempty (Str " with ")
+                                , Inline mempty (Str "<")
+                                , Inline
+                                    mempty (Str " to align the content to the left horizontally")
+                                ])
+                         ]
+                     , cellHorizAlign = Just AlignLeft
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             , TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline mempty (Str "Prefix the ")
+                                , Inline mempty (Str "{vbar}")
+                                , Inline mempty (Str " with ")
+                                , Inline mempty (Str ">")
+                                , Inline
+                                    mempty (Str " to align the content to the right horizontally")
+                                ])
+                         ]
+                     , cellHorizAlign = Just AlignRight
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             , TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline mempty (Str "Prefix the ")
+                                , Inline mempty (Str "{vbar}")
+                                , Inline mempty (Str " with a ")
+                                , Inline mempty (Str ".")
+                                , Inline mempty (Str " and ")
+                                , Inline mempty (Str "{caret}")
+                                , Inline
+                                    mempty (Str " to center the content in the cell vertically")
+                                ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Just AlignMiddle
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             , TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline mempty (Str "Prefix the ")
+                                , Inline mempty (Str "{vbar}")
+                                , Inline mempty (Str " with a ")
+                                , Inline mempty (Str ".")
+                                , Inline mempty (Str " and ")
+                                , Inline mempty (Str "<")
+                                , Inline
+                                    mempty (Str " to align the content to the top of the cell")
+                                ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Just AlignTop
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             , TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline mempty (Str "Prefix the ")
+                                , Inline mempty (Str "{vbar}")
+                                , Inline mempty (Str " with a ")
+                                , Inline mempty (Str ".")
+                                , Inline mempty (Str " and ")
+                                , Inline mempty (Str ">")
+                                , Inline
+                                    mempty (Str " to align the content to the bottom of the cell")
+                                ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Just AlignBottom
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline mempty (Str "This content spans three columns (")
+                                   , Inline mempty (Str "3{plus}")
+                                   , Inline mempty (Str ") and is centered horizontally (")
+                                   , Inline mempty (Str "{caret}")
+                                   , Inline mempty (Str ") and vertically (")
+                                   , Inline mempty (Str ".{caret}")
+                                   , Inline mempty (Str ") within the cell.")
+                                   ])
+                            ]
+                        , cellHorizAlign = Just AlignCenter
+                        , cellVertAlign = Just AlignMiddle
+                        , cellColspan = 3
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/basic.test b/test/asciidoctor/table/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/basic.test
@@ -0,0 +1,91 @@
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+| Cell in column 1, row 2 | Cell in column 2, row 2
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/cell-with-paragraphs.test b/test/asciidoctor/table/cell-with-paragraphs.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/cell-with-paragraphs.test
@@ -0,0 +1,69 @@
+|===
+
+|Single paragraph on row 1
+
+|First paragraph on row 2
+
+Second paragraph on row 2
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Single paragraph on row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "First paragraph on row 2") ])
+                            , Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Second paragraph on row 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/colspan.test b/test/asciidoctor/table/colspan.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/colspan.test
@@ -0,0 +1,116 @@
+|===
+
+| Cell in column 1, row 1 | Cell in column 2, row 1 | Cell in column 3, row 1
+
+2+|Content in a single cell that spans columns 1 and 3 | Cell in column 3, row 1
+
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Str "Content in a single cell that spans columns 1 and 3")
+                                   ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 2
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/insane-cells-formatting.test b/test/asciidoctor/table/insane-cells-formatting.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/insane-cells-formatting.test
@@ -0,0 +1,200 @@
+|===
+
+2*>m|This content is duplicated across two columns.
+
+It is aligned right horizontally.
+
+And it is monospaced.
+
+.3+^.>s|This cell spans 3 rows. The content is centered horizontally, aligned to the bottom of the cell, and strong.
+e|This content is emphasized.
+
+.^l|This content is aligned to the top of the cell and literal.
+
+a|
+[source]
+puts "This is a source block!"
+
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace
+                                          [ Inline
+                                              mempty
+                                              (Str "This content is duplicated across two columns.")
+                                          ])
+                                   ])
+                            , Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace
+                                          [ Inline mempty (Str "It is aligned right horizontally.")
+                                          ])
+                                   ])
+                            , Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace [ Inline mempty (Str "And it is monospaced.") ])
+                                   ])
+                            ]
+                        , cellHorizAlign = Just AlignRight
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace
+                                          [ Inline
+                                              mempty
+                                              (Str "This content is duplicated across two columns.")
+                                          ])
+                                   ])
+                            , Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace
+                                          [ Inline mempty (Str "It is aligned right horizontally.")
+                                          ])
+                                   ])
+                            , Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty
+                                       (Monospace [ Inline mempty (Str "And it is monospaced.") ])
+                                   ])
+                            ]
+                        , cellHorizAlign = Just AlignRight
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             [ TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline
+                                    mempty
+                                    (Bold
+                                       [ Inline
+                                           mempty
+                                           (Str
+                                              "This cell spans 3 rows. The content is centered horizontally, aligned to the bottom of the cell, and strong.")
+                                       ])
+                                ])
+                         ]
+                     , cellHorizAlign = Just AlignCenter
+                     , cellVertAlign = Just AlignBottom
+                     , cellColspan = 1
+                     , cellRowspan = 3
+                     }
+                 , TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline
+                                    mempty
+                                    (Italic [ Inline mempty (Str "This content is emphasized.") ])
+                                ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             , TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (LiteralBlock
+                                "This content is aligned to the top of the cell and literal.\n\n")
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Just AlignMiddle
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Listing
+                                   Nothing [ SourceLine "puts \"This is a source block!\"" [] ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/rowspan.test b/test/asciidoctor/table/rowspan.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/rowspan.test
@@ -0,0 +1,156 @@
+|===
+
+| Cell in column 1, row 1 | Cell in column 2, row 1 | Cell in column 3, row 1
+
+.2+|Content in a single cell that spans rows 2 and 3
+
+| Cell in column 2, row 2 | Cell in column 3, row 2
+
+| Cell in column 2, row 3 | Cell in column 3, row 3
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             [ TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph
+                                [ Inline
+                                    mempty (Str "Content in a single cell that spans rows 2 and 3")
+                                ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 2
+                     }
+                 , TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 2, row 2") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 , TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 3, row 2") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 3") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 3") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/with-autowidth-and-width.test b/test/asciidoctor/table/with-autowidth-and-width.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-autowidth-and-width.test
@@ -0,0 +1,65 @@
+[options="autowidth", width=80]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "width" , "80" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-autowidth.test b/test/asciidoctor/table/with-autowidth.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-autowidth.test
@@ -0,0 +1,64 @@
+[options="autowidth"]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-cols-halign.test b/test/asciidoctor/table/with-cols-halign.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-cols-halign.test
@@ -0,0 +1,84 @@
+[cols="<,^,>"]
+|===
+|Cell in column 1, row 1
+|Cell in column 2, row 1
+|Cell in column 3, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Just AlignLeft
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Just AlignCenter
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Just AlignRight
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-cols-styles.test b/test/asciidoctor/table/with-cols-styles.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-cols-styles.test
@@ -0,0 +1,148 @@
+[cols="a,e,h,l,m,s"]
+|===
+|image::sunset.jpg[AsciiDoc content]
+|Emphasized text
+|Styled like a header
+|Literal block
+|Monospaced text
+|Strong text
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just AsciiDocStyle
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just EmphasisStyle
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just HeaderStyle
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just LiteralStyle
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just MonospaceStyle
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Just StrongStyle
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (BlockImage
+                                   (Target "sunset.jpg")
+                                   (Just (AltText "AsciiDoc content"))
+                                   Nothing
+                                   Nothing)
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty (Italic [ Inline mempty (Str "Emphasized text") ])
+                                   ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Styled like a header") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block mempty Nothing (LiteralBlock "Literal block\n") ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline
+                                       mempty (Monospace [ Inline mempty (Str "Monospaced text") ])
+                                   ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline mempty (Bold [ Inline mempty (Str "Strong text") ]) ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-cols-valign.test b/test/asciidoctor/table/with-cols-valign.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-cols-valign.test
@@ -0,0 +1,84 @@
+[cols=".<,.^,.>"]
+|===
+|Cell in column 1, row 1
+|Cell in column 2, row 1
+|Cell in column 3, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Just AlignTop
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Just AlignMiddle
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Just AlignBottom
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-cols-width.test b/test/asciidoctor/table/with-cols-width.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-cols-width.test
@@ -0,0 +1,84 @@
+[cols="50,20,30"]
+|===
+|Cell in column 1, row 1
+|Cell in column 2, row 1
+|Cell in column 3, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Just 50
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Just 20
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Just 30
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 3, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-float.test b/test/asciidoctor/table/with-float.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-float.test
@@ -0,0 +1,65 @@
+[float=left]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "float" , "left" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-footer.test b/test/asciidoctor/table/with-footer.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-footer.test
@@ -0,0 +1,119 @@
+[options="footer"]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+| Cell in column 1, row 2 | Cell in column 2, row 2
+| Footer in column 1, row 3 | Footer in column 2, row 3
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             [ TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 1, row 2") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 , TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 2, row 2") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Footer in column 1, row 3") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Footer in column 2, row 3") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/with-frame-sides.test b/test/asciidoctor/table/with-frame-sides.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-frame-sides.test
@@ -0,0 +1,65 @@
+[frame=sides]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "frame" , "sides" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-grid-cols.test b/test/asciidoctor/table/with-grid-cols.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-grid-cols.test
@@ -0,0 +1,65 @@
+[grid=cols]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "grid" , "cols" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-header.test b/test/asciidoctor/table/with-header.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-header.test
@@ -0,0 +1,120 @@
+[options="header"]
+|===
+| Name of Column 1 | Name of Column 2
+
+| Cell in column 1, row 1 | Cell in column 2, row 1
+| Cell in column 1, row 2 | Cell in column 2, row 2
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Name of Column 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Name of Column 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             [ TableRow
+                 [ TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 , TableCell
+                     { cellContent =
+                         [ Block
+                             mempty
+                             Nothing
+                             (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                         ]
+                     , cellHorizAlign = Nothing
+                     , cellVertAlign = Nothing
+                     , cellColspan = 1
+                     , cellRowspan = 1
+                     }
+                 ]
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 2") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
diff --git a/test/asciidoctor/table/with-id-and-role.test b/test/asciidoctor/table/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-id-and-role.test
@@ -0,0 +1,65 @@
+[#tabular.center]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "tabular" ) , ( "role" , "center" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-title.test b/test/asciidoctor/table/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-title.test
@@ -0,0 +1,64 @@
+.Table FTW!
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Table FTW!") ]))
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/table/with-width.test b/test/asciidoctor/table/with-width.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/table/with-width.test
@@ -0,0 +1,65 @@
+[width=80]
+|===
+| Cell in column 1, row 1 | Cell in column 2, row 1
+|===
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "width" , "80" ) ] )
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 1, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block
+                                mempty
+                                Nothing
+                                (Paragraph [ Inline mempty (Str "Cell in column 2, row 1") ])
+                            ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             Nothing)
+      ]
+  }
diff --git a/test/asciidoctor/thematic_break/basic.test b/test/asciidoctor/thematic_break/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/thematic_break/basic.test
@@ -0,0 +1,13 @@
+'''
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks = [ Block mempty Nothing ThematicBreak ]
+  }
diff --git a/test/asciidoctor/toc/doc-without-sections.test b/test/asciidoctor/toc/doc-without-sections.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/doc-without-sections.test
@@ -0,0 +1,17 @@
+= Document Title
+:toc: macro
+
+toc::[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks = [ Block mempty Nothing TOC ]
+  }
diff --git a/test/asciidoctor/toc/in-preamble.test b/test/asciidoctor/toc/in-preamble.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/in-preamble.test
@@ -0,0 +1,37 @@
+= Document Title
+:toc: macro
+
+toc::[]
+
+== The Ravages of Writing
+
+=== A Recipe for Potion
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing TOC
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_the_ravages_of_writing" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "The Ravages of Writing") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_a_recipe_for_potion" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2) [ Inline mempty (Str "A Recipe for Potion") ] [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/toc/in-section.test b/test/asciidoctor/toc/in-section.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/in-section.test
@@ -0,0 +1,49 @@
+// The toc node is used only with toc::[] macro!
+// Actual TOC content is rendered in the outline template, this template
+// usually renders just a "border".
+= Document Title
+:toc: macro
+
+== Introduction
+
+toc::[]
+
+== The Ravages of Writing
+
+=== A Recipe for Potion
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Introduction") ]
+             [ Block mempty Nothing TOC ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_the_ravages_of_writing" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "The Ravages of Writing") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_a_recipe_for_potion" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2) [ Inline mempty (Str "A Recipe for Potion") ] [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/toc/with-id-and-role.test b/test/asciidoctor/toc/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/with-id-and-role.test
@@ -0,0 +1,41 @@
+= Document Title
+:toc: macro
+
+== Introduction
+
+toc::[id="mytoc", role="taco"]
+
+== The Ravages of Writing
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Introduction") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "mytoc" ) , ( "role" , "taco" ) ] )
+                 Nothing
+                 TOC
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_the_ravages_of_writing" ) ] )
+          Nothing
+          (Section
+             (Level 1) [ Inline mempty (Str "The Ravages of Writing") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/toc/with-levels.test b/test/asciidoctor/toc/with-levels.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/with-levels.test
@@ -0,0 +1,47 @@
+= Document Title
+:toc: macro
+
+== Introduction
+
+toc::[levels=1]
+
+== The Ravages of Writing
+
+=== A Recipe for Potion
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Introduction") ]
+             [ Block Attr ( [] , fromList [ ( "levels" , "1" ) ] ) Nothing TOC
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_the_ravages_of_writing" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "The Ravages of Writing") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_a_recipe_for_potion" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2) [ Inline mempty (Str "A Recipe for Potion") ] [])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/toc/with-title.test b/test/asciidoctor/toc/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/toc/with-title.test
@@ -0,0 +1,41 @@
+= Document Title
+:toc: macro
+
+== Introduction
+
+toc::[title="Table of Adventures"]
+
+== The Ravages of Writing
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Document Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "sectids" , "" ) , ( "toc" , "macro" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_introduction" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Introduction") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "title" , "Table of Adventures" ) ] )
+                 Nothing
+                 TOC
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_the_ravages_of_writing" ) ] )
+          Nothing
+          (Section
+             (Level 1) [ Inline mempty (Str "The Ravages of Writing") ] [])
+      ]
+  }
diff --git a/test/asciidoctor/ulist/basic.test b/test/asciidoctor/ulist/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/basic.test
@@ -0,0 +1,41 @@
+* Edgar Allen Poe
+* Sheri S. Tepper
+* Bill Bryson
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Edgar Allen Poe") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Sheri S. Tepper") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Bill Bryson") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/ulist/checklist.test b/test/asciidoctor/ulist/checklist.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/checklist.test
@@ -0,0 +1,45 @@
+- [*] checked
+- [x] also checked
+- [ ] not checked
+-     normal list item
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             CheckList
+             [ ListItem
+                 (Just Checked)
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "checked") ])
+                 ]
+             , ListItem
+                 (Just Checked)
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "also checked") ])
+                 ]
+             , ListItem
+                 (Just Unchecked)
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "not checked") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "normal list item") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/ulist/complex-content.test b/test/asciidoctor/ulist/complex-content.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/complex-content.test
@@ -0,0 +1,79 @@
+* Every list item has at least one paragraph of content,
+  which may be wrapped, even using a hanging indent.
++
+Additional paragraphs or blocks are adjoined by putting
+a list continuation on a line adjacent to both blocks.
++
+list continuation:: a plus sign (`{plus}`) on a line by itself
+
+* A literal paragraph does not require a list continuation.
+
+ $ gem install asciidoctor
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Every list item has at least one paragraph of content,\n  which may be wrapped, even using a hanging indent.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str
+                               "Additional paragraphs or blocks are adjoined by putting\na list continuation on a line adjacent to both blocks.")
+                        ])
+                 , Block
+                     mempty
+                     Nothing
+                     (DefinitionList
+                        [ ( [ Inline mempty (Str "list continuation") ]
+                          , [ Block
+                                mempty
+                                Nothing
+                                (Paragraph
+                                   [ Inline mempty (Str "a plus sign (")
+                                   , Inline mempty (Monospace [ Inline mempty (Str "+") ])
+                                   , Inline mempty (Str ") on a line by itself")
+                                   ])
+                            ]
+                          )
+                        ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph
+                        [ Inline
+                            mempty
+                            (Str "A literal paragraph does not require a list continuation.")
+                        ])
+                 ]
+             ])
+      , Block mempty Nothing (LiteralBlock "$ gem install asciidoctor\n")
+      ]
+  }
diff --git a/test/asciidoctor/ulist/max-nesting.test b/test/asciidoctor/ulist/max-nesting.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/max-nesting.test
@@ -0,0 +1,87 @@
+* level 1
+** level 2
+*** level 3
+**** level 4
+***** level 5
+** level 2
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "level 1") ])
+                 , Block
+                     mempty
+                     Nothing
+                     (List
+                        (BulletList (Level 2))
+                        [ ListItem
+                            Nothing
+                            [ Block
+                                mempty Nothing (Paragraph [ Inline mempty (Str "level 2") ])
+                            , Block
+                                mempty
+                                Nothing
+                                (List
+                                   (BulletList (Level 3))
+                                   [ ListItem
+                                       Nothing
+                                       [ Block
+                                           mempty
+                                           Nothing
+                                           (Paragraph [ Inline mempty (Str "level 3") ])
+                                       , Block
+                                           mempty
+                                           Nothing
+                                           (List
+                                              (BulletList (Level 4))
+                                              [ ListItem
+                                                  Nothing
+                                                  [ Block
+                                                      mempty
+                                                      Nothing
+                                                      (Paragraph [ Inline mempty (Str "level 4") ])
+                                                  , Block
+                                                      mempty
+                                                      Nothing
+                                                      (List
+                                                         (BulletList (Level 5))
+                                                         [ ListItem
+                                                             Nothing
+                                                             [ Block
+                                                                 mempty
+                                                                 Nothing
+                                                                 (Paragraph
+                                                                    [ Inline mempty (Str "level 5")
+                                                                    ])
+                                                             ]
+                                                         ])
+                                                  ]
+                                              ])
+                                       ]
+                                   ])
+                            ]
+                        , ListItem
+                            Nothing
+                            [ Block
+                                mempty Nothing (Paragraph [ Inline mempty (Str "level 2") ])
+                            ]
+                        ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/ulist/with-id-and-role.test b/test/asciidoctor/ulist/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/with-id-and-role.test
@@ -0,0 +1,43 @@
+[#authors.green]
+* Edgar Allen Poe
+* Sheri S. Tepper
+* Bill Bryson
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "authors" ) , ( "role" , "green" ) ] )
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Edgar Allen Poe") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Sheri S. Tepper") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Bill Bryson") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/ulist/with-title.test b/test/asciidoctor/ulist/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/ulist/with-title.test
@@ -0,0 +1,42 @@
+.Writers
+* Edgar Allen Poe
+* Sheri S. Tepper
+* Bill Bryson
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Writers") ]))
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Edgar Allen Poe") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty
+                     Nothing
+                     (Paragraph [ Inline mempty (Str "Sheri S. Tepper") ])
+                 ]
+             , ListItem
+                 Nothing
+                 [ Block
+                     mempty Nothing (Paragraph [ Inline mempty (Str "Bill Bryson") ])
+                 ]
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/basic-with-attribution-and-citetitle.test b/test/asciidoctor/verse/basic-with-attribution-and-citetitle.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/basic-with-attribution-and-citetitle.test
@@ -0,0 +1,30 @@
+[verse, Carl Sandburg, two lines from the poem Fog]
+The fog comes
+on little cat feet.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Verse
+             (Just (Attribution "Carl Sandburg, two lines from the poem Fog"))
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/basic-with-attribution.test b/test/asciidoctor/verse/basic-with-attribution.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/basic-with-attribution.test
@@ -0,0 +1,30 @@
+[verse, Carl Sandburg]
+The fog comes
+on little cat feet.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Verse
+             (Just (Attribution "Carl Sandburg"))
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/basic-with-id-and-role.test b/test/asciidoctor/verse/basic-with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/basic-with-id-and-role.test
@@ -0,0 +1,31 @@
+[verse, id="sandburg", role="center"]
+The fog comes
+on little cat feet.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "sandburg" ) , ( "role" , "center" ) ] )
+          Nothing
+          (Verse
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/basic-with-title.test b/test/asciidoctor/verse/basic-with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/basic-with-title.test
@@ -0,0 +1,31 @@
+[verse]
+.Poetry
+The fog comes
+on little cat feet.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Poetry") ]))
+          (Verse
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/basic.test b/test/asciidoctor/verse/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/basic.test
@@ -0,0 +1,30 @@
+[verse]
+The fog comes
+on little cat feet.
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Verse
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/verse/block.test b/test/asciidoctor/verse/block.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/verse/block.test
@@ -0,0 +1,49 @@
+[verse]
+____
+The fog comes
+on little cat feet.
+
+It sits looking
+over harbor and city
+on silent haunches
+and then moves on.
+____
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Verse
+             Nothing
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "The fog comes")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on little cat feet.")
+                    ])
+             , Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "It sits looking")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "over harbor and city")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "on silent haunches")
+                    , Inline mempty HardBreak
+                    , Inline mempty (Str "and then moves on.")
+                    ])
+             ])
+      ]
+  }
diff --git a/test/asciidoctor/video/basic.test b/test/asciidoctor/video/basic.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/basic.test
@@ -0,0 +1,14 @@
+video::video_file.mp4[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block mempty Nothing (BlockVideo (Target "video_file.mp4")) ]
+  }
diff --git a/test/asciidoctor/video/with-dimensions.test b/test/asciidoctor/video/with-dimensions.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-dimensions.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[width=640, height=480]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "height" , "480" ) , ( "width" , "640" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-end.test b/test/asciidoctor/video/with-end.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-end.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[end=60]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "end" , "60" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-id-and-role.test b/test/asciidoctor/video/with-id-and-role.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-id-and-role.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[id="lindsey", role="watch"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "lindsey" ) , ( "role" , "watch" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-options.test b/test/asciidoctor/video/with-options.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-options.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[options="autoplay, loop, nocontrols"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "options" , "autoplay, loop, nocontrols" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-poster.test b/test/asciidoctor/video/with-poster.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-poster.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[poster="sunset.jpg"]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "poster" , "sunset.jpg" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-start.test b/test/asciidoctor/video/with-start.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-start.test
@@ -0,0 +1,19 @@
+video::video_file.mp4[start=10]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "start" , "10" ) ] )
+          Nothing
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/asciidoctor/video/with-title.test b/test/asciidoctor/video/with-title.test
new file mode 100644
--- /dev/null
+++ b/test/asciidoctor/video/with-title.test
@@ -0,0 +1,19 @@
+.Must watch!
+video::video_file.mp4[]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just (BlockTitle [ Inline mempty (Str "Must watch!") ]))
+          (BlockVideo (Target "video_file.mp4"))
+      ]
+  }
diff --git a/test/feature/attrefs/basic.test b/test/feature/attrefs/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/attrefs/basic.test
@@ -0,0 +1,26 @@
+= Title
+:foo: 333
+
+{foo} and *{foo}*
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes =
+            fromList [ ( "foo" , "333" ) , ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Str "333")
+             , Inline mempty (Str " and ")
+             , Inline mempty (Bold [ Inline mempty (Str "333") ])
+             ])
+      ]
+  }
diff --git a/test/feature/callouts/multiple.test b/test/feature/callouts/multiple.test
new file mode 100644
--- /dev/null
+++ b/test/feature/callouts/multiple.test
@@ -0,0 +1,30 @@
+[,ruby]
+----
+import mylib <1> <2> <.>
+def foo
+  return 42 <.> <5>
+end
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Listing
+             (Just (Language "ruby"))
+             [ SourceLine "import mylib" [ Callout 1 , Callout 2 , Callout 3 ]
+             , SourceLine "def foo" []
+             , SourceLine "  return 42" [ Callout 4 , Callout 5 ]
+             , SourceLine "end" []
+             ])
+      ]
+  }
diff --git a/test/feature/counter/basic.test b/test/feature/counter/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/counter/basic.test
@@ -0,0 +1,64 @@
+.Theorem {counter:theorem:A}
+3 is odd.        
+
+.Theorem {counter:theorem}
+4 is even.
+
+{counter:new} {counter:new}
+ 
+{counter:new:15} should be 3.
+
+{counter:special:4} should be 4.
+and now {counter:special}
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          (Just
+             (BlockTitle
+                [ Inline mempty (Str "Theorem ")
+                , Inline mempty (Counter "theorem" UpperAlphaCounter 1)
+                ]))
+          (Paragraph [ Inline mempty (Str "3 is odd.") ])
+      , Block
+          mempty
+          (Just
+             (BlockTitle
+                [ Inline mempty (Str "Theorem ")
+                , Inline mempty (Counter "theorem" UpperAlphaCounter 2)
+                ]))
+          (Paragraph [ Inline mempty (Str "4 is even.") ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Counter "new" DecimalCounter 1)
+             , Inline mempty (Str " ")
+             , Inline mempty (Counter "new" DecimalCounter 2)
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Counter "new" DecimalCounter 3)
+             , Inline mempty (Str " should be 3.")
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (Counter "special" DecimalCounter 4)
+             , Inline mempty (Str " should be 4.\nand now ")
+             , Inline mempty (Counter "special" DecimalCounter 5)
+             ])
+      ]
+  }
diff --git a/test/feature/crossref/basic.test b/test/feature/crossref/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/crossref/basic.test
@@ -0,0 +1,49 @@
+[#mysec]
+== My section
+
+In <<mysec>>, and in <<myanch>>
+
+[[myanch,My anchor]]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "mysec" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "My section") ]
+             [ Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline mempty (Str "In ")
+                    , Inline
+                        mempty
+                        (CrossReference
+                           "mysec" (Just [ Inline mempty (Str "My section") ]))
+                    , Inline mempty (Str ", and in ")
+                    , Inline
+                        mempty
+                        (CrossReference
+                           "myanch" (Just [ Inline mempty (Str "My anchor") ]))
+                    ])
+             , Block
+                 mempty
+                 Nothing
+                 (Paragraph
+                    [ Inline
+                        mempty (InlineAnchor "myanch" [ Inline mempty (Str "My anchor") ])
+                    ])
+             ])
+      ]
+  }
diff --git a/test/feature/example/long.test b/test/feature/example/long.test
new file mode 100644
--- /dev/null
+++ b/test/feature/example/long.test
@@ -0,0 +1,21 @@
+======
+hi
+======
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (ExampleBlock
+             [ Block mempty Nothing (Paragraph [ Inline mempty (Str "hi") ]) ])
+      ]
+  }
diff --git a/test/feature/include/basic.test b/test/feature/include/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/include/basic.test
@@ -0,0 +1,29 @@
+include::inc/baz.adoc[]
+
+and more
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Include
+             "test/feature/include/inc/baz.adoc"
+             (Just
+                [ Block
+                    mempty
+                    Nothing
+                    (Paragraph [ Inline mempty (Bold [ Inline mempty (Str "baz") ]) ])
+                ]))
+      , Block
+          mempty Nothing (Paragraph [ Inline mempty (Str "and more") ])
+      ]
+  }
diff --git a/test/feature/include/foo.adoc b/test/feature/include/foo.adoc
new file mode 100644
--- /dev/null
+++ b/test/feature/include/foo.adoc
@@ -0,0 +1,3 @@
+foo
+
+include::inc/bar.adoc[]
diff --git a/test/feature/include/inc/bar.adoc b/test/feature/include/inc/bar.adoc
new file mode 100644
--- /dev/null
+++ b/test/feature/include/inc/bar.adoc
@@ -0,0 +1,3 @@
+bar
+
+include::baz.adoc[]
diff --git a/test/feature/include/inc/baz.adoc b/test/feature/include/inc/baz.adoc
new file mode 100644
--- /dev/null
+++ b/test/feature/include/inc/baz.adoc
@@ -0,0 +1,1 @@
+*baz*
diff --git a/test/feature/include/listing.test b/test/feature/include/listing.test
new file mode 100644
--- /dev/null
+++ b/test/feature/include/listing.test
@@ -0,0 +1,28 @@
+[,asciidoc]
+----
+include::foo.adoc[]
+----
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (IncludeListing
+             (Just (Language "asciidoc"))
+             "test/feature/include/foo.adoc"
+             (Just
+                [ SourceLine "foo" []
+                , SourceLine "" []
+                , SourceLine "include::inc/bar.adoc[]" []
+                ]))
+      ]
+  }
diff --git a/test/feature/include/nested.test b/test/feature/include/nested.test
new file mode 100644
--- /dev/null
+++ b/test/feature/include/nested.test
@@ -0,0 +1,46 @@
+include::foo.adoc[]
+
+and more
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Include
+             "test/feature/include/foo.adoc"
+             (Just
+                [ Block mempty Nothing (Paragraph [ Inline mempty (Str "foo") ])
+                , Block
+                    mempty
+                    Nothing
+                    (Include
+                       "test/feature/include/inc/bar.adoc"
+                       (Just
+                          [ Block mempty Nothing (Paragraph [ Inline mempty (Str "bar") ])
+                          , Block
+                              mempty
+                              Nothing
+                              (Include
+                                 "test/feature/include/inc/baz.adoc"
+                                 (Just
+                                    [ Block
+                                        mempty
+                                        Nothing
+                                        (Paragraph
+                                           [ Inline mempty (Bold [ Inline mempty (Str "baz") ]) ])
+                                    ]))
+                          ]))
+                ]))
+      , Block
+          mempty Nothing (Paragraph [ Inline mempty (Str "and more") ])
+      ]
+  }
diff --git a/test/feature/index/basic.test b/test/feature/index/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/index/basic.test
@@ -0,0 +1,39 @@
+(((Weapon, Sword)))
+indexterm2:[Lancelot] was a knight.
+Heindexterm:[knight, Knight of the Round Table, Lancelot] did brave deeds.
+I, ((Arthur)), am a king.
+My sword is Excalibur(((Sword, Broadsword, Excalibur)))
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline mempty (IndexEntry (TermConcealed [ "Weapon" , "Sword" ]))
+             , Inline mempty (Str "\n")
+             , Inline mempty (IndexEntry (TermInText "Lancelot"))
+             , Inline mempty (Str " was a knight.\nHe")
+             , Inline
+                 mempty
+                 (IndexEntry
+                    (TermConcealed
+                       [ "knight" , "Knight of the Round Table" , "Lancelot" ]))
+             , Inline mempty (Str " did brave deeds.\nI, ")
+             , Inline mempty (IndexEntry (TermInText "Arthur"))
+             , Inline mempty (Str ", am a king.\nMy sword is Excalibur")
+             , Inline
+                 mempty
+                 (IndexEntry
+                    (TermConcealed [ "Sword" , "Broadsword" , "Excalibur" ]))
+             ])
+      ]
+  }
diff --git a/test/feature/list/indented.test b/test/feature/list/indented.test
new file mode 100644
--- /dev/null
+++ b/test/feature/list/indented.test
@@ -0,0 +1,31 @@
+  * a
+  * b
+    * c
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (BulletList (Level 1))
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "a") ]) ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "b") ]) ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "c") ]) ]
+             ])
+      ]
+  }
diff --git a/test/feature/list/ordered_with_num.test b/test/feature/list/ordered_with_num.test
new file mode 100644
--- /dev/null
+++ b/test/feature/list/ordered_with_num.test
@@ -0,0 +1,27 @@
+3. a
+4. b
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (List
+             (OrderedList (Level 1) (Just 3))
+             [ ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "a") ]) ]
+             , ListItem
+                 Nothing
+                 [ Block mempty Nothing (Paragraph [ Inline mempty (Str "b") ]) ]
+             ])
+      ]
+  }
diff --git a/test/feature/sectids/basic.test b/test/feature/sectids/basic.test
new file mode 100644
--- /dev/null
+++ b/test/feature/sectids/basic.test
@@ -0,0 +1,62 @@
+= Title
+
+== Foo
+
+[#foobar]
+== Foo
+
+=== Foo
+
+==== Foobar
+
+== Hello *there*, section!
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          Attr
+          ( [] , fromList [ ( "id" , "_foo" ) ] )
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "Foo") ] [])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "foobar" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Foo") ]
+             [ Block
+                 Attr
+                 ( [] , fromList [ ( "id" , "_foo_2" ) ] )
+                 Nothing
+                 (Section
+                    (Level 2)
+                    [ Inline mempty (Str "Foo") ]
+                    [ Block
+                        Attr
+                        ( [] , fromList [ ( "id" , "_foobar" ) ] )
+                        Nothing
+                        (Section (Level 3) [ Inline mempty (Str "Foobar") ] [])
+                    ])
+             ])
+      , Block
+          Attr
+          ( [] , fromList [ ( "id" , "_hello_there,_section!" ) ] )
+          Nothing
+          (Section
+             (Level 1)
+             [ Inline mempty (Str "Hello ")
+             , Inline mempty (Bold [ Inline mempty (Str "there") ])
+             , Inline mempty (Str ", section!")
+             ]
+             [])
+      ]
+  }
diff --git a/test/feature/sectids/nosectid.test b/test/feature/sectids/nosectid.test
new file mode 100644
--- /dev/null
+++ b/test/feature/sectids/nosectid.test
@@ -0,0 +1,21 @@
+= Title
+:!sectids:
+
+== My section
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = [ Inline mempty (Str "Title") ]
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList []
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Section (Level 1) [ Inline mempty (Str "My section") ] [])
+      ]
+  }
diff --git a/test/feature/table/longborder.test b/test/feature/table/longborder.test
new file mode 100644
--- /dev/null
+++ b/test/feature/table/longborder.test
@@ -0,0 +1,75 @@
+|======
+| a | b
+| c | d
+|======
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Table
+             [ ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             , ColumnSpec
+                 { colHorizAlign = Nothing
+                 , colVertAlign = Nothing
+                 , colWidth = Nothing
+                 , colStyle = Nothing
+                 }
+             ]
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block mempty Nothing (Paragraph [ Inline mempty (Str "a") ]) ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block mempty Nothing (Paragraph [ Inline mempty (Str "b") ]) ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ])
+             []
+             (Just
+                [ TableRow
+                    [ TableCell
+                        { cellContent =
+                            [ Block mempty Nothing (Paragraph [ Inline mempty (Str "c") ]) ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    , TableCell
+                        { cellContent =
+                            [ Block mempty Nothing (Paragraph [ Inline mempty (Str "d") ]) ]
+                        , cellHorizAlign = Nothing
+                        , cellVertAlign = Nothing
+                        , cellColspan = 1
+                        , cellRowspan = 1
+                        }
+                    ]
+                ]))
+      ]
+  }
