hgeometry-0.4.0.0: src/Data/Geometry/Ipe/Reader.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
module Data.Geometry.Ipe.Reader where
import Data.Either(rights)
import Control.Applicative
import Control.Lens
import Data.Ext
import qualified Data.Foldable as F
import Data.Vinyl
import Data.Validation
import Data.Geometry.Point
import Data.Geometry.Line
import Data.Geometry.LineSegment
import Data.Geometry.PolyLine
import Data.Geometry.Ipe.Types
import Data.Geometry.Ipe.Attributes
import qualified Data.ByteString as B
import Data.Monoid
import Data.Text(Text)
import Data.Geometry.Ipe.PathParser
import Text.XML.Expat.Tree
import qualified Data.Text as T
-- fromIpeFile :: (Coordinate r, IpeRead t) => FilePath -> IO [PolyLine 2 () r]
-- fromIpeFile
fromIpeXML :: (Coordinate r, IpeRead t) => B.ByteString -> Either ConversionError (t r)
fromIpeXML b = (bimap (T.pack . show) id $ parse' defaultParseOptions b) >>= ipeRead
class IpeReadText t where
ipeReadText :: Coordinate r => Text -> Either ConversionError (t r)
type ConversionError = Text
-- TODO: We also want to do something with the attributes
class IpeRead t where
ipeRead :: Coordinate r => Node Text Text -> Either ConversionError (t r)
-- instance IpeRead IpeSymbol where
-- ipeRead (Element "use" ats _) = case extract ["pos","name"] ats of
-- given a list of keys, and a list of attributes. Extracts the values matching
-- the keys. The result is a pair (vs,others), where others are the remaining
-- attributes, and vs are the values corresponding to the keys. Sorted on
-- increasing order of their keys.
extract :: [Text] -> [(Text,Text)] -> ([Text],[(Text,Text)])
extract = undefined
instance IpeReadText (PolyLine 2 ()) where
ipeReadText t = readPathOperations t >>= fromOps
where
fromOps (MoveTo p:LineTo q:ops) = (\ps -> fromPoints $ [p,q] ++ ps)
<$> validateAll "Expected LineTo p" _LineTo ops
fromOps _ = Left "Expected MoveTo p:LineTo q:... "
validateAll :: ConversionError -> Prism' (Operation r) (Point 2 r) -> [Operation r]
-> Either ConversionError [Point 2 r]
validateAll err fld = bimap T.unlines id . validateAll' err fld
validateAll' :: err -> Prism' (Operation r) (Point 2 r) -> [Operation r]
-> Either [err] [Point 2 r]
validateAll' err field = toEither . foldr (\op res -> f op <> res) (Right' [])
where
f op = maybe (Left' [err]) (\p -> Right' [p]) $ op ^? field
toEither = either' Left Right
-- This is a bit of a hack
instance IpeRead (PolyLine 2 ()) where
ipeRead (Element "path" ats ts) = ipeReadText . T.unlines . map unText $ ts
-- apparently hexpat already splits the text into lines
ipeRead _ = Left "iperead: no polyline."
unText (Text t) = t
instance IpeRead PathSegment where
ipeRead = fmap PolyLineSegment . ipeRead
testP :: B.ByteString
testP = "<path stroke=\"black\">\n128 656 m\n224 768 l\n304 624 l\n432 752 l\n</path>"
testO :: Text
testO = "\n128 656 m\n224 768 l\n304 624 l\n432 752 l\n"
testPoly :: Either Text (PolyLine 2 () Double)
testPoly = fromIpeXML testP
-- ipeRead' :: [Element Text Text]
-- ipeRead' = map ipeRead
-- instance IpeRead (IpePage gs) where
-- ipeRead (Element "page" ats chs) = Right . IpePage [] [] . fromList' . rights $ map ipeRead chs
-- where
-- fromList' = Group' . foldr (\x r -> (IpeObject x :& RNil) :& r) RNil
-- ipeRead _ = Left "ipeRead: Not a page"
readPolyLines :: Coordinate r => Node Text Text -> [PolyLine 2 () r]
readPolyLines (Element "ipe" _ chs) = concatMap readPolyLines' chs
readPolyLines' :: Coordinate r => Node Text Text -> [PolyLine 2 () r]
readPolyLines' (Element "page" _ chs) = rights $ map ipeRead chs
readPolyLines' _ = []
polylinesFromIpeFile :: (Coordinate r) => FilePath -> IO [PolyLine 2 () r]
polylinesFromIpeFile = fmap readPolies . B.readFile
where
readPolies = either (const []) readPolyLines . parse' defaultParseOptions