packages feed

pandoc-3.12: src/Text/Pandoc/XMLFormat.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Text.Pandoc.XMLFormat
  ( decodeAttrName,
    encodeAttrName,
    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.Char (chr, digitToInt, isAsciiLower, isAsciiUpper, isDigit, isHexDigit, ord, toUpper)
import Data.Text (Text)
import qualified Data.Text as T
import Numeric (showHex)

-- | Encode an attribute name so that it is a valid XML name.
-- Characters that are not allowed in XML names -- and colons, which
-- XML parsers treat as namespace separators -- are encoded as
-- _xHHHH_, where HHHH is the hexadecimal code of the character
-- (uppercase, at least four digits).  An underscore that introduces
-- a literal "_x" is encoded as _x005F_, so that decoding is
-- unambiguous.  For example, "typst:property" is encoded as
-- "typst_x003A_property".
encodeAttrName :: Text -> Text
encodeAttrName name =
  case T.unpack name of
    [] -> name
    c : cs
      | isNameStartChar c && all isNameChar cs && not ("_x" `T.isInfixOf` name) ->
          name
      | otherwise ->
          T.pack $ concat $ go isNameStartChar (c : cs)
  where
    go _ [] = []
    go ok (c : cs)
      | c == '_' && take 1 cs == "x" = encodeChar '_' : go isNameChar cs
      | ok c = [c] : go isNameChar cs
      | otherwise = encodeChar c : go isNameChar cs
    encodeChar c = "_x" ++ replicate (4 - length h) '0' ++ h ++ "_"
      where
        h = map toUpper $ showHex (ord c) ""

-- | Decode an attribute name encoded by 'encodeAttrName': _xHHHH_
-- sequences (four to six hexadecimal digits) are decoded to the
-- character with the given code.
decodeAttrName :: Text -> Text
decodeAttrName name
  | "_x" `T.isInfixOf` name = T.concat $ go name
  | otherwise = name
  where
    go t =
      case T.breakOn "_x" t of
        (pre, rest)
          | T.null rest -> [pre]
          | otherwise ->
              let body = T.drop 2 rest
                  digits = T.takeWhile isHexDigit body
                  n = T.length digits
                  code = T.foldl' (\acc d -> 16 * acc + digitToInt d) 0 digits
               in if n >= 4
                    && n <= 6
                    && "_" `T.isPrefixOf` T.drop n body
                    && validChar code
                    then pre : T.singleton (chr code) : go (T.drop (n + 1) body)
                    else pre : "_x" : go body
    validChar code = code <= 0x10FFFF && not (code >= 0xD800 && code <= 0xDFFF)

-- the XML NameStartChar production, without the colon (which XML
-- parsers treat as a namespace separator)
isNameStartChar :: Char -> Bool
isNameStartChar c =
  isAsciiUpper c
    || isAsciiLower c
    || c == '_'
    || (c >= '\xC0' && c <= '\xD6')
    || (c >= '\xD8' && c <= '\xF6')
    || (c >= '\xF8' && c <= '\x2FF')
    || (c >= '\x370' && c <= '\x37D')
    || (c >= '\x37F' && c <= '\x1FFF')
    || (c >= '\x200C' && c <= '\x200D')
    || (c >= '\x2070' && c <= '\x218F')
    || (c >= '\x2C00' && c <= '\x2FEF')
    || (c >= '\x3001' && c <= '\xD7FF')
    || (c >= '\xF900' && c <= '\xFDCF')
    || (c >= '\xFDF0' && c <= '\xFFFD')
    || (c >= '\x10000' && c <= '\xEFFFF')

-- the XML NameChar production, without the colon
isNameChar :: Char -> Bool
isNameChar c =
  isNameStartChar c
    || isDigit c
    || c == '-'
    || c == '.'
    || c == '\xB7'
    || (c >= '\x300' && c <= '\x36F')
    || (c >= '\x203F' && c <= '\x2040')

-- 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"