New xml format exactly representing a Pandoc AST.

This adds a reader and writer for an XML format equivalent to `native`
and `json`.

XML schemas for validation can be found in `tools/pandoc-xml.*`.

The format is documented in `doc/xml.md`.

API changes:

- Add module Text.Pandoc.Readers.XML, exporting `readXML`.
- Add module Text.Pandoc.Writers.XML, exporting `writeXML`.

A new unexported module Text.Pandoc.XMLFormat is also added.
This commit is contained in:
massifrg
2025-06-16 01:14:36 +02:00
committed by John MacFarlane
parent cf11339c1d
commit fe3684632b
13 changed files with 3455 additions and 0 deletions
+376
View File
@@ -0,0 +1,376 @@
---
title: XML
author: massifrg@gmail.com
---
# Pandoc XML format
This document describes Pandoc's `xml` format, a 1:1 equivalent
of the `native` and `json` formats.
Here's the xml version of the beginning of this document,
to give you a glimpse of the format:
```xml
<?xml version='1.0' ?>
<Pandoc api-version="1,23,1">
<meta>
<entry key="author">
<MetaInlines>massifrg@gmail.com</MetaInlines>
</entry>
<entry key="title">
<MetaInlines>XML</MetaInlines>
</entry>
</meta>
<blocks>
<Header id="pandoc-xml-format" level="1">Pandoc XML format</Header>
<Para>This document describes Pandocs <Code>xml</Code> format, a 1:1 equivalent<SoftBreak />of the <Code>native</Code> and <Code>json</Code> formats.</Para>
...
</blocks>
</Pandoc>
```
## The tags
If you know [Pandoc types](https://hackage.haskell.org/package/pandoc-types-1.23.1/docs/Text-Pandoc-Definition.html), the XML conversion is fairly straightforward.
These are the main rules:
- `Str` inlines are usually converted to plain, UTF-8 text (see below for exceptions)
- `Space` inlines are usually converted to " " chars (see below for exceptions)
- every `Block` and `Inline` becomes an element with the same name and the same capitalization:
a `Para` Block becomes a `<Para>` element, an `Emph` Inline becomes an `<Emph>` element,
and so on;
- the root element is `<Pandoc>` and it has a `api-version` attribute, whose value
is a string of comma-separated integer numbers; it matches the `pandoc-api-version`
field of the `json` format;
- the root `<Pandoc>` element has only two children: `<meta>` and `<blocks>`
(lowercase, as in `json` format);
- blocks and inlines with an `Attr` are HTM-like, and they have:
- the `id` attribute for the identifier
- the `class` attribute, a string of space-separated classes
- the other attributes of `Attr`, without any prefix (so no `data-` prefix, instead of HTML)
- attributes are in lower (kebab) case:
- `level` in Header
- `start`, `number-style`, `number-delim` in OrderedList;
style and delimiter values are capitalized exactly as in `Text.Pandoc.Definition`;
- `format` in `RawBlock` and RawInline
- `quote-type` in Quoted (values are `SingleQuote` and `DoubleQuote`)
- `math-type` in Math (values are `InlineMath` and `DisplayMath`)
- `title` and `src` in Image target
- `title` and `href` in Link target
- `alignment` and `col-width` in ColSpec (about `col-width` values, see below);
(alignment values are capitalized as in `Text.Pandoc.Definition`)
- `alignment`, `row-span` and `col-span` in Cell
- `row-head-columns` in TableBody
- `id`, `mode`, `note-num` and `hash` for Citation (about Cite elements, see below);
(`mode` values are capitalized as in `Text.Pandoc.Definition`)
The classes of items with an `Attr` are put in a `class` attribute,
so that you can style the XML with CSS.
## Str and Space elements
`Str` and `Space` usually result in text and normal " " spaces, but there are exceptions:
- `Str ""`, an empty string, is not suppressed; instead it is converted into a `<Str />` element;
- `Str "foo bar"`, a string containing a space, is converted as `<Str content="foo bar" />`;
- consecutive `Str` inlines, as in `[ ..., Str "foo", Str "bar", ... ]`,
are encoded as `foo<Str content="bar" />` to keep their individuality;
- consecutive `Space` inlines, as in `[ ..., Space, Space, ... ]`,
are encoded as `<Space count="2" />`
- `Space` inlines at the start or at the end of their container element
are always encoded with a `<Space />` element, instead of just a " "
These encodings are necessary to ensure 1:1 equivalence of the `xml` format with the AST,
or the `native` and `json` formats.
Since the ones above are corner cases, usually you should not see those `<Str />` and `<Space />`
elements in your documents.
## Added tags
Some other elements have been introduced to better structure the resulting XML.
Since they are not Pandoc Blocks or Inlines, or they have no constructor or type
in Pandoc's haskell code, they are kept lowercased.
### BulletList and OrderedList items
Items of those lists are embedded in `<item>` elements.
These snippets are from the `xml` version of `test/testsuite.native`:
```xml
<BulletList>
<item>
<Plain>asterisk 1</Plain>
</item>
<item>
<Plain>asterisk 2</Plain>
</item>
<item>
<Plain>asterisk 3</Plain>
</item>
</BulletList>
...
<OrderedList start="1" number-style="Decimal" number-delim="Period">
<item>
<Plain>First</Plain>
</item>
<item>
<Plain>Second</Plain>
</item>
<item>
<Plain>Third</Plain>
</item>
</OrderedList>
```
### DefinitionList items
Definition lists have `<item>` elements.
Each `<item>` term has only one `<term>` child element,
and one or more `<def>` children elements.
This snippet is from the `xml` version of `test/testsuite.native`:
```xml
<DefinitionList>
<item>
<term>apple</term>
<def>
<Plain>red fruit</Plain>
</def>
</item>
<item>
<term>orange</term>
<def>
<Plain>orange fruit</Plain>
</def>
</item>
<item>
<term>banana</term>
<def>
<Plain>yellow fruit</Plain>
</def>
</item>
</DefinitionList>
```
### Figure and Table captions
Figures and tables have a `<Caption>` child element,
which in turn may optionally have a `<ShortCaption>` child element.
This snippet is from the `xml` version of `test/testsuite.native`:
```xml
<Figure>
<Caption>
<Plain>lalune</Plain>
</Caption>
<Plain><Image src="lalune.jpg" title="Voyage dans la Lune">lalune</Image></Plain>
</Figure>
```
### Tables
A `<Table>` element has:
- a `<Caption>` child element;
- a `<colspecs>` child element, whose children are empty
`<ColSpec alignment="..." col-width="..." />` elements;
- a `<TableHead>` child element;
- one or more `<TableBody>` children elements, that in turn
have two children: `<header>` and `<body>`, whose children
are `<Row>` elements;
- a `<TableFoot>` child element.
This specification is debatable; I have these doubts:
- is it necessary to enclose the `<ColSpec>` elements in a `<colspecs>` element?
- to discriminate between header and data cells in table bodies,
there are the `row-head-columns` attribute, and the `<header>` and `<body>` children
of the `<TableBody>` element, but there's only one type of cell:
every cell is a `<Cell>` element
- the specs are a tradeoff between consistency with pandoc types and CSS compatibility;
this way bodies' header rows are easily stylable with CSS, while header columns are not
The `ColWidthDefault` value becomes a "0" value for the attribute `col-width`;
this way it's type-consistent with non-zero values, but I'm still doubtful whether to
leave its value as a "ColWidthDefault" string.
Here's an example from the `xml` version of `test/tables/planets.native`:
```xml
<Table>
<Caption>
<Para>Data about the planets of our solar system.</Para>
</Caption>
<colspecs>
<ColSpec col-width="0" alignment="AlignCenter" />
<ColSpec col-width="0" alignment="AlignCenter" />
<ColSpec col-width="0" alignment="AlignDefault" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignRight" />
<ColSpec col-width="0" alignment="AlignDefault" />
</colspecs>
<TableHead>
<Row>
<Cell col-span="2" row-span="1" alignment="AlignDefault" />
<Cell col-span="1" row-span="1" alignment="AlignDefault">
<Plain>Name</Plain>
</Cell>
<Cell col-span="1" row-span="1" alignment="AlignDefault">
<Plain>Mass (10^24kg)</Plain>
</Cell>
...
</Row>
</TableHead>
<TableBody row-head-columns="3">
<header />
<body>
<Row>
<Cell col-span="2" row-span="4" alignment="AlignDefault">
<Plain>Terrestrial planets</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>Mercury</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>0.330</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>4,879</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>5427</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>3.7</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>4222.6</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>57.9</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>167</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>0</Plain>
</Cell>
<Cell alignment="AlignDefault">
<Plain>Closest to the Sun</Plain>
</Cell>
</Row>
...
</body>
</TableBody>
<TableFoot />
</Table>
```
### Metadata and MetaMap entries
Metadata entries are meta values (`MetaBool`, `MetaString`, `MetaInlines`, `MetaBlocks`,
`MetaList` and `MetaMap` elements) inside `<entry>` elements.
The `<meta>` and the `<MetaMap>` elements have the same children elements (`<entry>`),
which have a `key` attribute.
`<MetaInlines>`, `<MetaBlocks>`, `<MetaList>` and `<MetaMap>` elements
all have children elements.
`<MetaString>` elements have only text.
`<MetaBool>` elements are empty, they can be either `<MetaBool value="true" />`
or `<MetaBool value="false" />`.
This snippet is from the `xml` version of `test/testsuite.native`:
```xml
<meta>
<entry key="author">
<MetaList>
<MetaInlines>John MacFarlane</MetaInlines>
<MetaInlines>Anonymous</MetaInlines>
</MetaList>
</entry>
<entry key="date">
<MetaInlines>July 17, 2006</MetaInlines>
</entry>
<entry key="title">
<MetaInlines>Pandoc Test Suite</MetaInlines>
</entry>
</meta>
```
### Cite elements
`Cite` inlines are modeled with `<Cite>` elements, whose first child
is a `<citations>` element, that have only `<Citation>` children elements.
`<Citation>` elements are empty, unless they have a prefix and/or a suffix.
Here's an example from the `xml` version of `test/markdown-citations.native`:
```xml
<Para><Cite><citations>
<Citation note-num="3" mode="AuthorInText" id="item1" hash="0" />
</citations>@item1</Cite> says blah.</Para>
<Para><Cite><citations>
<Citation note-num="4" mode="AuthorInText" id="item1" hash="0">
<suffix>p. 30</suffix>
</Citation>
</citations>@item1 [p. 30]</Cite> says blah.</Para>
<Para>A citation group <Cite><citations>
<Citation note-num="8" mode="NormalCitation" id="item1" hash="0">
<prefix>see</prefix>
<suffix> chap. 3</suffix>
</Citation>
<Citation note-num="8" mode="NormalCitation" id="пункт3" hash="0">
<prefix>also</prefix>
<suffix> p. 34-35</suffix>
</Citation>
</citations>[see @item1 chap. 3; also @пункт3 p. 34-35]</Cite>.</Para>
```
+4
View File
@@ -614,6 +614,7 @@ library
Text.Pandoc.Readers.Pod,
Text.Pandoc.Writers,
Text.Pandoc.Writers.Native,
Text.Pandoc.Writers.XML,
Text.Pandoc.Writers.DocBook,
Text.Pandoc.Writers.JATS,
Text.Pandoc.Writers.OPML,
@@ -744,6 +745,7 @@ library
Text.Pandoc.Readers.Metadata,
Text.Pandoc.Readers.Roff,
Text.Pandoc.Readers.Roff.Escape,
Text.Pandoc.Readers.XML,
Text.Pandoc.Writers.Docx.OpenXML,
Text.Pandoc.Writers.Docx.StyleMap,
Text.Pandoc.Writers.Docx.Table,
@@ -769,6 +771,7 @@ library
Text.Pandoc.Char,
Text.Pandoc.TeX,
Text.Pandoc.URI,
Text.Pandoc.XMLFormat,
Text.Pandoc.CSS,
Text.Pandoc.CSV,
Text.Pandoc.RoffChar,
@@ -815,6 +818,7 @@ test-suite test-pandoc
Tests.Helpers
Tests.Shared
Tests.MediaBag
Tests.XML
Tests.Readers.LaTeX
Tests.Readers.HTML
Tests.Readers.JATS
+3
View File
@@ -65,6 +65,7 @@ module Text.Pandoc.Readers
, readTypst
, readDjot
, readPod
, readXML
-- * Miscellaneous
, getReader
, getDefaultExtensions
@@ -118,6 +119,7 @@ import Text.Pandoc.Readers.RIS
import Text.Pandoc.Readers.RTF
import Text.Pandoc.Readers.Typst
import Text.Pandoc.Readers.Djot
import Text.Pandoc.Readers.XML
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Sources (ToSources(..), sourcesToText)
@@ -174,6 +176,7 @@ readers = [("native" , TextReader readNative)
,("djot" , TextReader readDjot)
,("mdoc" , TextReader readMdoc)
,("pod" , TextReader readPod)
,("xml" , TextReader readXML)
]
-- | Retrieve reader, extensions based on format spec (format+extensions).
+540
View File
@@ -0,0 +1,540 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Text.Pandoc.Readers.XML
-- Copyright : Copyright (C) 2025- Massimiliano Farinella and John MacFarlane
-- License : GNU GPL, version 2 or above
--
-- Maintainer : Massimiliano Farinella <massifrg@gmail.com>
-- Stability : WIP
-- Portability : portable
--
-- Conversion of (Pandoc specific) xml to 'Pandoc' document.
module Text.Pandoc.Readers.XML (readXML) where
import Control.Monad (msum)
import Control.Monad.Except (throwError)
import Control.Monad.State.Strict (StateT (runStateT), modify)
import Data.Char (isSpace)
import Data.Default (Default (..))
import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
import qualified Data.Set as S (Set, fromList, member)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Lazy (fromStrict)
import Data.Version (Version, makeVersion)
import Text.Pandoc.Builder
import Text.Pandoc.Class.PandocMonad
import Text.Pandoc.Error (PandocError (..))
import Text.Pandoc.Options
import Text.Pandoc.Parsing (ToSources, toSources)
import Text.Pandoc.Sources (sourcesToText)
import Text.Pandoc.Version (pandocVersion)
import Text.Pandoc.XML (lookupEntity)
import Text.Pandoc.XML.Light
import Text.Pandoc.XMLFormat
import Text.Read (readMaybe)
-- TODO: use xmlPath state to give better context when an error occurs
type XMLReader m = StateT XMLReaderState m
data XMLReaderState = XMLReaderState
{ xmlApiVersion :: Version,
xmlMeta :: Meta,
xmlContent :: [Content],
xmlPath :: [Text]
}
deriving (Show)
instance Default XMLReaderState where
def =
XMLReaderState
{ xmlApiVersion = pandocVersion,
xmlMeta = mempty,
xmlContent = [],
xmlPath = ["root"]
}
readXML :: (PandocMonad m, ToSources a) => ReaderOptions -> a -> m Pandoc
readXML _ inp = do
let sources = toSources inp
tree <-
either (throwError . PandocXMLError "") return $
parseXMLContents (fromStrict . sourcesToText $ sources)
(bs, st') <- flip runStateT (def {xmlContent = tree}) $ mapM parseBlock tree
let blockList = toList $ concatMany bs
return $ Pandoc (xmlMeta st') blockList
concatMany :: [Many a] -> Many a
concatMany = Many . mconcat . map unMany
parseBlocks :: (PandocMonad m) => [Content] -> XMLReader m Blocks
parseBlocks contents = concatMany <$> mapM parseBlock contents
getBlocks :: (PandocMonad m) => Element -> XMLReader m Blocks
getBlocks e = parseBlocks (elContent e)
elementName :: Element -> Text
elementName e = qName $ elName e
attrValue :: Text -> Element -> Text
attrValue attr =
fromMaybe "" . maybeAttrValue attr
maybeAttrValue :: Text -> Element -> Maybe Text
maybeAttrValue attr elt =
lookupAttrBy (\x -> qName x == attr) (elAttribs elt)
parseBlock :: (PandocMonad m) => Content -> XMLReader m Blocks
parseBlock (Text (CData CDataRaw _ _)) = return mempty -- DOCTYPE
parseBlock (Text (CData _ s _)) =
if T.all isSpace s
then return mempty
else do
throwError $ PandocXMLError "" "non-space characters out of inline context"
parseBlock (CRef x) = do
throwError $ PandocXMLError "" ("reference \"" <> x <> "\" out of inline context")
parseBlock (Elem e) = do
let name = elementName e
in case (name) of
"Pandoc" -> parsePandoc
"?xml" -> return mempty
"blocks" -> getBlocks e
"meta" ->
let entry_els = childrenNamed tgNameMetaMapEntry e
in do
entries <- catMaybes <$> mapM parseMetaMapEntry entry_els
mapM_ (uncurry addMeta) entries
return mempty
"Para" -> para <$> getInlines (elContent e)
"Plain" -> do
ils <- getInlines (elContent e)
return $ singleton . Plain . toList $ ils
"Header" -> (headerWith attr level) <$> getInlines (elContent e)
where
level = textToInt (attrValue atNameLevel e) 1
attr = filterAttrAttributes [atNameLevel] $ attrFromElement e
"HorizontalRule" -> return horizontalRule
"BlockQuote" -> do
contents <- getBlocks e
return $ blockQuote contents
"Div" -> do
contents <- getBlocks e
return $ divWith (attrFromElement e) contents
"BulletList" -> do
items <- getListItems e
return $ bulletList items
"OrderedList" -> do
items <- getListItems e
return $ orderedListWith (getListAttributes e) items
"DefinitionList" -> do
let items_contents = getContentsOfElements (isElementNamed tgNameDefListItem) (elContent e)
items <- mapM parseDefinitionListItem items_contents
return $ definitionList items
"Figure" -> do
let attr = attrFromElement e
(maybe_caption_el, contents) = partitionFirstChildNamed "Caption" $ elContent e
figure_caption <- case (maybe_caption_el) of
Just (caption_el) -> parseCaption $ elContent caption_el
Nothing -> pure emptyCaption
blocks <- parseBlocks contents
return $ figureWith attr figure_caption blocks
"CodeBlock" -> do
let attr = attrFromElement e
return $ codeBlockWith attr $ strContentRecursive e
"RawBlock" -> do
let format = (attrValue atNameFormat e)
return $ rawBlock format $ strContentRecursive e
"LineBlock" -> do
lins <- mapM getInlines (contentsOfChildren tgNameLineItem (elContent e))
return $ lineBlock lins
"Table" -> do
-- TODO: check unexpected items
let attr = attrFromElement e
(maybe_caption_el, after_caption) = partitionFirstChildNamed "Caption" $ elContent e
children = elementsWithNames (S.fromList [tgNameColspecs, "TableHead", "TableBody", "TableFoot"]) after_caption
is_element tag el = tag == elementName el
colspecs <- getColspecs $ L.find (is_element tgNameColspecs) children
tbs <- getTableBodies $ filter (is_element "TableBody") children
th <- getTableHead $ L.find (is_element "TableHead") children
tf <- getTableFoot $ L.find (is_element "TableFoot") children
capt <- parseMaybeCaptionElement maybe_caption_el
case colspecs of
Nothing -> return mempty
Just cs -> return $ fromList [Table attr capt cs th tbs tf]
_ -> do
throwError $ PandocXMLError "" ("unexpected element \"" <> name <> "\" in blocks context")
where
parsePandoc = do
let version = maybeAttrValue atNameApiVersion e
apiversion = case (version) of
Just (v) -> makeVersion $ map (read . T.unpack) $ T.splitOn "," v
Nothing -> pandocVersion
in modify $ \st -> st {xmlApiVersion = apiversion}
getBlocks e
getListItems :: (PandocMonad m) => Element -> XMLReader m [Blocks]
getListItems e =
let items_els = childrenNamed tgNameListItem e
in do
mapM getBlocks items_els
getContentsOfElements :: (Content -> Bool) -> [Content] -> [[Content]]
getContentsOfElements filter_element contents = mapMaybe element_contents $ filter filter_element contents
where
element_contents :: Content -> Maybe [Content]
element_contents c = case (c) of
Elem e -> Just (elContent e)
_ -> Nothing
strContentRecursive :: Element -> Text
strContentRecursive =
strContent
. (\e' -> e' {elContent = map elementToStr $ elContent e'})
elementToStr :: Content -> Content
elementToStr (Elem e') = Text $ CData CDataText (strContentRecursive e') Nothing
elementToStr x = x
textToInt :: Text -> Int -> Int
textToInt t deflt =
let safe_to_int :: Text -> Maybe Int
safe_to_int s = readMaybe $ T.unpack s
in case (safe_to_int t) of
Nothing -> deflt
Just (n) -> n
parseInline :: (PandocMonad m) => Content -> XMLReader m Inlines
parseInline (Text (CData _ s _)) =
return $ text s
parseInline (CRef ref) =
return $
maybe (text $ T.toUpper ref) text $
lookupEntity ref
parseInline (Elem e) =
let name = elementName e
in case (name) of
"Space" ->
let count = textToInt (attrValue atNameSpaceCount e) 1
in return $ fromList $ replicate count Space
"Str" -> return $ fromList [Str $ attrValue atNameStrContent e]
"Emph" -> innerInlines emph
"Strong" -> innerInlines strong
"Strikeout" -> innerInlines strikeout
"Subscript" -> innerInlines subscript
"Superscript" -> innerInlines superscript
"Underline" -> innerInlines underline
"SoftBreak" -> return softbreak
"LineBreak" -> return linebreak
"SmallCaps" -> innerInlines smallcaps
"Quoted" -> case (attrValue atNameQuoteType e) of
"SingleQuote" -> innerInlines singleQuoted
_ -> innerInlines doubleQuoted
"Math" -> case (attrValue atNameMathType e) of
"DisplayMath" -> pure $ displayMath $ strContentRecursive e
_ -> pure $ math $ strContentRecursive e
"Span" -> innerInlines $ spanWith (attrFromElement e)
"Code" -> do
let attr = attrFromElement e
return $ codeWith attr $ strContentRecursive e
"Link" -> innerInlines $ linkWith attr url title
where
url = attrValue atNameLinkUrl e
title = attrValue atNameTitle e
attr = filterAttrAttributes [atNameLinkUrl, atNameTitle] $ attrFromElement e
"Image" -> innerInlines $ imageWith attr url title
where
url = attrValue atNameImageUrl e
title = attrValue atNameTitle e
attr = filterAttrAttributes [atNameImageUrl, atNameTitle] $ attrFromElement e
"RawInline" -> do
let format = (attrValue atNameFormat e)
return $ rawInline format $ strContentRecursive e
"Note" -> do
contents <- getBlocks e
return $ note contents
"Cite" ->
let (maybe_citations_el, contents) = partitionFirstChildNamed tgNameCitations $ elContent e
in case (maybe_citations_el) of
Just citations_el -> do
citations <- parseCitations $ elContent citations_el
(innerInlines' contents) $ cite citations
Nothing -> getInlines contents
_ -> do
throwError $ PandocXMLError "" ("unexpected element \"" <> name <> "\" in inline context")
where
innerInlines' contents f =
f . concatMany
<$> mapM parseInline contents
innerInlines f = innerInlines' (elContent e) f
getInlines :: (PandocMonad m) => [Content] -> XMLReader m Inlines
getInlines contents = concatMany <$> mapM parseInline contents
getListAttributes :: Element -> ListAttributes
getListAttributes e = (start, style, delim)
where
start = textToInt (attrValue atNameStart e) 1
style = case (attrValue atNameNumberStyle e) of
"Example" -> Example
"Decimal" -> Decimal
"LowerRoman" -> LowerRoman
"UpperRoman" -> UpperRoman
"LowerAlpha" -> LowerAlpha
"UpperAlpha" -> UpperAlpha
_ -> DefaultStyle
delim = case (attrValue atNameNumberDelim e) of
"Period" -> Period
"OneParen" -> OneParen
"TwoParens" -> TwoParens
_ -> DefaultDelim
contentsOfChildren :: Text -> [Content] -> [[Content]]
contentsOfChildren tag contents = mapMaybe childrenElementWithTag contents
where
childrenElementWithTag :: Content -> Maybe [Content]
childrenElementWithTag c = case (c) of
(Elem e) -> if tag == elementName e then Just (elContent e) else Nothing
_ -> Nothing
alignmentFromText :: Text -> Alignment
alignmentFromText t = case t of
"AlignLeft" -> AlignLeft
"AlignRight" -> AlignRight
"AlignCenter" -> AlignCenter
_ -> AlignDefault
getColWidth :: Text -> ColWidth
getColWidth txt = case reads (T.unpack txt) of
[(value, "")] -> if value == 0.0 then ColWidthDefault else ColWidth value
_ -> ColWidthDefault
getColspecs :: (PandocMonad m) => Maybe Element -> XMLReader m (Maybe [ColSpec])
getColspecs Nothing = pure Nothing
getColspecs (Just cs) = do
return $ Just $ map elementToColSpec (childrenNamed "ColSpec" cs)
where
elementToColSpec e = (alignmentFromText $ attrValue atNameAlignment e, getColWidth $ attrValue atNameColWidth e)
getTableBody :: (PandocMonad m) => Element -> XMLReader m (Maybe TableBody)
getTableBody body_el = do
let attr = filterAttrAttributes [atNameRowHeadColumns] $ attrFromElement body_el
bh = childrenNamed tgNameBodyHeader body_el
bb = childrenNamed tgNameBodyBody body_el
headcols = textToInt (attrValue atNameRowHeadColumns body_el) 0
hrows <- mconcat <$> mapM getRows bh
brows <- mconcat <$> mapM getRows bb
return $ Just $ TableBody attr (RowHeadColumns headcols) hrows brows
getTableBodies :: (PandocMonad m) => [Element] -> XMLReader m [TableBody]
getTableBodies body_elements = do
catMaybes <$> mapM getTableBody body_elements
getTableHead :: (PandocMonad m) => Maybe Element -> XMLReader m TableHead
getTableHead maybe_e = case maybe_e of
Just e -> do
let attr = attrFromElement e
rows <- getRows e
return $ TableHead attr rows
Nothing -> return $ TableHead nullAttr []
getTableFoot :: (PandocMonad m) => Maybe Element -> XMLReader m TableFoot
getTableFoot maybe_e = case maybe_e of
Just e -> do
let attr = attrFromElement e
rows <- getRows e
return $ TableFoot attr rows
Nothing -> return $ TableFoot nullAttr []
getCell :: (PandocMonad m) => Element -> XMLReader m Cell
getCell c = do
let alignment = alignmentFromText $ attrValue atNameAlignment c
rowspan = RowSpan $ textToInt (attrValue atNameRowspan c) 1
colspan = ColSpan $ textToInt (attrValue atNameColspan c) 1
attr = filterAttrAttributes [atNameAlignment, atNameRowspan, atNameColspan] $ attrFromElement c
blocks <- getBlocks c
return $ Cell attr alignment rowspan colspan (toList blocks)
getRows :: (PandocMonad m) => Element -> XMLReader m [Row]
getRows e = mapM getRow $ childrenNamed "Row" e
where
getRow r = do
cells <- mapM getCell (childrenNamed "Cell" r)
return $ Row (attrFromElement r) cells
parseCitations :: (PandocMonad m) => [Content] -> XMLReader m [Citation]
parseCitations contents = do
maybecitations <- mapM getCitation contents
return $ catMaybes maybecitations
where
getCitation :: (PandocMonad m) => Content -> XMLReader m (Maybe Citation)
getCitation content = case (content) of
(Elem e) ->
if qName (elName e) == "Citation"
then do
p <- inlinesOfChildrenNamed tgNameCitationPrefix e
s <- inlinesOfChildrenNamed tgNameCitationSuffix e
return $
Just
( Citation
{ citationId = attrValue "id" e,
citationPrefix = toList p,
citationSuffix = toList s,
citationMode = case (attrValue atNameCitationMode e) of
"AuthorInText" -> AuthorInText
"SuppressAuthor" -> SuppressAuthor
_ -> NormalCitation,
citationNoteNum = textToInt (attrValue atNameCitationNoteNum e) 0,
citationHash = textToInt (attrValue atNameCitationHash e) 0
}
)
else do
return Nothing
_ -> do
return Nothing
where
inlinesOfChildrenNamed tag e = getInlines $ concatMap (\e' -> elContent e') (childrenNamed tag e)
parseMaybeCaptionElement :: (PandocMonad m) => Maybe Element -> XMLReader m Caption
parseMaybeCaptionElement Nothing = pure emptyCaption
parseMaybeCaptionElement (Just e) = parseCaption $ elContent e
parseCaption :: (PandocMonad m) => [Content] -> XMLReader m Caption
parseCaption contents =
let (maybe_shortcaption_el, caption_contents) = partitionFirstChildNamed tgNameShortCaption contents
in do
blocks <- parseBlocks caption_contents
case (maybe_shortcaption_el) of
Just shortcaption_el -> do
short_caption <- getInlines (elContent shortcaption_el)
return $ caption (Just $ toList short_caption) blocks
Nothing -> return $ caption Nothing blocks
parseDefinitionListItem :: (PandocMonad m) => [Content] -> XMLReader m (Inlines, [Blocks])
parseDefinitionListItem contents = do
let term_contents = getContentsOfElements (isElementNamed tgNameDefListTerm) contents
defs_elements = elementContents $ filter (isElementNamed tgNameDefListDef) contents
term_inlines <- getInlines (concat term_contents)
defs <- mapM getBlocks defs_elements
return (term_inlines, defs)
elementContents :: [Content] -> [Element]
elementContents contents = mapMaybe toElement contents
where
toElement :: Content -> Maybe Element
toElement (Elem e) = Just e
toElement _ = Nothing
isElementNamed :: Text -> Content -> Bool
isElementNamed t c = case (c) of
Elem e -> t == elementName e
_ -> False
childrenNamed :: Text -> Element -> [Element]
childrenNamed tag e = elementContents $ filter (isElementNamed tag) (elContent e)
elementsWithNames :: S.Set Text -> [Content] -> [Element]
elementsWithNames tags contents = mapMaybe isElementWithNameInSet contents
where
isElementWithNameInSet c = case (c) of
Elem el ->
if (elementName el) `S.member` tags
then Just el
else Nothing
_ -> Nothing
partitionFirstChildNamed :: Text -> [Content] -> (Maybe Element, [Content])
partitionFirstChildNamed tag contents = case (contents) of
(Text (CData _ s _) : rest) ->
if T.all isSpace s
then partitionFirstChildNamed tag rest
else (Nothing, contents)
(Elem e : rest) ->
if tag == elementName e
then (Just e, rest)
else (Nothing, contents)
_ -> (Nothing, contents)
type PandocAttr = (Text, [Text], [(Text, Text)])
filterAttributes :: S.Set Text -> [(Text, Text)] -> [(Text, Text)]
filterAttributes to_be_removed a = filter keep_attr a
where
keep_attr (k, _) = not (k `S.member` to_be_removed)
filterAttrAttributes :: [Text] -> PandocAttr -> PandocAttr
filterAttrAttributes to_be_removed (idn, classes, a) = (idn, classes, filtered)
where
filtered = filterAttributes (S.fromList to_be_removed) a
attrFromElement :: Element -> PandocAttr
attrFromElement e = filterAttrAttributes ["id", "class"] (idn, classes, attributes)
where
idn = attrValue "id" e
classes = T.words $ attrValue "class" e
attributes = map (\a -> (qName $ attrKey a, attrVal a)) $ elAttribs e
addMeta :: (PandocMonad m) => (ToMetaValue a) => Text -> a -> XMLReader m ()
addMeta field val = modify (setMeta field val)
instance HasMeta XMLReaderState where
setMeta field v s = s {xmlMeta = setMeta field v (xmlMeta s)}
deleteMeta field s = s {xmlMeta = deleteMeta field (xmlMeta s)}
parseMetaMapEntry :: (PandocMonad m) => Element -> XMLReader m (Maybe (Text, MetaValue))
parseMetaMapEntry e =
let key = attrValue atNameMetaMapEntryKey e
in case (key) of
"" -> pure Nothing
k -> do
maybe_value <- parseMetaMapEntryContents $ elContent e
case (maybe_value) of
Nothing -> return Nothing
Just v -> return $ Just (k, v)
parseMetaMapEntryContents :: (PandocMonad m) => [Content] -> XMLReader m (Maybe MetaValue)
parseMetaMapEntryContents cs = msum <$> mapM parseMeta cs
parseMeta :: (PandocMonad m) => Content -> XMLReader m (Maybe MetaValue)
parseMeta (Text (CData CDataRaw _ _)) = return Nothing
parseMeta (Text (CData _ s _)) =
if T.all isSpace s
then return Nothing
else do
throwError $ PandocXMLError "" "non-space characters out of inline context in metadata"
parseMeta (CRef x) =
throwError $ PandocXMLError "" ("reference \"" <> x <> "\" out of inline context")
parseMeta (Elem e) = do
let name = elementName e
in case (name) of
"MetaBool" -> case (attrValue atNameMetaBoolValue e) of
"true" -> return $ Just $ MetaBool True
_ -> return $ Just $ MetaBool False
"MetaString" -> pure Nothing
"MetaInlines" -> do
inlines <- getInlines (elContent e)
return $ Just $ MetaInlines $ toList inlines
"MetaBlocks" -> do
blocks <- getBlocks e
return $ Just $ MetaBlocks $ toList blocks
"MetaList" -> do
maybe_items <- mapM parseMeta $ elContent e
let items = catMaybes maybe_items
in -- TODO: report empty MetaList?
return $ Just $ MetaList items
"MetaMap" ->
let entry_els = childrenNamed tgNameMetaMapEntry e
in do
entries <- catMaybes <$> mapM parseMetaMapEntry entry_els
if null entries
then
-- TODO: report empty MetaMap
return Nothing
else return $ Just $ MetaMap $ M.fromList entries
_ -> do
throwError $ PandocXMLError "" ("unexpected element \"" <> name <> "\" in metadata")
+3
View File
@@ -76,6 +76,7 @@ module Text.Pandoc.Writers
, writeTexinfo
, writeTextile
, writeTypst
, writeXML
, writeXWiki
, writeZimWiki
, getWriter
@@ -128,6 +129,7 @@ import Text.Pandoc.Writers.TEI
import Text.Pandoc.Writers.Texinfo
import Text.Pandoc.Writers.Textile
import Text.Pandoc.Writers.Typst
import Text.Pandoc.Writers.XML
import Text.Pandoc.Writers.XWiki
import Text.Pandoc.Writers.ZimWiki
@@ -203,6 +205,7 @@ writers = [
,("chunkedhtml" , ByteStringWriter writeChunkedHTML)
,("djot" , TextWriter writeDjot)
,("ansi" , TextWriter writeANSI)
,("xml" , TextWriter writeXML)
]
-- | Retrieve writer, extensions based on formatSpec (format+extensions).
+365
View File
@@ -0,0 +1,365 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Text.Pandoc.Writers.XML
-- Copyright : Copyright (C) 2025- Massimiliano Farinella and John MacFarlane
-- License : GNU GPL, version 2 or above
--
-- Maintainer : Massimiliano Farinella <massifrg@gmail.com>
-- Stability : WIP
-- Portability : portable
--
-- Conversion of 'Pandoc' documents to (pandoc specific) xml markup.
module Text.Pandoc.Writers.XML (writeXML) where
import Data.Map (Map, toList)
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import Data.Version (versionBranch)
import Text.Pandoc.Class.PandocMonad (PandocMonad)
import Text.Pandoc.Definition
import Text.Pandoc.Options (WriterOptions (..))
import Text.Pandoc.XML.Light
import qualified Text.Pandoc.XML.Light as XML
import Text.Pandoc.XMLFormat
import Text.XML.Light (xml_header)
type PandocAttr = Text.Pandoc.Definition.Attr
writeXML :: (PandocMonad m) => WriterOptions -> Pandoc -> m T.Text
writeXML _ doc = do
return $ pandocToXmlText doc
text_node :: T.Text -> Content
text_node text = Text (CData CDataText text Nothing)
emptyElement :: T.Text -> Element
emptyElement tag =
Element
{ elName = unqual tag,
elAttribs = [],
elContent = [],
elLine = Nothing
}
elementWithContents :: T.Text -> [Content] -> Element
elementWithContents tag contents =
Element
{ elName = unqual tag,
elAttribs = [],
elContent = contents,
elLine = Nothing
}
elementWithAttributes :: T.Text -> [XML.Attr] -> Element
elementWithAttributes tag attributes =
Element
{ elName = unqual tag,
elAttribs = attributes,
elContent = [],
elLine = Nothing
}
elementWithAttrAndContents :: T.Text -> PandocAttr -> [Content] -> Element
elementWithAttrAndContents tag attr contents = addAttrAttributes attr $ elementWithContents tag contents
asBlockOfInlines :: Element -> [Content]
asBlockOfInlines el = [Elem el, text_node "\n"]
asBlockOfBlocks :: Element -> [Content]
asBlockOfBlocks el = [Elem newline_before_first, newline]
where
newline = text_node "\n"
newline_before_first = if null (elContent el) then el else prependContents [newline] el
itemName :: (Show a) => a -> T.Text
itemName a = T.pack $ takeWhile (/= ' ') (show a)
intAsText :: Int -> T.Text
intAsText i = T.pack $ show i
itemAsEmptyElement :: (Show a) => a -> Element
itemAsEmptyElement item = emptyElement $ itemName item
pandocToXmlText :: Pandoc -> T.Text
pandocToXmlText (Pandoc (Meta meta) blocks) = with_header . with_blocks . with_meta . with_version $ el
where
el = prependContents [text_node "\n"] $ emptyElement "Pandoc"
with_version = addAttribute atNameApiVersion (T.intercalate "," $ map (T.pack . show) $ versionBranch pandocTypesVersion)
with_meta = appendContents (metaMapToXML meta "meta")
with_blocks = appendContents (asBlockOfBlocks $ elementWithContents "blocks" $ blocksToXML blocks)
with_header :: Element -> T.Text
with_header e = T.concat [T.pack xml_header, "\n", showElement e]
metaMapToXML :: Map T.Text MetaValue -> T.Text -> [Content]
metaMapToXML mmap tag = asBlockOfBlocks $ elementWithContents tag entries
where
entries = concatMap to_entry $ toList mmap
to_entry :: (T.Text, MetaValue) -> [Content]
to_entry (text, metavalue) = asBlockOfBlocks with_key
where
entry = elementWithContents tgNameMetaMapEntry $ metaValueToXML metavalue
with_key = addAttribute atNameMetaMapEntryKey text entry
metaValueToXML :: MetaValue -> [Content]
metaValueToXML value =
let name = itemName value
el = itemAsEmptyElement value
in case (value) of
MetaBool b -> asBlockOfInlines $ addAttribute atNameMetaBoolValue bool_value el
where
bool_value = if b then "true" else "false"
MetaString s -> asBlockOfInlines $ appendContents [text_node s] el
MetaInlines inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
MetaBlocks blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) el
MetaList items -> asBlockOfBlocks $ appendContents (concatMap metaValueToXML items) el
MetaMap mm -> metaMapToXML mm name
blocksToXML :: [Block] -> [Content]
blocksToXML blocks = concatMap blockToXML blocks
inlinesToXML :: [Inline] -> [Content]
inlinesToXML inlines = concatMap inlineContentToContents (ilsToIlsContent inlines [])
data InlineContent
= NormalInline Inline
| ElSpace Int
| ElStr T.Text
ilsToIlsContent :: [Inline] -> [InlineContent] -> [InlineContent]
ilsToIlsContent (Space : xs) [] = ilsToIlsContent xs [ElSpace 1]
ilsToIlsContent (Space : xs) (NormalInline Space : cs) = ilsToIlsContent xs (ElSpace 2 : cs)
ilsToIlsContent (Space : xs) (ElSpace n : cs) = ilsToIlsContent xs (ElSpace (n + 1) : cs)
-- empty Str are always encoded as <Str />
ilsToIlsContent (Str "" : xs) ilct = ilsToIlsContent xs (ElStr "" : ilct)
-- Str s1, Str s2 -> s1<Str content="s2">
ilsToIlsContent (Str s2 : xs) (NormalInline str1@(Str _) : ilct) = ilsToIlsContent xs (ElStr s2 : NormalInline str1 : ilct)
--
ilsToIlsContent (Str s : xs) ilct =
if T.any (== ' ') s
then ilsToIlsContent xs (ElStr s : ilct)
else ilsToIlsContent xs (NormalInline (Str s) : ilct)
ilsToIlsContent (x : xs) ilct = ilsToIlsContent xs (NormalInline x : ilct)
ilsToIlsContent [] ilct = reverse $ lastSpaceAsElem ilct
where
lastSpaceAsElem :: [InlineContent] -> [InlineContent]
lastSpaceAsElem (NormalInline Space : xs) = ElSpace 1 : xs
lastSpaceAsElem ilcts = ilcts
inlineContentToContents :: InlineContent -> [Content]
inlineContentToContents (NormalInline il) = inlineToXML il
inlineContentToContents (ElSpace 1) = [Elem $ emptyElement "Space"]
inlineContentToContents (ElSpace n) = [Elem $ addAttribute atNameSpaceCount (intAsText n) (emptyElement "Space")]
inlineContentToContents (ElStr "") = [Elem $ emptyElement "Str"]
inlineContentToContents (ElStr s) = [Elem $ addAttribute atNameStrContent s (emptyElement "Str")]
asContents :: Element -> [Content]
asContents el = [Elem el]
wrapBlocks :: T.Text -> [Block] -> [Content]
wrapBlocks tag blocks = asBlockOfBlocks $ elementWithContents tag $ blocksToXML blocks
wrapArrayOfBlocks :: T.Text -> [[Block]] -> [Content]
wrapArrayOfBlocks tag array = concatMap (wrapBlocks tag) array
-- wrapInlines :: T.Text -> [Inline] -> [Content]
-- wrapInlines tag inlines = asBlockOfInlines $ element_with_contents tag $ inlinesToXML inlines
blockToXML :: Block -> [Content]
blockToXML block =
let el = itemAsEmptyElement block
in case (block) of
Para inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
Header level (idn, cls, attrs) inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) with_attr
where
with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameLevel, intAsText level)]) el
Plain inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
Div attr blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) with_attr
where
with_attr = addAttrAttributes attr el
BulletList items -> asBlockOfBlocks $ appendContents (wrapArrayOfBlocks tgNameListItem items) el
OrderedList (start, style, delim) items -> asBlockOfBlocks $ with_contents . with_attrs $ el
where
with_attrs =
addAttributes
( validAttributes
[ (atNameStart, intAsText start),
(atNameNumberStyle, itemName style),
(atNameNumberDelim, itemName delim)
]
)
with_contents = appendContents (wrapArrayOfBlocks tgNameListItem items)
BlockQuote blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) el
HorizontalRule -> asBlockOfInlines el
CodeBlock attr text -> asBlockOfInlines $ with_contents . with_attr $ el
where
with_contents = appendContents [text_node text]
with_attr = addAttrAttributes attr
LineBlock lins -> asBlockOfBlocks $ appendContents (concatMap wrapInlines lins) el
where
wrapInlines inlines = asContents $ appendContents (inlinesToXML inlines) $ emptyElement tgNameLineItem
Table attr caption colspecs thead tbodies tfoot -> asBlockOfBlocks $ with_foot . with_bodies . with_head . with_colspecs . with_caption . with_attr $ el
where
with_attr = addAttrAttributes attr
with_caption = appendContents (captionToXML caption)
with_colspecs = appendContents (colSpecsToXML colspecs)
with_head = appendContents (tableHeadToXML thead)
with_bodies = appendContents (concatMap tableBodyToXML tbodies)
with_foot = appendContents (tableFootToXML tfoot)
Figure attr caption blocks -> asBlockOfBlocks $ with_contents . with_caption . with_attr $ el
where
with_attr = addAttrAttributes attr
with_caption = appendContents (captionToXML caption)
with_contents = appendContents (blocksToXML blocks)
RawBlock (Format format) text -> asContents $ appendContents [text_node text] raw
where
raw = addAttribute atNameFormat format el
DefinitionList items -> asBlockOfBlocks $ appendContents (map definitionListItemToXML items) el
inlineToXML :: Inline -> [Content]
inlineToXML inline =
let el = itemAsEmptyElement inline
wrapInlines inlines = asContents $ appendContents (inlinesToXML inlines) el
in case (inline) of
Space -> [text_node " "]
Str s -> [text_node s]
Emph inlines -> wrapInlines inlines
Strong inlines -> wrapInlines inlines
Quoted quote_type inlines -> asContents $ appendContents (inlinesToXML inlines) quoted
where
quoted = addAttribute atNameQuoteType (itemName quote_type) el
Underline inlines -> wrapInlines inlines
Strikeout inlines -> wrapInlines inlines
SmallCaps inlines -> wrapInlines inlines
Superscript inlines -> wrapInlines inlines
Subscript inlines -> wrapInlines inlines
SoftBreak -> asContents el
LineBreak -> asContents el
Span attr inlines -> asContents $ appendContents (inlinesToXML inlines) with_attr
where
with_attr = addAttrAttributes attr el
Link (idn, cls, attrs) inlines (url, title) -> asContents $ appendContents (inlinesToXML inlines) with_attr
where
with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameLinkUrl, url), (atNameTitle, title)]) el
Image (idn, cls, attrs) inlines (url, title) -> asContents $ appendContents (inlinesToXML inlines) with_attr
where
with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameImageUrl, url), (atNameTitle, title)]) el
RawInline (Format format) text -> asContents $ appendContents [text_node text] raw
where
raw = addAttribute atNameFormat format el
Math math_type text -> asContents $ appendContents [text_node text] math
where
math = addAttribute atNameMathType (itemName math_type) el
Code attr text -> asContents $ appendContents [text_node text] with_attr
where
with_attr = addAttrAttributes attr el
Note blocks -> asContents $ appendContents (blocksToXML blocks) el
Cite citations inlines -> asContents $ appendContents (inlinesToXML inlines) with_citations
where
with_citations = addCitations citations el
-- TODO: don't let an attribute overwrite id or class
maybeAttribute :: (T.Text, T.Text) -> Maybe XML.Attr
maybeAttribute (_, "") = Nothing
maybeAttribute ("", _) = Nothing
maybeAttribute (name, value) = Just $ XML.Attr (unqual name) value
validAttributes :: [(T.Text, T.Text)] -> [XML.Attr]
validAttributes pairs = mapMaybe maybeAttribute pairs
appendContents :: [Content] -> Element -> Element
appendContents newContents el = el {elContent = (elContent el) ++ newContents}
prependContents :: [Content] -> Element -> Element
prependContents newContents el = el {elContent = newContents ++ (elContent el)}
addAttributes :: [XML.Attr] -> Element -> Element
addAttributes newAttrs el = el {elAttribs = newAttrs ++ elAttribs el}
addAttribute :: T.Text -> T.Text -> Element -> Element
addAttribute attr_name attr_value el = el {elAttribs = new_attr : elAttribs el}
where
new_attr = XML.Attr (unqual attr_name) attr_value
addAttrAttributes :: PandocAttr -> Element -> Element
addAttrAttributes (identifier, classes, attributes) el = addAttributes attrs' el
where
attrs' = mapMaybe maybeAttribute (("id", identifier) : ("class", T.intercalate " " classes) : attributes)
addCitations :: [Citation] -> Element -> Element
addCitations citations el = appendContents [Elem $ elementWithContents tgNameCitations $ (text_node "\n") : concatMap citation_to_elem citations] el
where
citation_to_elem :: Citation -> [Content]
citation_to_elem citation = asBlockOfInlines with_suffix
where
cit_elem = elementWithAttributes (itemName citation) attrs
prefix = citationPrefix citation
suffix = citationSuffix citation
with_prefix =
if null prefix
then cit_elem
else appendContents [Elem $ elementWithContents tgNameCitationPrefix $ inlinesToXML prefix] cit_elem
with_suffix =
if null suffix
then with_prefix
else appendContents [Elem $ elementWithContents tgNameCitationSuffix $ inlinesToXML suffix] with_prefix
attrs =
map
(\(n, v) -> XML.Attr (unqual n) v)
[ ("id", citationId citation),
(atNameCitationMode, T.pack $ show $ citationMode citation),
(atNameCitationNoteNum, intAsText $ citationNoteNum citation),
(atNameCitationHash, intAsText $ citationHash citation)
]
definitionListItemToXML :: ([Inline], [[Block]]) -> Content
definitionListItemToXML (inlines, defs) = Elem $ elementWithContents tgNameDefListItem $ term ++ wrapArrayOfBlocks tgNameDefListDef defs
where
term = asBlockOfInlines $ appendContents (inlinesToXML inlines) $ emptyElement tgNameDefListTerm
captionToXML :: Caption -> [Content]
captionToXML (Caption short blocks) = asBlockOfBlocks with_short_caption
where
el = elementWithContents "Caption" $ blocksToXML blocks
with_short_caption = case (short) of
Just inlines -> prependContents (asBlockOfInlines $ elementWithContents tgNameShortCaption $ inlinesToXML inlines) el
_ -> el
colSpecToXML :: (Alignment, ColWidth) -> [Content]
colSpecToXML (align, cw) = asBlockOfInlines colspec
where
colspec = elementWithAttributes "ColSpec" $ validAttributes [(atNameAlignment, itemName align), (atNameColWidth, colwidth)]
colwidth = case (cw) of
ColWidth d -> T.pack $ show d
ColWidthDefault -> "0"
colSpecsToXML :: [(Alignment, ColWidth)] -> [Content]
colSpecsToXML colspecs = asBlockOfBlocks $ elementWithContents tgNameColspecs $ concatMap colSpecToXML colspecs
tableHeadToXML :: TableHead -> [Content]
tableHeadToXML (TableHead attr rows) = asBlockOfBlocks $ elementWithAttrAndContents "TableHead" attr $ concatMap rowToXML rows
tableBodyToXML :: TableBody -> [Content]
tableBodyToXML (TableBody (idn, cls, attrs) (RowHeadColumns headcols) hrows brows) = asBlockOfBlocks $ elementWithAttrAndContents "TableBody" attr children
where
attr = (idn, cls, (atNameRowHeadColumns, intAsText headcols) : attrs)
header_rows = asBlockOfBlocks $ elementWithContents tgNameBodyHeader $ concatMap rowToXML hrows
body_rows = asBlockOfBlocks $ elementWithContents tgNameBodyBody $ concatMap rowToXML brows
children = header_rows ++ body_rows
tableFootToXML :: TableFoot -> [Content]
tableFootToXML (TableFoot attr rows) = asBlockOfBlocks $ elementWithAttrAndContents "TableFoot" attr $ concatMap rowToXML rows
rowToXML :: Row -> [Content]
rowToXML (Row attr cells) = asBlockOfBlocks $ elementWithAttrAndContents "Row" attr $ concatMap cellToXML cells
cellToXML :: Cell -> [Content]
cellToXML (Cell (idn, cls, attrs) alignment (RowSpan rowspan) (ColSpan colspan) blocks) = asBlockOfBlocks $ elementWithAttrAndContents "Cell" attr $ blocksToXML blocks
where
with_alignment a = (atNameAlignment, itemName alignment) : a
with_rowspan a = if rowspan > 1 then (atNameRowspan, intAsText rowspan) : a else a
with_colspan a = if colspan > 1 then (atNameColspan, intAsText colspan) : a else a
attrs' = (with_colspan . with_rowspan . with_alignment) attrs
attr = (idn, cls, attrs')
+188
View File
@@ -0,0 +1,188 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Text.Pandoc.XMLFormat
( atNameAlignment,
atNameApiVersion,
atNameCitationHash,
atNameCitationMode,
atNameCitationNoteNum,
atNameColspan,
atNameColWidth,
atNameFormat,
atNameImageUrl,
atNameLevel,
atNameLinkUrl,
atNameMathType,
atNameMetaBoolValue,
atNameMetaMapEntryKey,
atNameNumberDelim,
atNameNumberStyle,
atNameQuoteType,
atNameRowHeadColumns,
atNameRowspan,
atNameSpaceCount,
atNameStart,
atNameStrContent,
atNameTitle,
tgNameBodyBody,
tgNameBodyHeader,
tgNameCitations,
tgNameCitationPrefix,
tgNameCitationSuffix,
tgNameColspecs,
tgNameDefListDef,
tgNameDefListItem,
tgNameDefListTerm,
tgNameLineItem,
tgNameListItem,
tgNameMetaMapEntry,
tgNameShortCaption,
)
where
import Data.Text (Text)
-- the attribute carrying the API version of pandoc types in the main Pandoc element
atNameApiVersion :: Text
atNameApiVersion = "api-version"
-- the element of a <meta> or <MetaMap> entry
tgNameMetaMapEntry :: Text
tgNameMetaMapEntry = "entry"
-- the attribute carrying the key name of a <meta> or <MetaMap> entry
atNameMetaMapEntryKey :: Text
atNameMetaMapEntryKey = "key"
-- the attribute carrying the boolean value ("true" or "false") of a MetaBool
atNameMetaBoolValue :: Text
atNameMetaBoolValue = "value"
-- level of a Header
atNameLevel :: Text
atNameLevel = "level"
-- start number of an OrderedList
atNameStart :: Text
atNameStart = "start"
-- number delimiter of an OrderedList
atNameNumberDelim :: Text
atNameNumberDelim = "number-delim"
-- number style of an OrderedList
atNameNumberStyle :: Text
atNameNumberStyle = "number-style"
-- target title in Image and Link
atNameTitle :: Text
atNameTitle = "title"
-- target url in Image
atNameImageUrl :: Text
atNameImageUrl = "src"
-- target url in Link
atNameLinkUrl :: Text
atNameLinkUrl = "href"
-- QuoteType of a Quoted
atNameQuoteType :: Text
atNameQuoteType = "quote-type"
-- MathType of a Math
atNameMathType :: Text
atNameMathType = "math-type"
-- format of a RawInline or a RawBlock
atNameFormat :: Text
atNameFormat = "format"
-- alignment attribute in a ColSpec or in a Cell
atNameAlignment :: Text
atNameAlignment = "alignment"
-- ColWidth attribute in a ColSpec
atNameColWidth :: Text
atNameColWidth = "col-width"
-- RowHeadColumns attribute in a TableBody
atNameRowHeadColumns :: Text
atNameRowHeadColumns = "row-head-columns"
-- RowSpan attribute in a Cell
atNameRowspan :: Text
atNameRowspan = "row-span"
-- ColSpan attribute in a Cell
atNameColspan :: Text
atNameColspan = "col-span"
-- the citationMode of a Citation
atNameCitationMode :: Text
atNameCitationMode = "mode"
-- the citationHash of a Citation
atNameCitationHash :: Text
atNameCitationHash = "hash"
-- the citationNoteNum of a Citation
atNameCitationNoteNum :: Text
atNameCitationNoteNum = "note-num"
-- the number of consecutive spaces of the <Space> element
atNameSpaceCount :: Text
atNameSpaceCount = "count"
-- the content of the <Str> element
atNameStrContent :: Text
atNameStrContent = "content"
-- container of Citation elements in Cite inlines
tgNameCitations :: Text
tgNameCitations = "citations"
-- element around the prefix inlines of a Citation
tgNameCitationPrefix :: Text
tgNameCitationPrefix = "prefix"
-- element around the suffix inlines of a Citation
tgNameCitationSuffix :: Text
tgNameCitationSuffix = "suffix"
-- list item for BulletList and OrderedList
tgNameListItem :: Text
tgNameListItem = "item"
-- list item for DefinitionList
tgNameDefListItem :: Text
tgNameDefListItem = "item"
-- element around the inlines of the term of a DefinitionList item
tgNameDefListTerm :: Text
tgNameDefListTerm = "term"
-- element around the blocks of a definition in a DefinitionList item
tgNameDefListDef :: Text
tgNameDefListDef = "def"
-- optional element of the ShortCaption
tgNameShortCaption :: Text
tgNameShortCaption = "ShortCaption"
-- element around the ColSpec of a Table
tgNameColspecs :: Text
tgNameColspecs = "colspecs"
-- element around the header rows of a TableBody
tgNameBodyHeader :: Text
tgNameBodyHeader = "header"
-- element around the body rows of a TableBody
tgNameBodyBody :: Text
tgNameBodyBody = "body"
-- element around the inlines of a line in a LineBlock
tgNameLineItem :: Text
tgNameLineItem = "line"
+28
View File
@@ -0,0 +1,28 @@
{-# LANGUAGE OverloadedStrings #-}
{- |
-- Module : Tests.XML
-- Copyright : Copyright (C) 2025- Massimiliano Farinella and John MacFarlane
-- License : GNU GPL, version 2 or above
--
-- Maintainer : Massimiliano Farinella <massifrg@gmail.com>
-- Stability : WIP
-- Portability : portable
Runs a roundtrip conversion of an AST trough the XML format:
- first from AST to XML (XML Writer),
- then back to AST (XML Reader),
- and checks that the two ASTs are the same
-}
module Tests.XML (tests) where
import Control.Monad ((>=>))
import Test.Tasty (TestTree)
import Test.Tasty.QuickCheck
import Tests.Helpers
import Text.Pandoc
import Text.Pandoc.Arbitrary ()
p_xml_roundtrip :: Pandoc -> Bool
p_xml_roundtrip d = d == purely (writeXML def {writerTemplate = Just mempty} >=> readXML def) d
tests :: [TestTree]
tests = [testProperty "p_xml_roundtrip" p_xml_roundtrip]
+2
View File
@@ -50,6 +50,7 @@ import qualified Tests.Writers.RST
import qualified Tests.Writers.AnnotatedTable
import qualified Tests.Writers.TEI
import qualified Tests.Writers.Markua
import qualified Tests.XML
import qualified Tests.MediaBag
import Text.Pandoc.Shared (inDirectory)
@@ -59,6 +60,7 @@ tests pandocPath = testGroup "pandoc tests"
, testGroup "Old" (Tests.Old.tests pandocPath)
, testGroup "Shared" Tests.Shared.tests
, testGroup "MediaBag" Tests.MediaBag.tests
, testGroup "XML" Tests.XML.tests
, testGroup "Writers"
[ testGroup "Native" Tests.Writers.Native.tests
, testGroup "ConTeXt" Tests.Writers.ConTeXt.tests
+181
View File
@@ -0,0 +1,181 @@
<!--
A DTD for Pandoc XML format.
Copyright : Copyright (C) 2025- Massimiliano Farinella
License : GNU GPL, version 2 or above
Maintainer : Massimiliano Farinella <massifrg@gmail.com>
This is a DTD for the XML representation of Pandoc AST.
It's an equivalent of native and JSON formats, but modeled as XML.
You can validate Pandoc XML documents with this DTD,
but there are some limitations:
- a Pandoc Attr can contain arbitrary attributes, but it looks like you can't tell
an element has arbitrary attributes in a DTD
- some Pandoc attributes have constraints that can't be specified with a DTD
The Relax-NG and XML Schema translations of this specification overcome
some of those limitations (see pandoc-xml.rng and pandoc-xml.xsd).
-->
<!ELEMENT Pandoc (meta, blocks)>
<!ATTLIST Pandoc api-version CDATA #REQUIRED>
<!ENTITY % block "Para | Plain | Header | Div | BlockQuote | HorizontalRule | BulletList | OrderedList | DefinitionList | Table | Figure | LineBlock | CodeBlock | RawBlock">
<!ENTITY % inline_element "Str | Space | Emph | Strong | Underline | Strikeout | Superscript | Subscript | SmallCaps | Quoted | Cite | Code | SoftBreak | LineBreak | Math | RawInline | Link | Image | Note | Span">
<!ENTITY % inline "#PCDATA | %inline_element;">
<!ENTITY % attr "id ID #IMPLIED class CDATA #IMPLIED">
<!ENTITY % metavalue "MetaMap | MetaList | MetaBool | MetaString | MetaInlines | MetaBlocks">
<!ELEMENT meta (entry*)>
<!ELEMENT MetaMap (entry*)>
<!ELEMENT entry (%metavalue;)*>
<!ATTLIST entry key CDATA #REQUIRED>
<!ELEMENT MetaList (%metavalue;)*>
<!ELEMENT MetaBool EMPTY>
<!ATTLIST MetaBool value (true | false) #REQUIRED>
<!ELEMENT MetaString (#PCDATA)>
<!ELEMENT MetaInlines (%inline;)*>
<!ELEMENT MetaBlocks (%block;)*>
<!ELEMENT blocks (%block;)*>
<!ELEMENT Para (%inline;)*>
<!ELEMENT Plain (%inline;)*>
<!ELEMENT Header (%inline;)*>
<!ATTLIST Header
level CDATA "1"
%attr;>
<!ELEMENT Div (%block;)* >
<!ATTLIST Div
custom-style CDATA #IMPLIED
%attr;>
<!ELEMENT BlockQuote (%block;)*>
<!ELEMENT HorizontalRule EMPTY>
<!ELEMENT BulletList (item)+>
<!ELEMENT OrderedList (item)+>
<!ATTLIST OrderedList
start CDATA "1"
number-style (DefaultStyle | Example | Decimal | LowerRoman | UpperRoman | LowerAlpha | UpperAlpha) "DefaultStyle"
number-delim (DefaultDelim | Period | OneParen | TwoParens) "DefaultDelim">
<!ELEMENT DefinitionList (item)+>
<!ELEMENT item ((%block;)*|(term,def+))>
<!ELEMENT term (%inline;)*>
<!ELEMENT def (%block;)*>
<!ELEMENT Table (Caption, colspecs, TableHead, TableBody+, TableFoot)>
<!ATTLIST Table
custom-style CDATA #IMPLIED
%attr;>
<!ELEMENT Caption (ShortCaption?, (%block;)*)>
<!ELEMENT ShortCaption (%inline;)*>
<!ELEMENT colspecs (ColSpec+)>
<!ELEMENT ColSpec EMPTY>
<!ATTLIST ColSpec
alignment (AlignLeft | AlignRight | AlignCenter | AlignDefault) "AlignDefault"
col-width CDATA "0">
<!ELEMENT TableHead (Row*)>
<!ATTLIST TableHead %attr;>
<!ELEMENT TableFoot (Row*)>
<!ATTLIST TableFoot %attr;>
<!ELEMENT TableBody (header, body)>
<!ATTLIST TableBody
row-head-columns CDATA "0"
%attr;>
<!ELEMENT header (Row*)>
<!ELEMENT body (Row*)>
<!ELEMENT Row (Cell*)>
<!ATTLIST Row %attr;>
<!ELEMENT Cell (%block;)*>
<!ATTLIST Cell
alignment (AlignLeft | AlignRight | AlignCenter | AlignDefault) "AlignDefault"
row-span CDATA "1"
col-span CDATA "1"
%attr; >
<!ELEMENT Figure (Caption,(%block;)*)>
<!ATTLIST Figure %attr;>
<!ELEMENT LineBlock (line+)>
<!ELEMENT line (%inline;)*>
<!ELEMENT CodeBlock (#PCDATA)>
<!ATTLIST CodeBlock %attr;>
<!ELEMENT RawBlock (#PCDATA)>
<!ATTLIST RawBlock format CDATA #REQUIRED>
<!ELEMENT Space EMPTY>
<!ATTLIST Space count CDATA "1">
<!ELEMENT Str EMPTY>
<!ATTLIST Str content CDATA "">
<!ELEMENT Emph (%inline;)*>
<!ELEMENT Strong (%inline;)*>
<!ELEMENT Underline (%inline;)*>
<!ELEMENT Strikeout (%inline;)*>
<!ELEMENT Superscript (%inline;)*>
<!ELEMENT Subscript (%inline;)*>
<!ELEMENT SmallCaps (%inline;)*>
<!ELEMENT Span (%inline;)*>
<!ATTLIST Span
custom-style CDATA #IMPLIED
%attr;>
<!ELEMENT Quoted (%inline;)*>
<!ATTLIST Quoted quote-type (SingleQuote | DoubleQuote) "DoubleQuote">
<!ELEMENT Math (#PCDATA)>
<!ATTLIST Math math-type (DisplayMath | InlineMath) "InlineMath">
<!ELEMENT RawInline (#PCDATA)>
<!ATTLIST RawInline format CDATA #REQUIRED>
<!ELEMENT Cite (#PCDATA | citations | %inline_element;)*>
<!ELEMENT citations (Citation)+>
<!ELEMENT Citation (prefix?, suffix?)>
<!ELEMENT prefix (%inline;)*>
<!ELEMENT suffix (%inline;)*>
<!ATTLIST Citation
id CDATA #IMPLIED
note-num CDATA #IMPLIED
hash CDATA "0"
mode (AuthorInText | SuppressAuthor | NormalCitation) "AuthorInText">
<!ELEMENT Code (#PCDATA)>
<!ATTLIST Code %attr;>
<!ENTITY % target "title CDATA #IMPLIED url CDATA #IMPLIED">
<!ELEMENT Image (%inline;)*>
<!ATTLIST Image
title CDATA #IMPLIED
src CDATA #IMPLIED
%attr;>
<!ELEMENT Link (%inline;)*>
<!ATTLIST Link
title CDATA #IMPLIED
href CDATA #IMPLIED
%attr;>
<!ELEMENT SoftBreak EMPTY>
<!ELEMENT LineBreak EMPTY>
<!ELEMENT Note (%block;)*>
+250
View File
@@ -0,0 +1,250 @@
# A RELAX NG schema for Pandoc XML format.
# Copyright : Copyright (C) 2025- Massimiliano Farinella
# License : GNU GPL, version 2 or above
# Maintainer : Massimiliano Farinella <massifrg@gmail.com>
#
# This is a RELAX NG schema for the XML representation of Pandoc AST.
# It's an equivalent of native and JSON formats, but modeled as XML.
# You can use this schema to validate Pandoc XML documents.
# It's translated from pandoc-xml.dtd with the "Trang" software by James Clark,
# and adjusted manually to add some constraints:
# - elements with Attr can have arbitrary attributes (this is not possible with a DTD)
# - Header's "level", OrderedList's "start" and Cell's "rowspan" and "colspan" attributes
# must be a positive integer and are equal to 1 if not specified
# - column widths in ColSpec must be between 0 and 1 (inclusive, with 0=ColWidthDefault)
# - the "count" attribute in the "<Space>" element must be positive and equal to 1 if not specified
namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
Pandoc = element Pandoc { attlist_Pandoc, meta, blocks }
attlist_Pandoc &= attribute api-version { text }
block =
Para
| Plain
| Header
| Div
| BlockQuote
| HorizontalRule
| BulletList
| OrderedList
| DefinitionList
| Table
| Figure
| LineBlock
| CodeBlock
| RawBlock
inline_element =
Str
| Space
| Emph
| Strong
| Underline
| Strikeout
| Superscript
| Subscript
| SmallCaps
| Quoted
| Cite
| Code
| SoftBreak
| LineBreak
| Math
| RawInline
| Link
| Image
| Note
| Span
inline = text | inline_element
attr =
attribute id { xsd:ID }?,
attribute class { text }?,
attribute * { text }*
metavalue =
MetaMap | MetaList | MetaBool | MetaString | MetaInlines | MetaBlocks
meta = element meta { attlist_meta, entry* }
attlist_meta &= empty
MetaMap = element MetaMap { attlist_MetaMap, entry* }
attlist_MetaMap &= empty
entry = element entry { attlist_entry, metavalue* }
attlist_entry &= attribute key { text }
MetaList = element MetaList { attlist_MetaList, metavalue* }
attlist_MetaList &= empty
MetaBool = element MetaBool { attlist_MetaBool, empty }
attlist_MetaBool &= attribute value { "true" | "false" }
MetaString = element MetaString { attlist_MetaString, text }
attlist_MetaString &= empty
MetaInlines = element MetaInlines { attlist_MetaInlines, inline* }
attlist_MetaInlines &= empty
MetaBlocks = element MetaBlocks { attlist_MetaBlocks, block* }
attlist_MetaBlocks &= empty
blocks = element blocks { attlist_blocks, block* }
attlist_blocks &= empty
Para = element Para { attlist_Para, inline* }
attlist_Para &= empty
Plain = element Plain { attlist_Plain, inline* }
attlist_Plain &= empty
Header = element Header { attlist_Header, inline* }
attlist_Header &=
[ a:defaultValue = "1" ] attribute level { xsd:positiveInteger }?,
attr
Div = element Div { attlist_Div, block* }
attlist_Div &= attr
BlockQuote = element BlockQuote { attlist_BlockQuote, block* }
attlist_BlockQuote &= empty
HorizontalRule =
element HorizontalRule { attlist_HorizontalRule, empty }
attlist_HorizontalRule &= empty
BulletList = element BulletList { attlist_BulletList, item+ }
attlist_BulletList &= empty
OrderedList = element OrderedList { attlist_OrderedList, item+ }
attlist_OrderedList &=
[ a:defaultValue = "1" ] attribute start { xsd:positiveInteger }?,
[ a:defaultValue = "DefaultStyle" ]
attribute number-style {
"DefaultStyle"
| "Example"
| "Decimal"
| "LowerRoman"
| "UpperRoman"
| "LowerAlpha"
| "UpperAlpha"
}?,
[ a:defaultValue = "DefaultDelim" ]
attribute number-delim {
"DefaultDelim" | "Period" | "OneParen" | "TwoParens"
}?
DefinitionList =
element DefinitionList { attlist_DefinitionList, item+ }
attlist_DefinitionList &= empty
item =
element item {
attlist_item,
(block* | (term, def+))
}
attlist_item &= empty
term = element term { attlist_term, inline* }
attlist_term &= empty
def = element def { attlist_def, block* }
attlist_def &= empty
Table =
element Table {
attlist_Table, Caption, colspecs, TableHead, TableBody+, TableFoot
}
attlist_Table &= attr
Caption = element Caption { attlist_Caption, ShortCaption?, block* }
attlist_Caption &= empty
ShortCaption = element ShortCaption { attlist_ShortCaption, inline* }
attlist_ShortCaption &= empty
colspecs = element colspecs { attlist_colspecs, ColSpec+ }
attlist_colspecs &= empty
ColSpec = element ColSpec { attlist_ColSpec, empty }
attlist_ColSpec &=
[ a:defaultValue = "AlignDefault" ]
attribute alignment {
"AlignLeft" | "AlignRight" | "AlignCenter" | "AlignDefault"
}?,
[ a:defaultValue = "0" ]
attribute col-width {
xsd:double { minInclusive = "0" maxInclusive = "1" }
}?
TableHead = element TableHead { attlist_TableHead, Row* }
attlist_TableHead &= attr
TableFoot = element TableFoot { attlist_TableFoot, Row* }
attlist_TableFoot &= attr
TableBody = element TableBody { attlist_TableBody, header, body }
attlist_TableBody &=
[ a:defaultValue = "0" ] attribute row-head-columns { text }?,
attr
header = element header { attlist_header, Row* }
attlist_header &= empty
body = element body { attlist_body, Row* }
attlist_body &= empty
Row = element Row { attlist_Row, Cell* }
attlist_Row &= attr
Cell = element Cell { attlist_Cell, block* }
attlist_Cell &=
[ a:defaultValue = "AlignDefault" ]
attribute alignment {
"AlignLeft" | "AlignRight" | "AlignCenter" | "AlignDefault"
}?,
[ a:defaultValue = "1" ] attribute row-span { xsd:positiveInteger }?,
[ a:defaultValue = "1" ] attribute col-span { xsd:positiveInteger }?,
attr
Figure = element Figure { attlist_Figure, Caption, block* }
attlist_Figure &= attr
LineBlock = element LineBlock { attlist_LineBlock, line+ }
attlist_LineBlock &= empty
line = element line { attlist_line, inline* }
attlist_line &= empty
CodeBlock = element CodeBlock { attlist_CodeBlock, text }
attlist_CodeBlock &= attr
RawBlock = element RawBlock { attlist_RawBlock, text }
attlist_RawBlock &= attribute format { text }
Space = element Space { attlist_Space, empty }
attlist_Space &=
[ a:defaultValue = "1" ] attribute count { xsd:positiveInteger }?
Str = element Str { attlist_Str, empty }
attlist_Str &= [ a:defaultValue = "" ] attribute content { text }?
Emph = element Emph { attlist_Emph, inline* }
attlist_Emph &= empty
Strong = element Strong { attlist_Strong, inline* }
attlist_Strong &= empty
Underline = element Underline { attlist_Underline, inline* }
attlist_Underline &= empty
Strikeout = element Strikeout { attlist_Strikeout, inline* }
attlist_Strikeout &= empty
Superscript = element Superscript { attlist_Superscript, inline* }
attlist_Superscript &= empty
Subscript = element Subscript { attlist_Subscript, inline* }
attlist_Subscript &= empty
SmallCaps = element SmallCaps { attlist_SmallCaps, inline* }
attlist_SmallCaps &= empty
Span = element Span { attlist_Span, inline* }
attlist_Span &= attr
Quoted = element Quoted { attlist_Quoted, inline* }
attlist_Quoted &=
[ a:defaultValue = "DoubleQuote" ]
attribute quote-type { "SingleQuote" | "DoubleQuote" }?
Math = element Math { attlist_Math, text }
attlist_Math &=
[ a:defaultValue = "InlineMath" ]
attribute math-type { "DisplayMath" | "InlineMath" }?
RawInline = element RawInline { attlist_RawInline, text }
attlist_RawInline &= attribute format { text }
Cite =
element Cite { attlist_Cite, (text | citations | inline_element)* }
attlist_Cite &= empty
citations = element citations { attlist_citations, Citation+ }
attlist_citations &= empty
Citation = element Citation { attlist_Citation, prefix?, suffix? }
prefix = element prefix { attlist_prefix, inline* }
attlist_prefix &= empty
suffix = element suffix { attlist_suffix, inline* }
attlist_suffix &= empty
attlist_Citation &=
attribute id { text }?,
attribute note-num { text }?,
[ a:defaultValue = "0" ] attribute hash { text }?,
[ a:defaultValue = "AuthorInText" ]
attribute mode {
"AuthorInText" | "SuppressAuthor" | "NormalCitation"
}?
Code = element Code { attlist_Code, text }
attlist_Code &= attr
Image = element Image { attlist_Image, inline* }
attlist_Image &=
attribute title { text }?,
attribute src { text }?,
attr
Link = element Link { attlist_Link, inline* }
attlist_Link &=
attribute title { text }?,
attribute href { text }?,
attr
SoftBreak = element SoftBreak { attlist_SoftBreak, empty }
attlist_SoftBreak &= empty
LineBreak = element LineBreak { attlist_LineBreak, empty }
attlist_LineBreak &= empty
Note = element Note { attlist_Note, block* }
attlist_Note &= empty
start = Pandoc
+913
View File
@@ -0,0 +1,913 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
A RELAX NG schema for Pandoc XML format.
Copyright : Copyright (C) 2025- Massimiliano Farinella
License : GNU GPL, version 2 or above
Maintainer : Massimiliano Farinella <massifrg@gmail.com>
This is a RELAX NG schema for the XML representation of Pandoc AST.
It's an equivalent of native and JSON formats, but modeled as XML.
You can use this schema to validate Pandoc XML documents.
It's translated from pandoc-xml.dtd with the "Trang" software by James Clark,
and adjusted manually to add some constraints:
- elements with Attr can have arbitrary attributes (this is not possible with a DTD)
- Header's "level", OrderedList's "start" and Cell's "rowspan" and "colspan" attributes
must be a positive integer and are equal to 1 if not specified
- column widths in ColSpec must be between 0 and 1 (inclusive, with 0=ColWidthDefault)
- the "count" attribute in the "<Space>" element must be positive and equal to 1 if not specified
-->
<grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<define name="Pandoc">
<element name="Pandoc">
<ref name="attlist_Pandoc" />
<ref name="meta" />
<ref name="blocks" />
</element>
</define>
<define name="attlist_Pandoc" combine="interleave">
<attribute name="api-version" />
</define>
<define name="block">
<choice>
<ref name="Para" />
<ref name="Plain" />
<ref name="Header" />
<ref name="Div" />
<ref name="BlockQuote" />
<ref name="HorizontalRule" />
<ref name="BulletList" />
<ref name="OrderedList" />
<ref name="DefinitionList" />
<ref name="Table" />
<ref name="Figure" />
<ref name="LineBlock" />
<ref name="CodeBlock" />
<ref name="RawBlock" />
</choice>
</define>
<define name="inline_element">
<choice>
<ref name="Str" />
<ref name="Space" />
<ref name="Emph" />
<ref name="Strong" />
<ref name="Underline" />
<ref name="Strikeout" />
<ref name="Superscript" />
<ref name="Subscript" />
<ref name="SmallCaps" />
<ref name="Quoted" />
<ref name="Cite" />
<ref name="Code" />
<ref name="SoftBreak" />
<ref name="LineBreak" />
<ref name="Math" />
<ref name="RawInline" />
<ref name="Link" />
<ref name="Image" />
<ref name="Note" />
<ref name="Span" />
</choice>
</define>
<define name="inline">
<choice>
<text />
<ref name="inline_element" />
</choice>
</define>
<define name="attr">
<optional>
<attribute name="id">
<data type="ID" />
</attribute>
</optional>
<optional>
<attribute name="class" />
</optional>
<zeroOrMore>
<attribute>
<anyName />
</attribute>
</zeroOrMore>
</define>
<define name="metavalue">
<choice>
<ref name="MetaMap" />
<ref name="MetaList" />
<ref name="MetaBool" />
<ref name="MetaString" />
<ref name="MetaInlines" />
<ref name="MetaBlocks" />
</choice>
</define>
<define name="meta">
<element name="meta">
<ref name="attlist_meta" />
<zeroOrMore>
<ref name="entry" />
</zeroOrMore>
</element>
</define>
<define name="attlist_meta" combine="interleave">
<empty />
</define>
<define name="MetaMap">
<element name="MetaMap">
<ref name="attlist_MetaMap" />
<zeroOrMore>
<ref name="entry" />
</zeroOrMore>
</element>
</define>
<define name="attlist_MetaMap" combine="interleave">
<empty />
</define>
<define name="entry">
<element name="entry">
<ref name="attlist_entry" />
<zeroOrMore>
<ref name="metavalue" />
</zeroOrMore>
</element>
</define>
<define name="attlist_entry" combine="interleave">
<attribute name="key" />
</define>
<define name="MetaList">
<element name="MetaList">
<ref name="attlist_MetaList" />
<zeroOrMore>
<ref name="metavalue" />
</zeroOrMore>
</element>
</define>
<define name="attlist_MetaList" combine="interleave">
<empty />
</define>
<define name="MetaBool">
<element name="MetaBool">
<ref name="attlist_MetaBool" />
<empty />
</element>
</define>
<define name="attlist_MetaBool" combine="interleave">
<attribute name="value">
<choice>
<value>true</value>
<value>false</value>
</choice>
</attribute>
</define>
<define name="MetaString">
<element name="MetaString">
<ref name="attlist_MetaString" />
<text />
</element>
</define>
<define name="attlist_MetaString" combine="interleave">
<empty />
</define>
<define name="MetaInlines">
<element name="MetaInlines">
<ref name="attlist_MetaInlines" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_MetaInlines" combine="interleave">
<empty />
</define>
<define name="MetaBlocks">
<element name="MetaBlocks">
<ref name="attlist_MetaBlocks" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_MetaBlocks" combine="interleave">
<empty />
</define>
<define name="blocks">
<element name="blocks">
<ref name="attlist_blocks" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_blocks" combine="interleave">
<empty />
</define>
<define name="Para">
<element name="Para">
<ref name="attlist_Para" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Para" combine="interleave">
<empty />
</define>
<define name="Plain">
<element name="Plain">
<ref name="attlist_Plain" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Plain" combine="interleave">
<empty />
</define>
<define name="Header">
<element name="Header">
<ref name="attlist_Header" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Header" combine="interleave">
<optional>
<attribute name="level" a:defaultValue="1">
<data type="positiveInteger" />
</attribute>
</optional>
<ref name="attr" />
</define>
<define name="Div">
<element name="Div">
<ref name="attlist_Div" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Div" combine="interleave">
<ref name="attr" />
</define>
<define name="BlockQuote">
<element name="BlockQuote">
<ref name="attlist_BlockQuote" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_BlockQuote" combine="interleave">
<empty />
</define>
<define name="HorizontalRule">
<element name="HorizontalRule">
<ref name="attlist_HorizontalRule" />
<empty />
</element>
</define>
<define name="attlist_HorizontalRule" combine="interleave">
<empty />
</define>
<define name="BulletList">
<element name="BulletList">
<ref name="attlist_BulletList" />
<oneOrMore>
<ref name="item" />
</oneOrMore>
</element>
</define>
<define name="attlist_BulletList" combine="interleave">
<empty />
</define>
<define name="OrderedList">
<element name="OrderedList">
<ref name="attlist_OrderedList" />
<oneOrMore>
<ref name="item" />
</oneOrMore>
</element>
</define>
<define name="attlist_OrderedList" combine="interleave">
<optional>
<attribute name="start" a:defaultValue="1">
<data type="positiveInteger" />
</attribute>
</optional>
<optional>
<attribute name="number-style" a:defaultValue="DefaultStyle">
<choice>
<value>DefaultStyle</value>
<value>Example</value>
<value>Decimal</value>
<value>LowerRoman</value>
<value>UpperRoman</value>
<value>LowerAlpha</value>
<value>UpperAlpha</value>
</choice>
</attribute>
</optional>
<optional>
<attribute name="number-delim" a:defaultValue="DefaultDelim">
<choice>
<value>DefaultDelim</value>
<value>Period</value>
<value>OneParen</value>
<value>TwoParens</value>
</choice>
</attribute>
</optional>
</define>
<define name="DefinitionList">
<element name="DefinitionList">
<ref name="attlist_DefinitionList" />
<oneOrMore>
<ref name="item" />
</oneOrMore>
</element>
</define>
<define name="attlist_DefinitionList" combine="interleave">
<empty />
</define>
<define name="item">
<element name="item">
<ref name="attlist_item" />
<choice>
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
<group>
<ref name="term" />
<oneOrMore>
<ref name="def" />
</oneOrMore>
</group>
</choice>
</element>
</define>
<define name="attlist_item" combine="interleave">
<empty />
</define>
<define name="term">
<element name="term">
<ref name="attlist_term" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_term" combine="interleave">
<empty />
</define>
<define name="def">
<element name="def">
<ref name="attlist_def" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_def" combine="interleave">
<empty />
</define>
<define name="Table">
<element name="Table">
<ref name="attlist_Table" />
<ref name="Caption" />
<ref name="colspecs" />
<ref name="TableHead" />
<oneOrMore>
<ref name="TableBody" />
</oneOrMore>
<ref name="TableFoot" />
</element>
</define>
<define name="attlist_Table" combine="interleave">
<ref name="attr" />
</define>
<define name="Caption">
<element name="Caption">
<ref name="attlist_Caption" />
<optional>
<ref name="ShortCaption" />
</optional>
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Caption" combine="interleave">
<empty />
</define>
<define name="ShortCaption">
<element name="ShortCaption">
<ref name="attlist_ShortCaption" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_ShortCaption" combine="interleave">
<empty />
</define>
<define name="colspecs">
<element name="colspecs">
<ref name="attlist_colspecs" />
<oneOrMore>
<ref name="ColSpec" />
</oneOrMore>
</element>
</define>
<define name="attlist_colspecs" combine="interleave">
<empty />
</define>
<define name="ColSpec">
<element name="ColSpec">
<ref name="attlist_ColSpec" />
<empty />
</element>
</define>
<define name="attlist_ColSpec" combine="interleave">
<optional>
<attribute name="alignment" a:defaultValue="AlignDefault">
<choice>
<value>AlignLeft</value>
<value>AlignRight</value>
<value>AlignCenter</value>
<value>AlignDefault</value>
</choice>
</attribute>
</optional>
<optional>
<attribute name="col-width" a:defaultValue="0">
<data type="double">
<param name="minInclusive">0</param>
<param name="maxInclusive">1</param>
</data>
</attribute>
</optional>
</define>
<define name="TableHead">
<element name="TableHead">
<ref name="attlist_TableHead" />
<zeroOrMore>
<ref name="Row" />
</zeroOrMore>
</element>
</define>
<define name="attlist_TableHead" combine="interleave">
<ref name="attr" />
</define>
<define name="TableFoot">
<element name="TableFoot">
<ref name="attlist_TableFoot" />
<zeroOrMore>
<ref name="Row" />
</zeroOrMore>
</element>
</define>
<define name="attlist_TableFoot" combine="interleave">
<ref name="attr" />
</define>
<define name="TableBody">
<element name="TableBody">
<ref name="attlist_TableBody" />
<ref name="header" />
<ref name="body" />
</element>
</define>
<define name="attlist_TableBody" combine="interleave">
<optional>
<attribute name="row-head-columns" a:defaultValue="0" />
</optional>
<ref name="attr" />
</define>
<define name="header">
<element name="header">
<ref name="attlist_header" />
<zeroOrMore>
<ref name="Row" />
</zeroOrMore>
</element>
</define>
<define name="attlist_header" combine="interleave">
<empty />
</define>
<define name="body">
<element name="body">
<ref name="attlist_body" />
<zeroOrMore>
<ref name="Row" />
</zeroOrMore>
</element>
</define>
<define name="attlist_body" combine="interleave">
<empty />
</define>
<define name="Row">
<element name="Row">
<ref name="attlist_Row" />
<zeroOrMore>
<ref name="Cell" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Row" combine="interleave">
<ref name="attr" />
</define>
<define name="Cell">
<element name="Cell">
<ref name="attlist_Cell" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Cell" combine="interleave">
<optional>
<attribute name="alignment" a:defaultValue="AlignDefault">
<choice>
<value>AlignLeft</value>
<value>AlignRight</value>
<value>AlignCenter</value>
<value>AlignDefault</value>
</choice>
</attribute>
</optional>
<optional>
<attribute name="row-span" a:defaultValue="1">
<data type="positiveInteger" />
</attribute>
</optional>
<optional>
<attribute name="col-span" a:defaultValue="1">
<data type="positiveInteger" />
</attribute>
</optional>
<ref name="attr" />
</define>
<define name="Figure">
<element name="Figure">
<ref name="attlist_Figure" />
<ref name="Caption" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Figure" combine="interleave">
<ref name="attr" />
</define>
<define name="LineBlock">
<element name="LineBlock">
<ref name="attlist_LineBlock" />
<oneOrMore>
<ref name="line" />
</oneOrMore>
</element>
</define>
<define name="attlist_LineBlock" combine="interleave">
<empty />
</define>
<define name="line">
<element name="line">
<ref name="attlist_line" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_line" combine="interleave">
<empty />
</define>
<define name="CodeBlock">
<element name="CodeBlock">
<ref name="attlist_CodeBlock" />
<text />
</element>
</define>
<define name="attlist_CodeBlock" combine="interleave">
<ref name="attr" />
</define>
<define name="RawBlock">
<element name="RawBlock">
<ref name="attlist_RawBlock" />
<text />
</element>
</define>
<define name="attlist_RawBlock" combine="interleave">
<attribute name="format" />
</define>
<define name="Space">
<element name="Space">
<ref name="attlist_Space" />
<empty />
</element>
</define>
<define name="attlist_Space" combine="interleave">
<optional>
<attribute name="count" a:defaultValue="1">
<data type="positiveInteger" />
</attribute>
</optional>
</define>
<define name="Str">
<element name="Str">
<ref name="attlist_Str" />
<empty />
</element>
</define>
<define name="attlist_Str" combine="interleave">
<optional>
<attribute name="content" a:defaultValue="" />
</optional>
</define>
<define name="Emph">
<element name="Emph">
<ref name="attlist_Emph" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Emph" combine="interleave">
<empty />
</define>
<define name="Strong">
<element name="Strong">
<ref name="attlist_Strong" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Strong" combine="interleave">
<empty />
</define>
<define name="Underline">
<element name="Underline">
<ref name="attlist_Underline" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Underline" combine="interleave">
<empty />
</define>
<define name="Strikeout">
<element name="Strikeout">
<ref name="attlist_Strikeout" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Strikeout" combine="interleave">
<empty />
</define>
<define name="Superscript">
<element name="Superscript">
<ref name="attlist_Superscript" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Superscript" combine="interleave">
<empty />
</define>
<define name="Subscript">
<element name="Subscript">
<ref name="attlist_Subscript" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Subscript" combine="interleave">
<empty />
</define>
<define name="SmallCaps">
<element name="SmallCaps">
<ref name="attlist_SmallCaps" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_SmallCaps" combine="interleave">
<empty />
</define>
<define name="Span">
<element name="Span">
<ref name="attlist_Span" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Span" combine="interleave">
<ref name="attr" />
</define>
<define name="Quoted">
<element name="Quoted">
<ref name="attlist_Quoted" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Quoted" combine="interleave">
<optional>
<attribute name="quote-type" a:defaultValue="DoubleQuote">
<choice>
<value>SingleQuote</value>
<value>DoubleQuote</value>
</choice>
</attribute>
</optional>
</define>
<define name="Math">
<element name="Math">
<ref name="attlist_Math" />
<text />
</element>
</define>
<define name="attlist_Math" combine="interleave">
<optional>
<attribute name="math-type" a:defaultValue="InlineMath">
<choice>
<value>DisplayMath</value>
<value>InlineMath</value>
</choice>
</attribute>
</optional>
</define>
<define name="RawInline">
<element name="RawInline">
<ref name="attlist_RawInline" />
<text />
</element>
</define>
<define name="attlist_RawInline" combine="interleave">
<attribute name="format" />
</define>
<define name="Cite">
<element name="Cite">
<ref name="attlist_Cite" />
<zeroOrMore>
<choice>
<text />
<ref name="citations" />
<ref name="inline_element" />
</choice>
</zeroOrMore>
</element>
</define>
<define name="attlist_Cite" combine="interleave">
<empty />
</define>
<define name="citations">
<element name="citations">
<ref name="attlist_citations" />
<oneOrMore>
<ref name="Citation" />
</oneOrMore>
</element>
</define>
<define name="attlist_citations" combine="interleave">
<empty />
</define>
<define name="Citation">
<element name="Citation">
<ref name="attlist_Citation" />
<optional>
<ref name="prefix" />
</optional>
<optional>
<ref name="suffix" />
</optional>
</element>
</define>
<define name="prefix">
<element name="prefix">
<ref name="attlist_prefix" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_prefix" combine="interleave">
<empty />
</define>
<define name="suffix">
<element name="suffix">
<ref name="attlist_suffix" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_suffix" combine="interleave">
<empty />
</define>
<define name="attlist_Citation" combine="interleave">
<optional>
<attribute name="id" />
</optional>
<optional>
<attribute name="note-num" />
</optional>
<optional>
<attribute name="hash" a:defaultValue="0" />
</optional>
<optional>
<attribute name="mode" a:defaultValue="AuthorInText">
<choice>
<value>AuthorInText</value>
<value>SuppressAuthor</value>
<value>NormalCitation</value>
</choice>
</attribute>
</optional>
</define>
<define name="Code">
<element name="Code">
<ref name="attlist_Code" />
<text />
</element>
</define>
<define name="attlist_Code" combine="interleave">
<ref name="attr" />
</define>
<define name="Image">
<element name="Image">
<ref name="attlist_Image" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Image" combine="interleave">
<optional>
<attribute name="title"/>
</optional>
<optional>
<attribute name="src"/>
</optional>
<ref name="attr"/>
</define>
<define name="Link">
<element name="Link">
<ref name="attlist_Link" />
<zeroOrMore>
<ref name="inline" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Link" combine="interleave">
<optional>
<attribute name="title"/>
</optional>
<optional>
<attribute name="href"/>
</optional>
<ref name="attr"/>
</define>
<define name="SoftBreak">
<element name="SoftBreak">
<ref name="attlist_SoftBreak" />
<empty />
</element>
</define>
<define name="attlist_SoftBreak" combine="interleave">
<empty />
</define>
<define name="LineBreak">
<element name="LineBreak">
<ref name="attlist_LineBreak" />
<empty />
</element>
</define>
<define name="attlist_LineBreak" combine="interleave">
<empty />
</define>
<define name="Note">
<element name="Note">
<ref name="attlist_Note" />
<zeroOrMore>
<ref name="block" />
</zeroOrMore>
</element>
</define>
<define name="attlist_Note" combine="interleave">
<empty />
</define>
<start>
<choice>
<ref name="Pandoc" />
</choice>
</start>
</grammar>
+602
View File
@@ -0,0 +1,602 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
A XML Schema definition for Pandoc XML format.
Copyright : Copyright (C) 2025- Massimiliano Farinella
License : GNU GPL, version 2 or above
Maintainer : Massimiliano Farinella <massifrg@gmail.com>
This is a XML Schema schema for the XML representation of Pandoc AST.
It's an equivalent of native and JSON formats, but modeled as XML.
You can use this schema to validate Pandoc XML documents.
It's translated from pandoc-xml.dtd with the "Trang" software by James Clark,
and adjusted manually to add some constraints:
- elements with Attr can have arbitrary attributes (this is not possible with a DTD)
- Header's "level", OrderedList's "start" and Cell's "rowspan" and "colspan" attributes
must be a positive integer and are equal to 1 if not specified
- column widths in ColSpec must be between 0 and 1 (inclusive, with 0=ColWidthDefault)
- the "count" attribute in the "<Space>" element must be a positive integer,
equal to 1 if not specified
- OrderedList's number style and delimiter, ColSpec's and Cell's alignment,
Quoted's QuoteType and Math's Mathtype
can only take the values specified in pandoc-types
- TableBody's row-head-columns must be zero (default, when not specified) or a positive integer
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Pandoc">
<xs:complexType>
<xs:sequence>
<xs:element ref="meta"/>
<xs:element ref="blocks"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Pandoc"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Pandoc">
<xs:attribute name="api-version" use="required"/>
</xs:attributeGroup>
<xs:element name="block" abstract="true"/>
<xs:element name="inline_element" abstract="true"/>
<xs:group name="inline">
<xs:sequence>
<xs:element minOccurs="0" ref="inline_element"/>
</xs:sequence>
</xs:group>
<xs:attributeGroup name="attr">
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="class"/>
<xs:anyAttribute processContents="skip"/>
</xs:attributeGroup>
<xs:element name="metavalue" abstract="true"/>
<xs:element name="meta">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="entry"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="MetaMap" substitutionGroup="metavalue">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="entry"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="entry">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="metavalue"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_entry"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_entry">
<xs:attribute name="key" use="required"/>
</xs:attributeGroup>
<xs:element name="MetaList" substitutionGroup="metavalue">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="metavalue"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="MetaBool" substitutionGroup="metavalue">
<xs:complexType>
<xs:attributeGroup ref="attlist_MetaBool"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_MetaBool">
<xs:attribute name="value" use="required">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="MetaString" substitutionGroup="metavalue" type="xs:string"/>
<xs:element name="MetaInlines" substitutionGroup="metavalue">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="MetaBlocks" substitutionGroup="metavalue">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="blocks">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Para" substitutionGroup="block">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Plain" substitutionGroup="block">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Header" substitutionGroup="block">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
<xs:attributeGroup ref="attlist_Header"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Header">
<xs:attribute name="level" default="1" type="xs:positiveInteger"/>
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Div" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Div"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Div">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="BlockQuote" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="HorizontalRule" substitutionGroup="block">
<xs:complexType/>
</xs:element>
<xs:element name="BulletList" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="item"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="OrderedList" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="item"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_OrderedList"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_OrderedList">
<xs:attribute name="start" default="1" type="xs:positiveInteger"/>
<xs:attribute name="number-style" default="DefaultStyle">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="DefaultStyle"/>
<xs:enumeration value="Example"/>
<xs:enumeration value="Decimal"/>
<xs:enumeration value="LowerRoman"/>
<xs:enumeration value="UpperRoman"/>
<xs:enumeration value="LowerAlpha"/>
<xs:enumeration value="UpperAlpha"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="number-delim" default="DefaultDelim">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="DefaultDelim"/>
<xs:enumeration value="Period"/>
<xs:enumeration value="OneParen"/>
<xs:enumeration value="TwoParens"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="DefinitionList" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="item"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="item">
<xs:complexType>
<xs:choice>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
<xs:sequence>
<xs:element ref="term"/>
<xs:element maxOccurs="unbounded" ref="def"/>
</xs:sequence>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="term">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="def">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Table" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element ref="Caption"/>
<xs:element ref="colspecs"/>
<xs:element ref="TableHead"/>
<xs:element maxOccurs="unbounded" ref="TableBody"/>
<xs:element ref="TableFoot"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Table"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Table">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Caption">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="ShortCaption"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ShortCaption">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="colspecs">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="ColSpec"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ColSpec">
<xs:complexType>
<xs:attributeGroup ref="attlist_ColSpec"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_ColSpec">
<xs:attribute name="alignment" default="AlignDefault">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="AlignLeft"/>
<xs:enumeration value="AlignRight"/>
<xs:enumeration value="AlignCenter"/>
<xs:enumeration value="AlignDefault"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="col-width" default="0">
<xs:simpleType>
<xs:restriction base="xs:double">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="TableHead">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Row"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_TableHead"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_TableHead">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="TableFoot">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Row"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_TableFoot"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_TableFoot">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="TableBody">
<xs:complexType>
<xs:sequence>
<xs:element ref="header"/>
<xs:element ref="body"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_TableBody"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_TableBody">
<xs:attribute name="row-head-columns" default="0"/>
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="header">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Row"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="body">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Row"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Row">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Cell"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Row"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Row">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Cell">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Cell"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Cell">
<xs:attribute name="alignment" default="AlignDefault">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="AlignLeft"/>
<xs:enumeration value="AlignRight"/>
<xs:enumeration value="AlignCenter"/>
<xs:enumeration value="AlignDefault"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="row-span" default="1" type="xs:positiveInteger"/>
<xs:attribute name="col-span" default="1" type="xs:positiveInteger"/>
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Figure" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element ref="Caption"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Figure"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Figure">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="LineBlock" substitutionGroup="block">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="line"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="line">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="CodeBlock" substitutionGroup="block">
<xs:complexType mixed="true">
<xs:attributeGroup ref="attlist_CodeBlock"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_CodeBlock">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="RawBlock" substitutionGroup="block">
<xs:complexType mixed="true">
<xs:attributeGroup ref="attlist_RawBlock"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_RawBlock">
<xs:attribute name="format" use="required"/>
</xs:attributeGroup>
<xs:element name="Space" substitutionGroup="inline_element">
<xs:complexType>
<xs:attributeGroup ref="attlist_Space"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Space">
<xs:attribute name="count" default="1" type="xs:positiveInteger"/>
</xs:attributeGroup>
<xs:element name="Str" substitutionGroup="inline_element">
<xs:complexType>
<xs:attributeGroup ref="attlist_Str"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Str">
<xs:attribute name="content" default=""/>
</xs:attributeGroup>
<xs:element name="Emph" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Strong" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Underline" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Strikeout" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Superscript" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Subscript" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="SmallCaps" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="Span" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
<xs:attributeGroup ref="attlist_Span"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Span">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Quoted" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
<xs:attributeGroup ref="attlist_Quoted"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Quoted">
<xs:attribute name="quote-type" default="DoubleQuote">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="SingleQuote"/>
<xs:enumeration value="DoubleQuote"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="Math" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:attributeGroup ref="attlist_Math"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Math">
<xs:attribute name="math-type" default="InlineMath">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="DisplayMath"/>
<xs:enumeration value="InlineMath"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="RawInline" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:attributeGroup ref="attlist_RawInline"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_RawInline">
<xs:attribute name="format" use="required"/>
</xs:attributeGroup>
<xs:element name="Cite" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="citations"/>
<xs:element ref="inline_element"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="citations">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="Citation"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Citation">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="prefix"/>
<xs:element minOccurs="0" ref="suffix"/>
</xs:sequence>
<xs:attributeGroup ref="attlist_Citation"/>
</xs:complexType>
</xs:element>
<xs:element name="prefix">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:element name="suffix">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Citation">
<xs:attribute name="id"/>
<xs:attribute name="note-num"/>
<xs:attribute name="hash" default="0"/>
<xs:attribute name="mode" default="AuthorInText">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="AuthorInText"/>
<xs:enumeration value="SuppressAuthor"/>
<xs:enumeration value="NormalCitation"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="Code" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:attributeGroup ref="attlist_Code"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Code">
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Image" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
<xs:attributeGroup ref="attlist_Image"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Image">
<xs:attribute name="title"/>
<xs:attribute name="src"/>
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="Link" substitutionGroup="inline_element">
<xs:complexType mixed="true">
<xs:group minOccurs="0" maxOccurs="unbounded" ref="inline"/>
<xs:attributeGroup ref="attlist_Link"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="attlist_Link">
<xs:attribute name="title"/>
<xs:attribute name="href"/>
<xs:attributeGroup ref="attr"/>
</xs:attributeGroup>
<xs:element name="SoftBreak" substitutionGroup="inline_element">
<xs:complexType/>
</xs:element>
<xs:element name="LineBreak" substitutionGroup="inline_element">
<xs:complexType/>
</xs:element>
<xs:element name="Note" substitutionGroup="inline_element">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="block"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>