Initial commit
Getty Ritter
11 years ago
| 1 | {-# LANGUAGE OverloadedStrings, RecordWildCards #-} | |
| 2 | ||
| 3 | module Data.NML.Parse (NMLPair(..), NMLElem(..), parseNML) where | |
| 4 | ||
| 5 | import Control.Applicative | |
| 6 | import Data.Attoparsec.Text | |
| 7 | import Data.Char (isAlpha,isNumber) | |
| 8 | import Data.Text (Text) | |
| 9 | import qualified Data.Text as T | |
| 10 | import Data.Text.Encoding (decodeUtf8) | |
| 11 | import Data.Word (Word8) | |
| 12 | import Prelude hiding (takeWhile) | |
| 13 | ||
| 14 | data NMLPair = NMLPair | |
| 15 | { pName :: Text | |
| 16 | , pAttrs :: [NMLPair] | |
| 17 | , pElems :: [NMLElem] | |
| 18 | } deriving (Eq, Show) | |
| 19 | ||
| 20 | data NMLElem | |
| 21 | = EEntity Text | |
| 22 | | EPair NMLPair | |
| 23 | | EText Text | |
| 24 | deriving (Eq, Show) | |
| 25 | ||
| 26 | parseNML :: Text -> Either String NMLPair | |
| 27 | parseNML = parseOnly prElement | |
| 28 | ||
| 29 | ---- | |
| 30 | ||
| 31 | isNameStartChar :: Char -> Bool | |
| 32 | isNameStartChar = inClass ":_A-Za-z" | |
| 33 | ||
| 34 | isNameChar :: Char -> Bool | |
| 35 | isNameChar = inClass "-:_A-Za-z.0-9" | |
| 36 | ||
| 37 | prName :: Parser Text | |
| 38 | prName = do | |
| 39 | c <- satisfy isNameStartChar | |
| 40 | cs <- many (satisfy isNameChar) | |
| 41 | return (T.pack (c:cs)) | |
| 42 | ||
| 43 | ---- | |
| 44 | ||
| 45 | prElement :: Parser NMLPair | |
| 46 | prElement = do | |
| 47 | string "<" | |
| 48 | pName <- prName | |
| 49 | skipSpace | |
| 50 | pAttrs <- many (prElement <* skipSpace) | |
| 51 | prFinish pName pAttrs <|> prElems pName pAttrs | |
| 52 | ||
| 53 | prEElement :: Parser NMLElem | |
| 54 | prEElement = fmap EPair prElement | |
| 55 | ||
| 56 | prFinish :: Text -> [NMLPair] -> Parser NMLPair | |
| 57 | prFinish pName pAttrs = do | |
| 58 | string ">" | |
| 59 | return (NMLPair { .. }) | |
| 60 | where pElems = [] | |
| 61 | ||
| 62 | prElems :: Text -> [NMLPair] -> Parser NMLPair | |
| 63 | prElems pName pAttrs = do | |
| 64 | string "|" | |
| 65 | pElems <- many (prEElement <|> prEntity <|> prText) | |
| 66 | string ">" | |
| 67 | return (NMLPair { .. }) | |
| 68 | ||
| 69 | prEntity :: Parser NMLElem | |
| 70 | prEntity = do | |
| 71 | string "[" | |
| 72 | en <- takeWhile1 (not . (== ']')) | |
| 73 | string "]" | |
| 74 | return (EEntity en) | |
| 75 | ||
| 76 | prText :: Parser NMLElem | |
| 77 | prText = do | |
| 78 | t <- takeWhile1 (not . (inClass "<>|")) | |
| 79 | return (EText t) |
| 1 | {-# LANGUAGE RecordWildCards #-} | |
| 2 | ||
| 3 | -- | Module for converting an internal NML datum to the | |
| 4 | -- @Text.XML.Light@ module provided by the package @xml@. | |
| 5 | module Data.NML.XMLLight | |
| 6 | ( decode | |
| 7 | , encode | |
| 8 | , module Text.XML.Light.Types | |
| 9 | ) where | |
| 10 | ||
| 11 | import Control.Monad ((>=>)) | |
| 12 | import Data.Text (Text, unpack, pack) | |
| 13 | import Text.XML.Light.Output (showContent) | |
| 14 | import Text.XML.Light.Types | |
| 15 | ||
| 16 | import Data.NML.Parse | |
| 17 | ||
| 18 | toMaybe :: Either a b -> Maybe b | |
| 19 | toMaybe (Right x) = Just x | |
| 20 | toMaybe _ = Nothing | |
| 21 | ||
| 22 | -- | Parse raw text to Content | |
| 23 | decode :: Text -> Maybe Content | |
| 24 | decode = toMaybe . (parseNML >=> toPair) | |
| 25 | ||
| 26 | -- | Convert Content to NML | |
| 27 | encode :: Content -> Text | |
| 28 | encode = pack . showContent | |
| 29 | ||
| 30 | -- | Take an NML @<x|y>@ pair to an XML node datum | |
| 31 | toPair :: NMLPair -> Either String Content | |
| 32 | toPair NMLPair { .. } = do | |
| 33 | elName <- toName pName | |
| 34 | elAttribs <- mapM toAttr pAttrs | |
| 35 | elContent <- mapM toElement pElems | |
| 36 | let elLine = Nothing | |
| 37 | return (Elem (Element { .. })) | |
| 38 | ||
| 39 | -- | Take an NML name to an XML name datum | |
| 40 | toName :: Text -> Either String QName | |
| 41 | toName n = return $ QName | |
| 42 | { qName = unpack n | |
| 43 | , qURI = Nothing | |
| 44 | , qPrefix = Nothing | |
| 45 | } | |
| 46 | ||
| 47 | -- | Take an NML @<x|y>@ pair to an XML attribute. Note that | |
| 48 | -- it is not valid for nested pairs to be attributes. | |
| 49 | toAttr :: NMLPair -> Either String Attr | |
| 50 | toAttr (NMLPair { pName = nm, pAttrs = [], pElems = [ EText t ] }) = do | |
| 51 | attrKey <- toName nm | |
| 52 | let attrVal = unpack t | |
| 53 | return Attr { .. } | |
| 54 | toAttr (NMLPair { pName = nm, pAttrs = [], pElems = [] }) = do | |
| 55 | attrKey <- toName nm | |
| 56 | let attrVal = "" | |
| 57 | return Attr { .. } | |
| 58 | toAttr p@(NMLPair { pAttrs = (_:_)}) = | |
| 59 | fail ("Attributes can't have their own attributes: " ++ show p) | |
| 60 | toAttr p = | |
| 61 | fail ("Attribute values must be raw text: " ++ show p) | |
| 62 | ||
| 63 | -- | Take an NML element to an XML content node | |
| 64 | toElement :: NMLElem -> Either String Content | |
| 65 | toElement (EEntity t) = | |
| 66 | return $ Text (CData { cdVerbatim = CDataRaw | |
| 67 | , cdData = "&" ++ unpack t ++ ";" | |
| 68 | , cdLine = Nothing | |
| 69 | }) | |
| 70 | toElement (EText t) = | |
| 71 | return $ Text (CData { cdVerbatim = CDataText | |
| 72 | , cdData = unpack t | |
| 73 | , cdLine = Nothing | |
| 74 | }) | |
| 75 | toElement (EPair p) = toPair p |
| 1 | {-# LANGUAGE RecordWildCards #-} | |
| 2 | ||
| 3 | -- | Module for converting an internal NML datum to the | |
| 4 | -- @Data.XML.Types@ module provided by the package @xml-types@. | |
| 5 | module Data.NML.XMLTypes | |
| 6 | ( decode | |
| 7 | , module Data.XML.Types | |
| 8 | ) where | |
| 9 | ||
| 10 | import Control.Monad ((>=>)) | |
| 11 | import Data.Text (Text) | |
| 12 | import Data.XML.Types | |
| 13 | ||
| 14 | import Data.NML.Parse | |
| 15 | ||
| 16 | toMaybe :: Either a b -> Maybe b | |
| 17 | toMaybe (Right x) = Just x | |
| 18 | toMaybe _ = Nothing | |
| 19 | ||
| 20 | -- | Parse raw text to Content | |
| 21 | decode :: Text -> Maybe Node | |
| 22 | decode = toMaybe . (parseNML >=> toPair) | |
| 23 | ||
| 24 | -- | Take an NML @<x|y>@ pair to an XML node datum | |
| 25 | toPair :: NMLPair -> Either String Node | |
| 26 | toPair NMLPair { .. } = do | |
| 27 | elementName <- toName pName | |
| 28 | elementAttributes <- mapM toAttr pAttrs | |
| 29 | elementNodes <- mapM toElement pElems | |
| 30 | return (NodeElement (Element { .. })) | |
| 31 | ||
| 32 | -- | Take an NML name to an XML name datum | |
| 33 | toName :: Text -> Either String Name | |
| 34 | toName n = return $ Name | |
| 35 | { nameLocalName = n | |
| 36 | , nameNamespace = Nothing | |
| 37 | , namePrefix = Nothing | |
| 38 | } | |
| 39 | ||
| 40 | -- | Take an NML @<x|y>@ pair to an XML attribute. Note that | |
| 41 | -- it is not valid for nested pairs to be attributes. | |
| 42 | toAttr :: NMLPair -> Either String (Name, [Content]) | |
| 43 | toAttr (NMLPair { pName = nm, pAttrs = [], pElems = [ EText t ] }) = do | |
| 44 | nm' <- toName nm | |
| 45 | return (nm', [ContentText t]) | |
| 46 | toAttr (NMLPair { pName = nm, pAttrs = [], pElems = [] }) = do | |
| 47 | nm' <- toName nm | |
| 48 | return (nm', []) | |
| 49 | toAttr p@(NMLPair { pAttrs = (_:_)}) = | |
| 50 | fail ("Attributes can't have their own attributes: " ++ show p) | |
| 51 | toAttr p = | |
| 52 | fail ("Attribute values must be raw text: " ++ show p) | |
| 53 | ||
| 54 | -- | Take an NML element to an XML content node | |
| 55 | toElement :: NMLElem -> Either String Node | |
| 56 | toElement (EEntity t) = return $ NodeContent (ContentEntity t) | |
| 57 | toElement (EText t) = return $ NodeContent (ContentText t) | |
| 58 | toElement (EPair p) = toPair p |
| 1 | -- | NML is a convenient syntax for writing XML documents without | |
| 2 | -- the excess line-noise while preserving (for the most part) the | |
| 3 | -- semantics and niceties of XML documents. NML was originally | |
| 4 | -- proposed by Erik Naggum, and his formulation can be seen at | |
| 5 | -- <http://www.schnada.de/grapt/eriknaggum-enamel.html#impressum>. | |
| 6 | -- | |
| 7 | -- This module exports the types 'NMLPair' and 'NMLElem' from | |
| 8 | -- "Data.NML.Parse", which are strictly more expressive than XML. | |
| 9 | -- For example, @\<one \<two|three\>\>@ corresponds to the XML fragment | |
| 10 | -- @\<one two=\"three\"/\>@, but @\<one \<two\<a|b\>|c\>\>@ has no XML | |
| 11 | -- analogue, as attributes cannot recursively possess attributes. This | |
| 12 | -- means that functions which render NML to XML generally return an | |
| 13 | -- @Either@ type, to account for possible failure. | |
| 14 | -- | |
| 15 | -- Other modules are provided that expose an interface in terms of | |
| 16 | -- the @xml@ package and the @xml-types@ package at | |
| 17 | -- "Data.NML.XMLLight" and "Data.NML.XMLTypes", respectively. | |
| 18 | ||
| 19 | module Data.NML | |
| 20 | ( encode | |
| 21 | , decode | |
| 22 | , module Data.NML.Parse | |
| 23 | ) where | |
| 24 | ||
| 25 | import Data.Text (Text, pack) | |
| 26 | ||
| 27 | import Data.NML.Parse | |
| 28 | ||
| 29 | toMaybe :: Either a b -> Maybe b | |
| 30 | toMaybe (Right x) = Just x | |
| 31 | toMaybe _ = Nothing | |
| 32 | ||
| 33 | -- | Parse an NML fragment as an NML Pair | |
| 34 | decode :: Text -> Maybe NMLPair | |
| 35 | decode = toMaybe . parseNML | |
| 36 | ||
| 37 | -- | Convert an NML Pair to an NML fragment | |
| 38 | encode :: NMLPair -> Text | |
| 39 | encode = undefined |
| 1 | Copyright (c) 2014, Getty Ritter | |
| 2 | ||
| 3 | All rights reserved. | |
| 4 | ||
| 5 | Redistribution and use in source and binary forms, with or without | |
| 6 | modification, are permitted provided that the following conditions are met: | |
| 7 | ||
| 8 | * Redistributions of source code must retain the above copyright | |
| 9 | notice, this list of conditions and the following disclaimer. | |
| 10 | ||
| 11 | * Redistributions in binary form must reproduce the above | |
| 12 | copyright notice, this list of conditions and the following | |
| 13 | disclaimer in the documentation and/or other materials provided | |
| 14 | with the distribution. | |
| 15 | ||
| 16 | * Neither the name of Getty Ritter nor the names of other | |
| 17 | contributors may be used to endorse or promote products derived | |
| 18 | from this software without specific prior written permission. | |
| 19 | ||
| 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 1 | name: nml | |
| 2 | version: 0.1.0.0 | |
| 3 | -- synopsis: | |
| 4 | -- description: | |
| 5 | license: BSD3 | |
| 6 | license-file: LICENSE | |
| 7 | author: Getty Ritter | |
| 8 | maintainer: gettylefou@gmail.com | |
| 9 | -- copyright: | |
| 10 | category: Data | |
| 11 | build-type: Simple | |
| 12 | -- extra-source-files: | |
| 13 | cabal-version: >=1.10 | |
| 14 | ||
| 15 | library | |
| 16 | exposed-modules: Data.NML, Data.NML.XMLLight, Data.NML.XMLTypes | |
| 17 | other-modules: Data.NML.Parse | |
| 18 | -- other-extensions: | |
| 19 | build-depends: base >=4.6 && <4.7, xml, xml-types, text, attoparsec | |
| 20 | default-language: Haskell2010 |