packages feed

PageIO 0.0.2 → 0.0.3

raw patch · 11 files changed

+1074/−241 lines, 11 filesdep +attoparsecdep +base64-stringdep +directorydep −parsecsetup-changednew-component:exe:pioPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: attoparsec, base64-string, directory, network, old-time, regex-base, regex-compat, regex-tdfa, sqlite, utf8-string, uuid

Dependencies removed: parsec

API changes (from Hackage documentation)

- Text.PageIO.Parser: ret :: a -> Parser a
+ Text.PageIO.Extract: fieldLen :: Field -> Int
+ Text.PageIO.Extract: type Bound = Value
+ Text.PageIO.Index: blockProduct :: [(Area, LabelMap Bound)] -> BlockResult -> [(Area, LabelMap Bound)]
+ Text.PageIO.Index: fromYMD :: Int -> Int -> Int -> String
+ Text.PageIO.Index: indexDocs :: Sheet -> [Doc] -> IO ()
+ Text.PageIO.Infer: MkDateMatch :: Int -> Bool -> (Int, Int) -> (Int, Int) -> Maybe (Int, Int) -> DateMatch
+ Text.PageIO.Infer: data DateMatch
+ Text.PageIO.Infer: dateMatchToFields :: DateMatch -> [(Label, Field)]
+ Text.PageIO.Infer: datePattern :: Value
+ Text.PageIO.Infer: inferPageDateFields :: Page -> [(Label, Field)]
+ Text.PageIO.Infer: inferPageNameField :: Page -> (Label, Field)
+ Text.PageIO.Infer: inferSheet :: FilePath -> [Page] -> Sheet
+ Text.PageIO.Infer: instance Eq DateMatch
+ Text.PageIO.Infer: instance Ord DateMatch
+ Text.PageIO.Infer: instance Show DateMatch
+ Text.PageIO.Infer: matchDay :: DateMatch -> Maybe (Int, Int)
+ Text.PageIO.Infer: matchIsROC :: DateMatch -> Bool
+ Text.PageIO.Infer: matchLine :: DateMatch -> Int
+ Text.PageIO.Infer: matchMonth :: DateMatch -> (Int, Int)
+ Text.PageIO.Infer: matchYear :: DateMatch -> (Int, Int)
+ Text.PageIO.Infer: mkField :: String -> Col -> Row -> Col -> Row -> (Label, Field)
+ Text.PageIO.Infer: namePattern :: Value
+ Text.PageIO.Infer: tryMatchDate :: (Int, Value) -> [DateMatch]
+ Text.PageIO.Infer: tryMatchName :: (Int, Value) -> [(Label, Field)]
+ Text.PageIO.Infer: validateMatch :: MatchText Value -> Maybe DateMatch
+ Text.PageIO.LabelMap: null :: LabelMap a -> Bool
+ Text.PageIO.Parser: (<$$>) :: (Functor f) => f a -> (a -> b) -> f b
+ Text.PageIO.Parser: between :: Parser a -> Parser b -> Parser c -> Parser c
+ Text.PageIO.Parser: choice :: (Alternative f) => [f a] -> f a
+ Text.PageIO.Parser: commaSep :: Parser a -> Parser [a]
+ Text.PageIO.Parser: liftA4 :: (Applicative f) => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e
+ Text.PageIO.Parser: liftA5 :: (Applicative f) => (a -> b -> c -> d -> e -> result) -> f a -> f b -> f c -> f d -> f e -> f result
+ Text.PageIO.Parser: many1 :: (Alternative f) => f a -> f [a]
+ Text.PageIO.Parser: noneOf :: [Char] -> Parser Char
+ Text.PageIO.Parser: oneOf :: [Char] -> Parser Char
+ Text.PageIO.Parser: parseFromFile :: Parser a -> String -> IO (Either ParseError a)
+ Text.PageIO.Parser: r :: Parser a -> Parser a
+ Text.PageIO.Parser: replaceVariable :: Parser Variable
+ Text.PageIO.Parser: sepEndBy :: (Alternative f) => f a -> f b -> f [a]
+ Text.PageIO.Parser: sepEndBy1 :: (Alternative f) => f a -> f b -> f [a]
+ Text.PageIO.Parser: substrVariable :: Parser Variable
+ Text.PageIO.Transform: constToPattern :: Sheet -> Sheet
+ Text.PageIO.Transform: doExpandFields :: LabelMap Field -> Ordered FieldBinding -> Ordered FieldBinding
+ Text.PageIO.Transform: doGroupArea :: [Label] -> LabelMap Field -> [Ordered FieldBinding] -> [Ordered FieldBinding]
+ Text.PageIO.Transform: doGroupBlockData :: LabelMap [Label] -> LabelMap (LabelMap Field) -> Label -> BlockData -> BlockData
+ Text.PageIO.Transform: doGroupBy :: [(Block, LabelMap Bound)] -> [(Block, LabelMap Bound)]
+ Text.PageIO.Transform: doGroupRows :: LabelMap Field -> [Ordered FieldBinding] -> Ordered FieldBinding
+ Text.PageIO.Transform: docValueMap :: DocBinding -> !ValueMap
+ Text.PageIO.Transform: formatFloat :: Int -> Int -> Value
+ Text.PageIO.Transform: formatInt :: Int -> Int -> Value
+ Text.PageIO.Transform: gatherForSheetBlock :: (Block -> a) -> Sheet -> LabelMap a
+ Text.PageIO.Transform: replacePage :: (Page, Box) -> (Block, LabelMap Bound) -> (Page, Box)
+ Text.PageIO.Transform: replaceWith :: Value -> Value -> Value -> Value
+ Text.PageIO.Transform: sheetBlockFields :: Sheet -> LabelMap (LabelMap Field)
+ Text.PageIO.Transform: sheetBlockGroupBys :: Sheet -> LabelMap [Label]
+ Text.PageIO.Transform: type FieldBinding = (Area, LabelMap Bound)
+ Text.PageIO.Types: VReplace :: !Label -> ![(Value, Value)] -> Variable
+ Text.PageIO.Types: VSubStr :: !Label -> !Int -> !Int -> Variable
+ Text.PageIO.Types: blockGroupBy :: Block -> ![Label]
+ Text.PageIO.Types: blockUsePriority :: Block -> !Bool
+ Text.PageIO.Types: instance Monoid Page
+ Text.PageIO.Types: matchSubstring :: Value -> Match -> (Maybe Int)
+ Text.PageIO.Types: sheetOrderBy :: Sheet -> ![Label]
+ Text.PageIO.Types: sheetUseBlockSortPriority :: Sheet -> !Bool
+ Text.PageIO.Types: vDrop :: Variable -> !Int
+ Text.PageIO.Types: vMatchReplace :: Variable -> ![(Value, Value)]
+ Text.PageIO.Types: vTake :: Variable -> !Int
- Text.PageIO.Extract: MkBlockResult :: [(Area, LabelMap Value)] -> BlockResult
+ Text.PageIO.Extract: MkBlockResult :: [(Area, LabelMap Bound)] -> BlockResult
- Text.PageIO.Extract: MkSheetResult :: !LabelMap Value -> !LabelMap BlockResult -> SheetResult
+ Text.PageIO.Extract: MkSheetResult :: !LabelMap Bound -> !LabelMap BlockResult -> SheetResult
- Text.PageIO.Extract: blockResults :: BlockResult -> [(Area, LabelMap Value)]
+ Text.PageIO.Extract: blockResults :: BlockResult -> [(Area, LabelMap Bound)]
- Text.PageIO.Extract: extractField :: Page -> Field -> Value
+ Text.PageIO.Extract: extractField :: Page -> Field -> Bound
- Text.PageIO.Extract: resultFields :: SheetResult -> !LabelMap Value
+ Text.PageIO.Extract: resultFields :: SheetResult -> !LabelMap Bound
- Text.PageIO.Transform: MkBlockData :: !Row -> ![Ordered Area] -> BlockData
+ Text.PageIO.Transform: MkBlockData :: !Row -> ![Ordered FieldBinding] -> BlockData
- Text.PageIO.Transform: MkDocBinding :: !SheetResult -> !Page -> !PageBinding -> DocBinding
+ Text.PageIO.Transform: MkDocBinding :: !ValueMap -> !SheetResult -> !Page -> !PageBinding -> DocBinding
- Text.PageIO.Transform: dataAreas :: BlockData -> ![Ordered Area]
+ Text.PageIO.Transform: dataAreas :: BlockData -> ![Ordered FieldBinding]
- Text.PageIO.Transform: doAttempt :: LabelMap [OrderBy Label] -> SheetResult -> FitAttempt -> FitAttempt
+ Text.PageIO.Transform: doAttempt :: LabelMap [OrderBy Label] -> FitAttempt -> SheetResult -> FitAttempt
- Text.PageIO.Transform: doCapacity :: Sheet -> SheetResult -> [PageCapacity] -> [PageCapacity]
+ Text.PageIO.Transform: doCapacity :: Sheet -> [PageCapacity] -> SheetResult -> [PageCapacity]
- Text.PageIO.Transform: sheetVariableFields :: Sheet -> LabelMap Field
+ Text.PageIO.Transform: sheetVariableFields :: Sheet -> (LabelMap Field, LabelMap Field)
- Text.PageIO.Transform: type PageBinding = LabelMap [Area]
+ Text.PageIO.Transform: type PageBinding = LabelMap [FieldBinding]
- Text.PageIO.Types: MkBlock :: !Row -> !LabelMap Pattern -> !LabelMap Field -> ![OrderBy Label] -> ![Filter] -> Block
+ Text.PageIO.Types: MkBlock :: !Row -> !LabelMap Pattern -> !LabelMap Field -> ![OrderBy Label] -> ![Label] -> ![Filter] -> !Bool -> Block
- Text.PageIO.Types: MkSheet :: !Label -> !Box -> !LabelMap Pattern -> !LabelMap Field -> ![Frame] -> ![Label] -> Sheet
+ Text.PageIO.Types: MkSheet :: !Label -> !Box -> !LabelMap Pattern -> !LabelMap Field -> ![Frame] -> ![Label] -> ![Label] -> !Bool -> Sheet

Files

PageIO.cabal view
@@ -1,5 +1,5 @@ name:               PageIO-version:            0.0.2+version:            0.0.3 copyright:          2008 Audrey Tang license:            BSD3 license-file:       LICENSE@@ -12,13 +12,25 @@                     (Extremely experimental, no documentations at the moment!) stability:          experimental build-type:         Simple-extensions:         GeneralizedNewtypeDeriving, RecordPuns, ParallelListComp, PatternGuards+extensions:         GeneralizedNewtypeDeriving, RecordPuns, ParallelListComp, PatternGuards, PatternSignatures exposed-modules:    Text.PageIO                     Text.PageIO.Extract Text.PageIO.LabelMap                     Text.PageIO.Parser Text.PageIO.Run                     Text.PageIO.Transform Text.PageIO.Types -build-depends:      base, array, bytestring, parsec >= 3.0, containers, stringtable-atom, iconv+                    Text.PageIO.Infer Text.PageIO.Index+build-depends:      base, array, bytestring, attoparsec >= 0.4, containers,+                    stringtable-atom, iconv, uuid, sqlite, utf8-string,+                    network, base64-string, regex-compat, regex-base, regex-tdfa, old-time, directory extra-source-files: README+extra-lib-dirs:     /usr/lib /usr/local/lib+include-dirs:       /usr/include /usr/local/include hs-source-dirs:     src category:           Text-ghc-options:        -O2+-- ghc-options:        -O++executable:         pio+main-is:            pio.hs+hs-source-dirs:     ., src+extra-lib-dirs:     /usr/lib /usr/local/lib+include-dirs:       /usr/include /usr/local/include+-- ghc-options:        -prof -auto-all
Setup.lhs view
@@ -1,3 +1,14 @@ #!/usr/bin/env runhaskell > import Distribution.Simple-> main = defaultMain+> import System.Cmd+> import System.Exit+> import System.Directory+> main = defaultMainWithHooks (simpleUserHooks { runTests = quickCheck } )+>     where+>     quickCheck _ _ _ _ = do+>         ec <- system $ "ghc --make -odir dist/build -hidir dist/build -idist/build:src t/runTests.hs -L/usr/lib -liconv -luuid -o t/runTests"+>         case ec of+>             ExitSuccess -> do+>                 system "t/runTests"+>             _           -> return ec+>         return ()
+ pio.hs view
@@ -0,0 +1,54 @@+module Main where+import Text.PageIO+import System.Exit+import System.Environment (getArgs, getProgName)++import Codec.Text.IConv+import qualified Data.ByteString.Char8 as S++main :: IO ()+main = do+    args            <- getArgs+    case args of+        ("--dump":rest) -> do+            (_, docs)   <- runFromArgs rest+            (`mapM_` docs) $ \(MkDoc _ lns) -> do+                let lns' = S.lines . packLBS $ convert "CP950" "UTF-8" lns+                (`mapM_` lns') $ S.putStrLn+        ("--meta":rest) -> do+            (_, docs)   <- runFromArgs rest+            (`mapM_` docs) $ \(MkDoc meta _) -> print meta+        _   -> do+            (sheet, docs)   <- runFromArgs args+            indexDocs sheet docs++runFromArgs args = case args of+    [fileIn] -> do+        pagesIn  <- readPages fileIn++        let docs = parsePages sheetIn pagesIn+            sheetIn = inferSheet fileIn pagesIn+        return (sheetIn, docs)++    [fileIn, duxIn] -> do+        sheetIn  <- readSheet duxIn+        pagesIn  <- readPages fileIn++        let docs = parsePages sheetIn pagesIn+        return (sheetIn, docs)++    (fileIn:duxIn:fileOut:duxOut:_) -> do+        sheetIn  <- readSheet duxIn+        sheetOut <- readSheet duxOut++        pagesIn  <- readPages fileIn+        pagesOut <- readPages fileOut++        let docs = transformPages sheetIn pagesIn sheetOut pagesOut+        return (sheetOut, docs)++    _ -> do+        prog <- getProgName+        putStrLn $ "Usage: " ++ prog ++ " input.txt [input.dux] [output.txt] [output.dux]"+        exitWith ExitSuccess+
src/Text/PageIO.hs view
@@ -3,8 +3,12 @@     , module Text.PageIO.Parser     , module Text.PageIO.Transform     , module Text.PageIO.Extract+    , module Text.PageIO.Index+    , module Text.PageIO.Infer     ) where import Text.PageIO.Run import Text.PageIO.Parser import Text.PageIO.Transform import Text.PageIO.Extract+import Text.PageIO.Index+import Text.PageIO.Infer
src/Text/PageIO/Extract.hs view
@@ -1,19 +1,22 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, RecordPuns #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, RecordPuns, PatternGuards #-} {-# OPTIONS -fno-warn-name-shadowing -funbox-strict-fields #-}  module Text.PageIO.Extract where import Data.Maybe import Data.Monoid import Text.PageIO.Types+import Control.Applicative import qualified Text.PageIO.LabelMap as LM import qualified Data.ByteString.Char8 as S  type Area = Page-newtype BlockResult = MkBlockResult { blockResults :: [(Area, LabelMap Value)] }+newtype BlockResult = MkBlockResult { blockResults :: [(Area, LabelMap Bound)] }     deriving (Eq, Ord, Monoid) +type Bound = Value+ data SheetResult = MkSheetResult-    { resultFields  :: !(LabelMap Value)+    { resultFields  :: !(LabelMap Bound)     , resultBlocks  :: !(LabelMap BlockResult)     }     deriving (Eq, Ord)@@ -53,17 +56,18 @@         }     | otherwise          = Nothing     where-    sheetArea = crop sheetBox page-    patternResults = map (checkPattern sheetArea) (LM.elems sheetPatterns)-    fieldResults   = fmap (extractField sheetArea) sheetFields-    frameResults   = LM.unionsWith mappend (map extractFrame sheetFrames)-    extractFrame MkFrame{ frameBox, frameBlocks } = LM.fromListWith mappend blockResults+--    sheetArea = crop sheetBox page+    sheetArea = page+    patternResults = checkPattern sheetArea <$> LM.elems sheetPatterns+    fieldResults   = extractField sheetArea <$> sheetFields+    frameResults   = LM.unionsWith mappend $ extractFrame <$> sheetFrames+    extractFrame MkFrame{ frameBox, frameBlocks } = LM.fromListWith mappend $ reverse blockResults         where         frameArea    = crop frameBox sheetArea         blockResults = extractBlocks frameBlocks frameArea  crop :: Box -> Page -> Page-crop MkBox{boxTop, boxBottom, boxLeft, boxRight} (MkPage lns) = MkPage (map doCol (doRows lns))+crop MkBox{boxTop, boxBottom, boxLeft, boxRight} (MkPage lns) = MkPage $ doCol <$> doRows lns     where     doRows = take (boxBottom - boxTop + 1) . drop (boxTop - 1)     doCol = S.take (boxRight - boxLeft + 1) . S.drop (boxLeft - 1)@@ -75,13 +79,28 @@     where     val = pageVal $ crop patternBox page -extractField :: Page -> Field -> Value-extractField page MkField{ fieldBox, fieldFormat } = case fieldFormat of+extractField :: Page -> Field -> Bound+extractField page fld@MkField{ fieldBox, fieldFormat } = case fieldFormat of+--    FNumeric 2  -> formatDotted (fieldLen fld) (S.unpack (valToIntVal val))     FNumeric{}  -> valToIntVal val-    _           -> val+    _ | Just idx <- S.elemIndexEnd '.' val+      , frac <- S.drop idx val+      -> S.append (formatDotted (fieldLen fld - S.length frac) (show $ valToInt val)) frac+      | otherwise -> val     where     val = pageVal $ crop fieldBox page +fieldLen :: Field -> Int+fieldLen MkField{ fieldBox } = boxRight fieldBox - boxLeft fieldBox + 1++formatDotted len n = S.pack $ (replicate pad ' ') ++ dottedStr+    where+    pad         = len - length dottedStr + 3+    str         = n+    dottedStr   = reverse (addDot $ reverse str)+    addDot (x:y:z:rest@(_:_))   = (x:y:z:',':addDot rest)+    addDot xs                   = xs+ extractBlocks :: LabelMap Block -> Page -> [(Label, BlockResult)] extractBlocks blocks page@(MkPage lns) = case blockResults of     []              -> case lns of@@ -104,12 +123,12 @@     | otherwise                       = Nothing     where     blockArea       = take blockLines lns-    patternResults  = map (checkPattern page) (LM.elems blockPatterns)-    fieldResults    = fmap (extractField page) blockFields+    patternResults  = checkPattern page <$> LM.elems blockPatterns+    fieldResults    = extractField page <$> blockFields     filtersPass     = all runFilter blockFilterBy     runFilter (flt@MkFilter{ filterField, filterOperator, filterMatch })         = case LM.lookup filterField fieldResults of-            Just val    -> case filterOperator of+            Just val -> case filterOperator of                 ONot op     -> not $ runFilter flt{ filterOperator = op }                 OContains   -> val `matches` filterMatch                 OStartsWith -> matchValue filterMatch `S.isPrefixOf` val 
+ src/Text/PageIO/Index.hs view
@@ -0,0 +1,299 @@+{-# LANGUAGE RecordPuns #-}+{-# OPTIONS -fno-warn-name-shadowing #-}+module Text.PageIO.Index where+import Codec.Text.IConv+import Control.Exception (try)+import Control.Monad (unless)+import Data.Char (isSpace)+import Data.List (nub, sort, intersperse)+import Data.Monoid (mappend)+import Database.SQLite+import System.Environment+import System.Time+import Debug.Trace+import Text.PageIO.Extract+import Text.PageIO.Parser (packLBS)+import Text.PageIO.Transform+import Text.PageIO.Types+import Text.Printf+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.ByteString.UTF8 as UTF8+import qualified Text.PageIO.LabelMap as LM++addTables dbh = mapM_ (execStatement_ dbh)+    [ "CREATE VIRTUAL TABLE doc USING FTS3 ( head TEXT, body TEXT )"+    , "CREATE TABLE idx ( pri_nid TEXT, sec_nid TEXT, doc_name INT )"+    , "CREATE UNIQUE INDEX doc_name_idx ON idx (doc_name)"+    , "CREATE TABLE txt ( body TEXT )"+    ]++addColumn dbh cols = (`mapM_` cols) $ \col -> mapM_ (execStatement_ dbh)+    [ "ALTER TABLE idx ADD COLUMN '" ++ col ++ "' COLLATE NOCASE"+    , "CREATE INDEX '" ++ col ++ "_idx' ON idx ('" ++ col ++ "' COLLATE NOCASE)"+    ]++indexDocs :: Sheet -> [Doc] -> IO ()+indexDocs MkSheet{ sheetName, sheetFields, sheetFrames, sheetBox = MkBox{ boxRight, boxBottom } } docs = do+    env <- getEnvironment++    let dbName = case lookup "PIO_DB" env of+            Just n  -> n+            _       -> LM.fromLabel sheetName++    putStr dbName++    dbh <- openConnection $ dbName ++ ".db"++    -- XXX - Suspicious overload: pri_nid means "cols" and sec_nid means "rows"+    addTables dbh+    addColumn dbh columns++    dbhDetails <- case lookup "PIO_DETAILS_DB" env of+        Just n -> do+            h <- openConnection $ n ++ ".db"+            addTables h+            addColumn h ("fulltext":detailColumns)+            execStatement_ h "BEGIN EXCLUSIVE TRANSACTION"+            return (Just h)+        _ -> return Nothing++    execStatement_ dbh "BEGIN EXCLUSIVE TRANSACTION"++    (`mapM_` ([1..] `zip` docs)) $ \(docIndex, MkDoc meta contents) -> try $ do+        putStr "."+        let fields  = resultFields meta+            body    = UTF8.toString . packLBS $ convert "CP950" "UTF-8" contents++        let valOf x = LM.lookup (LM.toLabel x) fields+            year = case valOf "year_roc" of+                Just year_roc   -> valToInt year_roc + 11+                _               -> case valOf "year" of+                    Just year   -> valToInt year+                    _           -> 0+            r_date  | year == 0   = "0"+                    | otherwise   =+                let month = maybe 1 valToInt (valOf "month")+                    day   = maybe 1 valToInt (valOf "day")+                 in fromYMD (fixY2K year) month day++            fixY2K year | year >= 1900  = year+                        | year >= 70    = 1900 + year+                        | otherwise     = 2000 + year++        let attrsVanilla =+                [ (LM.fromLabel lbl, dropWhile isSpace (decode val))+                | (lbl, val) <- LM.toList fields+                ]+            -- XXX - Blob?+            decode bound = UTF8.toString . packLBS $ convert "CP950" "UTF-8" (L.fromChunks [bound]) +            attrs = (("r_id", LM.fromLabel sheetName):("r_date", r_date):attrsVanilla)+            attrHead = unlines (map snd attrs)+            cols = concatMap ((++ "'") . (", '" ++) . fst) attrs+            prms = concatMap ((++ "'") . (", '" ++) . snd) attrs++        execStatement_ dbh $ concat+            [ "INSERT INTO doc (head, body) VALUES ('"+            , attrHead+            , "', '"+            , body+            , "')"+            ]++        execStatement_ dbh $ concat+            [ "INSERT INTO idx (doc_name, pri_nid, sec_nid"+            , cols +            , ") VALUES (last_insert_rowid(), "+            , show boxRight+            , ", "+            , show boxBottom+            , prms+            , ")"+            ]++        let blocks  = LM.elems $ resultBlocks meta++        case dbhDetails of+            Just h -> do+                execStatement_ h $ concat+                    [ "INSERT INTO txt (rowid, body) VALUES ("+                    , show docIndex+                    , ", '"+                    , body+                    , "')"+                    ]+                (`mapM_` foldl blockProduct [] blocks) $ \(area, vals) -> try $ unless (LM.null vals) $ do+                    let attrsBlock =+                            [ (LM.fromLabel lbl, dropWhile isSpace (decode val))+                            | (lbl, val) <- LM.toList vals+                            ]+                        attrs' = attrs ++ map maybeFix attrsBlock+                        attrHead' = unlines (map snd attrs')+                        cols' = concatMap ((++ "'") . (", '" ++) . fst) attrs'+                        prms' = concatMap ((++ "'") . (", '" ++) . snd) attrs'+                        maybeFix ("expiry_date", date) = ("expiry_date", parseDate date)+                        maybeFix (c@['F','D','S','D',x,'D'], date@(_:_))+                            | x == 'B' || x == 'E' || x == 'O'+                            = (c, parseDate date)+                        maybeFix x = x+                        parseDate date = fromYMD y m d+                            where+                            i = read date+                            y = 1911 + (i `div` 10000)+                            m = i `mod` 10000 `div` 100+                            d = i `mod` 100++                    execStatement_ h $ concat+                        [ "INSERT INTO doc (head, body) VALUES ('"+                        , attrHead'+                        , "', '"+                        , concatMap decode (pageLines area)+                        , "')"+                        ]++                    execStatement_ h $ concat+                        [ "INSERT INTO idx (doc_name, fulltext, pri_nid, sec_nid"+                        , cols'+                        , ") VALUES (last_insert_rowid(), '"+                        , show docIndex+                        , "', "+                        , show boxRight+                        , ", "+                        , show boxBottom+                        , prms'+                        , ")"+                        ]+                    return ()+            _ -> return ()+    execStatement_ dbh "COMMIT"++    case dbhDetails of+        Just h  -> execStatement_ h "COMMIT"+        _       -> return Nothing++    closeConnection dbh+    +    putStrLn "done!"+    where+    columns = nub $ sort ("r_date":"r_id":map LM.fromLabel (LM.keys sheetFields))+    detailColumns = nub $ sort ("r_date":"r_id":map LM.fromLabel (concatMap LM.keys (sheetFields : frameFields)))+    frameFields = concatMap (map blockFields . LM.elems . frameBlocks) sheetFrames++blockProduct :: [(Area, LabelMap Bound)] -> BlockResult -> [(Area, LabelMap Bound)]+blockProduct [] (MkBlockResult ys) = ys+blockProduct xs (MkBlockResult []) = xs+blockProduct xs (MkBlockResult ys) =+    [ (xa `mappend` ya, xb `mappend` yb)+    | (xa, xb)  <- xs+    , (ya, yb)  <- ys+    ]++fromYMD :: Int -> Int -> Int -> String+fromYMD y m d = case toClockTime cal of+    TOD sec _ -> show (succ (sec `div` 86400))+    where+    cal = CalendarTime+        { ctYear    = y+        , ctMonth   = toEnum (m-1)+        , ctDay     = d+        , ctHour    = 0+        , ctMin     = 0+        , ctSec     = 0+        , ctPicosec = 0+        , ctWDay    = Sunday+        , ctYDay    = 0+        , ctTZName  = "UTC"+        , ctTZ      = 0+        , ctIsDST   = False+        }+    +{-+import Codec.Binary.Base64.String+import Data.Maybe (fromJust)+import Data.UUID (generate, toStringUpper)+import Network.URI+import Text.HyperEstraier+import Text.PageIO.Extract+import Text.PageIO.Parser (packLBS)+import Text.PageIO.Transform+import Text.PageIO.Types+import System.Environment+import qualified Text.PageIO.LabelMap as LM+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L++indexDocs :: Sheet -> [Doc] -> IO ()+indexDocs MkSheet{ sheetName, sheetBox = MkBox{ boxRight, boxBottom } } docs = do+    -- removeDirectoryRecursive "casket"+    withDatabase "casket" (Writer [Create [], WriteLock NoLock]) $ \db -> do+    withDatabase "Details" (Writer [Create[], Truncate [], WriteLock NoLock]) $ \db2 -> do+    (`mapM_` docs) $ \(MkDoc meta contents) -> try $ do+        doc     <- newDocument+        uuid    <- generate+        let fields  = resultFields meta+            lns     = S.lines . packLBS $ convert "CP950" "UTF-8" contents+            uri     = parseURI $ "urn:uuid:" ++ (toStringUpper uuid)++        setURI doc uri+        (`mapM_` lns) $ addText doc . UTF8.toString ++        let valOf x = LM.lookup (LM.toLabel x) fields+            year = case valOf "year_roc" of+                Just year_roc   -> valToInt year_roc + 11+                _               -> case valOf "year" of+                    Just year   -> valToInt year+                    _           -> 0+            r_date  | year == 0   = Nothing+                    | otherwise   =+                let month = maybe 1 valToInt (valOf "month")+                    day   = maybe 1 valToInt (valOf "day")+                 in Just $ printf "%04d-%02d-%02d 00:00:00"+                    (fixY2K year)+                    (month) +                    (day)+            fixY2K year | year >= 1900  = year+                        | year >= 70    = 1900 + year+                        | otherwise     = 2000 + year+        let addAttributes d = do+                (`mapM_` LM.toList fields) $ \(lbl, bound) -> try $ do+                    setAttribute d (LM.fromLabel lbl) $ Just+                        ( UTF8.toString . packLBS $ convert "CP950" "UTF-8" (L.fromChunks [bound]) )+                setAttribute d "__text__" $ Just (encode . L.unpack $ contents)+                setAttribute d "__cols__" $ Just (show boxRight)+                setAttribute d "__rows__" $ Just (show boxBottom)+                setAttribute d "r_date" r_date+                setAttribute d "r_id" $ Just (LM.fromLabel sheetName)+                setAttribute d "__index_code__" $ Just ""+                maybe (return ()) (addHiddenText doc) r_date+                addHiddenText doc (LM.fromLabel sheetName)++        addAttributes doc+        putDocument db doc []+        docID <- getId doc+        print (docID, fromJust uri)++        env <- getEnvironment+        unless (lookup "PIO" env == Just "1") $ do++        let blocks  = LM.elems $ resultBlocks meta+        (`mapM_` foldl blockProduct [] blocks) $ \(area, vals) -> try . unless (LM.null vals) $ do+            doc2    <- newDocument+            uuid2   <- generate+            let uri2 = parseURI $ "urn:uuid:" ++ (toStringUpper uuid2)+            setURI doc2 uri2++            (`mapM_` pageLines area) $ \val -> do+                addText doc2 $+                    ( UTF8.toString . packLBS $ convert "CP950" "UTF-8" (L.fromChunks [val]) )++            (`mapM_` LM.toList vals) $ \(lbl, val) -> do+                let str = UTF8.toString . packLBS $ convert "CP950" "UTF-8" (L.fromChunks [val])+                addHiddenText doc2 str+                setAttribute doc2 (LM.fromLabel lbl) $ Just str++            setAttribute doc2 "__index_code__" $ Just ""++            addAttributes doc2+            putDocument db2 doc2 []+            docID2 <- getId doc2+            print (docID2, fromJust uri2)+-}
+ src/Text/PageIO/Infer.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE RecordPuns, ParallelListComp, PatternGuards, PatternSignatures #-}+module Text.PageIO.Infer where+import Text.PageIO.Types+import Data.Char (isAlphaNum, toUpper)+import Data.Maybe (catMaybes)+import Data.Monoid+import Control.Applicative+import Data.Array (elems, (!))+import Text.Regex.TDFA+import Text.Regex.Base.RegexLike+import qualified Data.ByteString.Char8 as S+import qualified Text.PageIO.LabelMap as LM+import Debug.Trace++inferSheet :: FilePath -> [Page] -> Sheet+inferSheet fn pages = MkSheet+    { sheetName = name+    , sheetBox  = MkBox+        { boxTop    = 1+        , boxLeft   = 1+        , boxBottom = rows+        , boxRight  = cols+        }+    , sheetPatterns = mempty+    , sheetFields   = LM.fromList+        ( mkField "b_id" 1 1 4 1+        : case pages of+            []      -> []+            (p:_)   -> inferPageNameField p : inferPageDateFields p+        )+    , sheetFrames   = []+    , sheetOrderBy  = [toLabel "b_id"]+    , sheetGroupBy  = [toLabel "b_id"]+    , sheetUseBlockSortPriority = True+    }+    where+    lns     = pageLines <$> pages   +    rows    = maximum $ length <$> lns+    cols    = maximum $ S.length <$> concat lns+    name    = toLabel $ toUpper <$> takeWhile isAlphaNum fn++mkField :: String -> Col -> Row -> Col -> Row -> (Label, Field)+mkField lbl l t r b = (,) (toLabel lbl) MkField+    { fieldBox        = MkBox+        { boxLeft   = l+        , boxTop    = t+        , boxRight  = r+        , boxBottom = b+        }+    , fieldVariable   = Nothing+    , fieldKeepSpaces = False+    , fieldFormat     = FGeneral+    }++namePattern :: Value+namePattern = S.concat+    [ S.pack "(([^ ]+ {0,2})*[^ ]*("+    , keywords+    , S.pack ")[^< ]*)"+    ]+    where+    keywords = S.intercalate (S.pack "|") $ S.pack <$>+        [ "\xB3\xF8"    -- 報+        , "\xAA\xED"    -- 表+        , "\xB1\x62"    -- 帳+        , "\xC3\xAF"    -- 簿+        , "\xB3\xE6"    -- 單+        , "\xA5\x55"    -- 冊+        , "\xB2\xD3"    -- 細+        , "\xBF\xFD"    -- 錄+        , "\xB8\xEA"    -- 資+        , "\xAE\xC6"    -- 料+        , "\xAE\xD1"    -- 書+        , "\xC0\xC9"    -- 檔+        , "\xC1\x60"    -- 總+        , "\xAD\x70"    -- 計+        ]++datePattern :: Value+datePattern = S.pack $ concat+    [ "( {0,2}[0-9]{2,4} {0,2})"+    , "([^ 0-9]{1,4}|  )"+    , "( {0,3}[0-9]{1,2}) {0,2}"+    , "(([^ 0-9]{1,4}|  )"+    , "( {0,3}[0-9]{1,2} {0,1}))?"+    ]++data DateMatch = MkDateMatch+    { matchLine     :: Int+    , matchIsROC    :: Bool +    , matchYear     :: (Int, Int)+    , matchMonth    :: (Int, Int)+    , matchDay      :: Maybe (Int, Int)+    }+    deriving (Show, Eq, Ord)++inferPageNameField :: Page -> (Label, Field)+inferPageNameField page = case concatMap tryMatchName (take 6 lns) of+    []      -> mkField "r_name" 1 1 0 0 -- error "No r_name found!"+    (f:_)   -> f+    where+    lns = [1..] `zip` pageLines page++tryMatchName :: (Int, Value) -> [(Label, Field)]+tryMatchName (lineNum, content) =+    [ mkField "r_name" (start+delta+1) lineNum (start+len) lineNum+    | (text, (start, len)) <- (! (0 :: Int)) <$> content =~ namePattern+    , let delta = tryDelta text+    ]+    where+    tryDelta text = case text `matchSubstring` MkMatch commercialBank of+        Nothing     -> 0+        Just off    -> off + S.length commercialBank+    commercialBank = S.pack "\xB0\xD3\xB7\x7E\xBB\xC8\xA6\xE6\xA1\x40"++-- Algorithm: Find the first line with three consecutive integers+--            that looks sufficiently like a date.  We do so by+--            finding each 3-grams of digits close enough together+--            (distance 7 or less), then inspect the actual values,+--            then finally capture the spaces around them too.+--            If the year is >= 1900 then it's "year" else it's "year_roc".+inferPageDateFields :: Page -> [(Label, Field)]+inferPageDateFields page = case concatMap tryMatchDate lns of+    []      -> []+    (m:_)   -> dateMatchToFields m+    where+    lns = [1..] `zip` pageLines page++tryMatchDate :: (Int, Value) -> [DateMatch]+tryMatchDate (lineNum, content) =+    [ dateMatch{ matchLine = lineNum }+    | Just dateMatch <- validateMatch <$> reverse (content =~ datePattern)+    ]++validateMatch :: MatchText Value -> Maybe DateMatch+validateMatch match+    | month >= 1, month <= 12, day >= 1, day <= 31+    = Just MkDateMatch+        { matchLine     = undefined+        , matchIsROC    = year < 1900+        , matchYear     = yearPos+        , matchMonth    = monthPos+        , matchDay      = if S.null dayText then Nothing else Just dayPos+        }+    | otherwise+    = Nothing+    where+    [ _, (yearText,  yearPos)+       , _+       , (monthText, monthPos)+       , _+       , _+       , (dayText,   dayPos) ] = elems match+    [year, month, day] = valToInt <$> [yearText, monthText, dateText']+    dateText' | S.null dayText  = S.singleton '1'+              | otherwise       = dayText++dateMatchToFields :: DateMatch -> [(Label, Field)]+dateMatchToFields MkDateMatch{ matchLine, matchIsROC, matchYear, matchMonth, matchDay } =+    ( dateField yearLabel matchYear+    : dateField "month" matchMonth+    : maybe [] ((:[]) . dateField "day") matchDay+    )+    where+    dateField lbl (start, len) = mkField lbl (start+1) matchLine (start+len) matchLine+    yearLabel = if matchIsROC then "year_roc" else "year"+
src/Text/PageIO/LabelMap.hs view
@@ -72,6 +72,9 @@ filter :: (a -> Bool) -> LabelMap a -> LabelMap a filter f = MkLabelMap . IM.filter f . labelMap +null :: LabelMap a -> Bool+null = IM.null . labelMap+ mapMaybe :: (a -> Maybe b) -> LabelMap a -> LabelMap b mapMaybe f = MkLabelMap . IM.mapMaybe f . labelMap 
src/Text/PageIO/Parser.hs view
@@ -1,133 +1,163 @@ module Text.PageIO.Parser where -import Text.Parsec-import Text.Parsec.ByteString-import Data.Char (isDigit)+import Codec.Text.IConv+import Control.Applicative import Data.ByteString.Lazy.Char8 (pack, ByteString, toChunks)-import Text.PageIO.Types-import Control.Monad (liftM3, liftM4)+import Data.Maybe+import Data.ParserCombinators.Attoparsec.Char8 import Numeric (readDec)-import Codec.Text.IConv+import Text.PageIO.Types import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L import qualified Text.PageIO.LabelMap as LM +liftA4 :: Applicative f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e+liftA4 f a b c d = f <$> a <*> b <*> c <*> d++liftA5 :: Applicative f => (a -> b -> c -> d -> e -> result) -> f a -> f b -> f c -> f d -> f e -> f result+liftA5 f a b c d e = f <$> a <*> b <*> c <*> d <*> e++(<$$>) :: Functor f => f a -> (a -> b) -> f b+(<$$>) = flip (<$>)+ readSheet :: FilePath -> IO Sheet-readSheet fn = do-    rv <- parseFromFile sheet fn-    case rv of-        Right s     -> return s-        Left err    -> fail $ show err+readSheet fn = either (error . show) id+    <$> parseFromFile sheet fn +parseFromFile :: Parser a -> String -> IO (Either ParseError a)+parseFromFile p fname = do+    input <- L.readFile fname+    return . snd $ parse p input+ parseMaybe :: Parser a -> Parser (Maybe a)-parseMaybe r = fmap Just r <|> return Nothing+parseMaybe p = Just <$> p+    <|> return Nothing  maybeFieldVariable :: Parser (Maybe Variable)-maybeFieldVariable = parseMaybe $ do+maybeFieldVariable = parseMaybe $     char '$'-    choice-        [ char '$'   >> fmap VLabel bareLabel-        , sym "PAGE" >> return VPage+    *> choice+        [ VLabel <$> (char '$' *> bareLabel)+        , VPage  <$ sym "PAGE"         , literalVariable         , functionVariable+        , substrVariable+        , replaceVariable         ]  literalVariable :: Parser Variable-literalVariable = do-    s <- literalStr-    ret $ VLiteral s+literalVariable = r$ VLiteral <$> literalStr  functionVariable :: Parser Variable-functionVariable = do-    fun <- choice-        [ sym "SUM("    >> return VSum-        , sym "COUNT("  >> return VCount-        ]-    scope <- choice-        [ sym "page."   >> return SPage-        , sym "doc."    >> return SDoc-        , return SPage-        ]-    fld <- bareLabel-    sym ")"-    return $ fun scope fld+functionVariable =+    ($) <$> choice+            [ VSum   <$ sym "SUM("+            , VCount <$ sym "COUNT("+            ]+        <*> choice+            [ SDoc   <$ sym "doc."+            , SPage  <$ optional (sym "page.")+            ]+        <*> bareLabel+    <* sym ")" +substrVariable :: Parser Variable+substrVariable = between (sym "SUBSTR(") (sym ")") $+    liftA3 VSubStr+        (bareLabel  <* sym ",")+        (num        <* sym ",")+        (num)++replaceVariable :: Parser Variable+replaceVariable = between (sym "REPLACE(") (sym ")") $+    liftA2 VReplace (bareLabel <* sym ",")+                    (getPairs <$> literalStr `sepEndBy` sym ",")+    where+    getPairs []       = []+    getPairs [x]      = [(x, S.empty)]+    getPairs (x:y:zs) = ((x, y):getPairs zs)+ bareLabel :: Parser Label-bareLabel = do-    s   <- many1 $ noneOf ".;) "-    ret $ toLabel s+bareLabel = r$ toLabel <$> many1 (noneOf ",.;) ")  maybeFieldFormat :: Parser (Maybe FieldFormat)-maybeFieldFormat = parseMaybe $ do +maybeFieldFormat = parseMaybe $     sym "Format"-    choice-        [ sym "\"NZ ZZZ ZZ9,99\""       >> return (FNumeric 0)-        , sym "\"Nk= d=.\""             >> return (FNumeric 0)-        , sym "\"NZ,ZZZ,ZZZ,ZZ#.##\""   >> return (FNumeric 2)+    *> choice+        [ FNumeric 0 <$ sym "\"NZ ZZZ ZZ9,99\""+        , FNumeric 0 <$ sym "\"Nk= d=.\""+        , FNumeric 2 <$ sym "\"NZ,ZZZ,ZZZ,ZZ#.##\""         ]  maybeFilters :: Parser (Maybe [Filter])-maybeFilters = parseMaybe $ do-    sym "WHERE"+maybeFilters = parseMaybe $+    sym "WHERE" *>     parseFilter `sepEndBy` sym "AND"  parseFilter :: Parser Filter-parseFilter = liftM3 MkFilter parseLabel operator (fmap mkMatch quotedValue)+parseFilter = liftA3 MkFilter+    parseLabel+    operator+    (mkMatch <$> quotedValue)  operator :: Parser Operator operator = choice-    [ sym "NOT"         >> fmap ONot operator-    , sym "CONTAINS"    >> return OContains-    , sym "STARTSWITH"  >> return OStartsWith-    , sym "ENDSWITH"    >> return OEndsWith-    , sym "=="          >> return OEq-    , sym "="           >> return OEq-    , sym "!="          >> return (ONot OEq)-    , sym "<>"          >> return (ONot OEq)+    [ ONot      <$> (sym "NOT" *> operator)+    , OContains   <$ sym "CONTAINS"+    , OStartsWith <$ sym "STARTSWITH"+    , OEndsWith   <$ sym "ENDSWITH"+    , OEq         <$ sym "=="+    , OEq         <$ sym "="+    , ONot OEq    <$ sym "!="+    , ONot OEq    <$ sym "<>"     ] +commaSep :: Parser a -> Parser [a]+commaSep = (`sepEndBy` sym ",")+ maybeOrderBys :: Parser (Maybe [OrderBy Label])-maybeOrderBys = parseMaybe $ do-    sym "ORDER"-    sym "BY"-    orders <- (`sepEndBy` sym ",") $ do-        lbl <- parseLabel-        choice-            [ sym "DESC"            >> return (DDescending lbl)-            , optional (sym "ASC")  >> return (DAscending lbl)-            ]-    ret $ orders+maybeOrderBys = r$ parseMaybe $+    sym "ORDER" *>+    sym "BY" *> (+        commaSep $ do+            lbl <- parseLabel+            choice+                [ DDescending lbl <$ sym "DESC"+                , DAscending lbl  <$ optional (sym "ASC")+                ]+    )  maybeBy :: String -> Parser (Maybe [Label])-maybeBy by = parseMaybe $ do-    sym by-    sym "BY"-    lbls <- parseLabel `sepEndBy` sym ","-    ret $ lbls+maybeBy by = r$ parseMaybe $+    sym by *>+    sym "BY" *>+    commaSep parseLabel  maybeRule :: Parser (Maybe a) -> Parser (Maybe a)-maybeRule p = (<|> return Nothing) $ do-    between (sym "Rule" >> sym "\"") (sym "\";") p+maybeRule p = +    between (sym "Rule" *> sym "\"") (sym "\";") p+        <|> return Nothing  parseLabel :: Parser Label-parseLabel = do-    s   <- many (noneOf "\", ") -    ret $ toLabel s+parseLabel = r$ toLabel <$> many (noneOf "\", ") +r :: Parser a -> Parser a+r = (<* sp)+ sheet :: Parser Sheet-sheet = do-    sym "Event"+sheet = r$ between (sym "Event") (sym "End") $ do     name    <- labelStr     manyTill anyChar (sym "UseCharPos" <|> sym "UseGridPos")     sym "Sheet"     right   <- num     bottom  <- num-    optional (sym "UsePriority")+    pri     <- (True <$ sym "UsePriority") <|> return False     pats    <- many pattern-    grp     <- maybeRule $ maybeBy "GROUP"+    (grp, odr) <- (fromMaybe (Nothing, Nothing) <$>) . maybeRule $+        Just <$> ((,) <$> maybeBy "GROUP" <*> maybeBy "ORDER")     flds    <- many field     frames  <- many frame-    sym "End"-    ret $ MkSheet+    return $ MkSheet         { sheetName     = name         , sheetBox      = MkBox             { boxLeft   = 1@@ -140,39 +170,37 @@             | (lbl, pat) <- pats, let box = patternBox pat             ]         , sheetFields   = LM.fromList flds-        , sheetFrames   = (`fmap` frames) $ \frm -> frm-            { frameBlocks = (`fmap` frameBlocks frm) $ \blk -> blk-                { blockPatterns = (`fmap` blockPatterns blk) $ \pat -> pat+        , sheetFrames   = frames <$$> \frm -> frm+            { frameBlocks = frameBlocks frm <$$> \blk -> blk+                { blockPatterns = blockPatterns blk <$$> \pat -> pat                     { patternBox = (patternBox pat){ boxRight = right }                     }                 }             }-        , sheetGroupBy  = maybe [] id grp+        , sheetOrderBy  = fromMaybe (fromMaybe [] grp) odr+        , sheetGroupBy  = fromMaybe [] grp+        , sheetUseBlockSortPriority = pri         }  sym :: String -> Parser ()-sym s = try (string s) >> sp--ret :: a -> Parser a-ret x = sp >> return x+sym s = try (string (pack s)) *> sp  sp :: Parser ()-sp = skipMany (space <|> (try (string "//") >> skipMany (noneOf ['\n']) >> anyChar))+sp = skipMany (space <|> (try (string (pack "//")) *> skipMany (notChar '\n') *> anyChar))  num :: Parser Int-num = do-    digits <- many1 digit-    ret $ fst (head $ readDec digits)+num = r$ fst . head . readDec <$> many1 digit  pattern :: Parser (Label, Pattern)-pattern = do-    sym "Match"-    lbl     <- labelStr-    left    <- num-    top     <- num-    mat     <- matchStr-    wc      <- (sym "UseWildCards" >> return True) <|> return False-    retLabel lbl MkPattern+pattern = r$ sym "Match"+    *> liftA5 ret+        labelStr+        num+        num+        matchStr+        (True <$ sym "UseWildCards" <|> return False)+    where+    ret lbl left top mat wc = (,) lbl MkPattern         { patternBox    = MkBox             { boxLeft   = left             , boxTop    = top@@ -184,51 +212,47 @@         }  field :: Parser (Label, Field)-field = do-    sym "Field"-    lbl <- labelStr-    box <- boxNumbers-    var <- maybeFieldVariable-    fmt <- maybeFieldFormat-    char ';'-    retLabel lbl MkField+field = between (sym "Field") (sym ";") $+    liftA4 ret+        labelStr+        boxNumbers+        maybeFieldVariable+        maybeFieldFormat+    where+    ret lbl box var fmt = (,) lbl MkField         { fieldBox         = box         , fieldKeepSpaces  = True         , fieldVariable    = var-        , fieldFormat      = maybe FGeneral id fmt+        , fieldFormat      = fromMaybe FGeneral fmt         }  boxNumbers :: Parser Box-boxNumbers = liftM4 MkBox num num num num+boxNumbers = liftA4 MkBox num num num num  frame :: Parser Frame-frame = do-    sym "Frame"-    box     <- boxNumbers-    blocks  <- many block-    sym "End"-    ret $ MkFrame+frame = between (sym "Frame") (sym "End")+    $ liftA2 ret+        boxNumbers+        (many block)+    where+    ret box blocks = MkFrame         { frameBox    = box         , frameBlocks = LM.fromList blocks         }  retLabel :: Label -> a -> Parser (Label, a)-retLabel l x = ret (l, x)+retLabel l x = (l, x) <$ sp  block :: Parser (Label, Block)-block = do-    sym "Block"+block = between (sym "Block") (sym "End") $ do     lbl     <- labelStr-    optional (sym "UsePriority")+    pri     <- (True <$ sym "UsePriority") <|> return False     sym "Lines"     lns     <- num     pats    <- many pattern-    (filters, orders) <- fmap (maybe (Nothing, Nothing) id) . maybeRule $ do-        filters <- maybeFilters-        orders  <- maybeOrderBys-        return (Just (filters, orders))+    (filters, groups, orders) <- (fromMaybe (Nothing, Nothing, Nothing) <$>) . maybeRule $+        Just <$> ((,,) <$> maybeFilters <*> maybeBy "GROUP" <*> maybeOrderBys)     flds    <- many field-    sym "End"     retLabel lbl MkBlock         { blockLines    = lns         , blockPatterns = LM.fromList@@ -239,22 +263,24 @@             [ (l, fld{ fieldBox = adjustBox box })             | (l, fld) <- flds, let box = fieldBox fld             ]-        , blockOrderBy  = maybe [] id orders-        , blockFilterBy = maybe [] id filters+        , blockOrderBy  = fromMaybe (DAscending <$> fromMaybe [] groups) orders+        , blockGroupBy  = fromMaybe [] groups+        , blockFilterBy = fromMaybe [] filters+        , blockUsePriority = pri         }     where     -- We adjust box because StreamServe counts in-block coordinates from (0,0).-    adjustBox (MkBox l t r b) = MkBox (l+1) (t+1) (r+1) (b+1)+    adjustBox (MkBox left top right bottom) = MkBox (left+1) (top+1) (right+1) (bottom+1)  literalStr :: Parser Value -literalStr = do-    beginQuote  <- oneOf "\"'"-    s           <- many (noneOf [beginQuote])-    char beginQuote-    ret . packLBS . convert "UTF-8" "CP950" $ pack s+literalStr = r$ do+    beginQuote <- oneOf "\"'"+    packLBS . convert "UTF-8" "CP950" . pack+        <$> many (noneOf [beginQuote])+            <* char beginQuote  matchStr :: Parser Match-matchStr = fmap (mkMatch . packLBS . convert "UTF-8" "CP950") str+matchStr = mkMatch . packLBS . convert "UTF-8" "CP950" <$> str  mkMatch :: Value -> Match mkMatch s = MkMatch $ S.init (s `S.append` S.singleton '\0')@@ -263,18 +289,43 @@ packLBS = S.concat . toChunks  labelStr :: Parser Label-labelStr = fmap (toLabel . packLBS) str+labelStr = toLabel . packLBS <$> str  str :: Parser ByteString-str = do-    char '"'-    s <- many (noneOf ['"'])-    char '"'-    ret $ pack s+str = pack <$> (char '"' *> many (noneOf ['"']) <* char '"' <* sp)  quotedValue :: Parser Value-quotedValue = do-    beginQuote  <- oneOf "\"'"-    s           <- many (noneOf [beginQuote])-    char beginQuote-    ret $ S.pack s+quotedValue = r$ do+    beginQuote <- oneOf "\"'"+    S.pack <$> many (noneOf [beginQuote])+                <* char beginQuote++--- Alternative combinators++sepEndBy :: Alternative f => f a -> f b -> f [a]+sepEndBy p sep = sepEndBy1 p sep <|> pure []++sepEndBy1 :: Alternative f => f a -> f b -> f [a]+sepEndBy1 p sep = (:) <$> p <*> next+    where+    next = sep *> sepEndBy p sep+        <|> pure []++many1 :: Alternative f => f a -> f [a] +many1 p = (:) <$> p <*> many p++choice :: Alternative f => [f a] -> f a+choice ps = foldr (<|>) empty ps++--- Parser specific++between :: Parser a -> Parser b -> Parser c -> Parser c+between open close p = open *> p <* close++--- Char8 Parser specific++oneOf :: [Char] -> Parser Char+oneOf = satisfy . flip elem++noneOf :: [Char] -> Parser Char+noneOf = satisfy . flip notElem
src/Text/PageIO/Transform.hs view
@@ -5,14 +5,17 @@ import Data.Maybe import Data.Monoid import Data.Function (on)-import Data.List (find, inits, tails, sortBy, groupBy, intersperse)+import Data.List (find, inits, tails, sortBy, groupBy, intersperse, mapAccumR, sort) import Text.Printf+import Text.Regex+import Control.Applicative import qualified Text.PageIO.LabelMap as LM import qualified Data.ByteString.Char8 as S import qualified Data.ByteString.Lazy.Char8 as L +import Debug.Trace import Text.PageIO.Types-import Text.PageIO.Extract (extractPage, SheetResult(..), BlockResult(..), Area)+import Text.PageIO.Extract (extractPage, SheetResult(..), BlockResult(..), Area, Bound(..), crop, fieldLen, formatDotted)  data Doc = MkDoc     { docMeta    :: !SheetResult@@ -38,7 +41,7 @@  data BlockData = MkBlockData     { dataSize      :: !Row-    , dataAreas     :: ![Ordered Area]+    , dataAreas     :: ![Ordered FieldBinding]     }     deriving (Eq, Ord, Show) @@ -55,17 +58,21 @@ type FitAttempt = LabelMap BlockData  -- The actual data bound to that area.-type PageBinding = LabelMap [Area]+type PageBinding = LabelMap [FieldBinding]+type FieldBinding = (Area, LabelMap Bound)  parsePages :: Sheet -> [Page] -> [Doc] parsePages sheetIn pagesIn = map emitDoc docGroupsOut     where     docGroupsOut = case sheetGroupBy sheetIn of-        []      -> [resultsIn]-        lbls    -> groupBy ((==) `on` docKeys) resultsIn+        []      -> [map snd resultsIn]+        lbls    -> map (map snd) $ groupBy ((==) `on` docKeys) $ sortBy (compare `on` docKeys) resultsIn             where-            docKeys (res, _) = map (`LM.lookup` resultFields res) lbls-    resultsIn = [ (res, page) | (Just res, page) <- map (\p -> (extractPage sheetIn p, p)) pagesIn ]+            docKeys (vm, _) = map (`LM.lookup` vm) lbls+    resultsIn =+        [ (makeValueMap res, (res, page))+        | (Just res, page) <- map (\p -> (extractPage sheetIn p, p)) pagesIn+        ]  emitDoc :: [(SheetResult, Page)] -> Doc emitDoc xs = MkDoc meta (packPages pages)@@ -82,24 +89,33 @@     bindingsOut = concatMap (bindDoc sheetOut pagesOut) docGroupsIn     docGroupsOut = case sheetGroupBy sheetOut of         []      -> [bindingsOut]-        lbls    -> groupBy ((==) `on` docKeys) bindingsOut+        lbls    -> groupBy ((==) `on` docKeys)+            $ sortBy (compare `on` docKeys) bindingsOut             where-            docKeys MkDocBinding{ docResult } = map (`LM.lookup` resultFields docResult) lbls-    docGroupsIn = case sheetGroupBy sheetIn of-        []      -> [resultsIn]-        lbls    -> groupBy ((==) `on` docKeys) resultsIn+            docKeys MkDocBinding{ docValueMap } = map (`LM.lookup` docValueMap) lbls+    docGroupsIn = case groupBys of+        []      -> [map snd resultsIn]+        lbls    -> map (map snd)+            $ groupBy ((==) `on` docKeys)+            $ sortBy (compare `on` docKeys) resultsIn             where-            docKeys res = map (`LM.lookup` resultFields res) lbls-    resultsIn = [ res | Just res <- map (extractPage sheetIn) pagesIn ]+            docKeys (vm, _) = map (`LM.lookup` vm) lbls+        where+        -- Really `melse`+        groupBys = case sheetOrderBy sheetIn of+            []  -> sheetOrderBy sheetOut+            xs  -> xs+    resultsIn = [ (makeValueMap res, res) | Just res <- map (extractPage sheetIn) pagesIn ]  makeDoc :: Sheet -> [DocBinding] -> Doc-makeDoc sheetOut xs = MkDoc meta . packPages $ fillVariables sheetOut results-    [ makePage sheetOut b p-    | b <- map docBinding xs-    | p <- map docPage xs-    ]+makeDoc sheetOut xs = MkDoc meta (packPages pages)     where-    meta = mconcat results+    meta = mconcat . catMaybes $ map (extractPage $ constToPattern sheetOut) pages+    pages = map (crop (sheetBox sheetOut)) $ fillVariables sheetOut results+        [ makePage sheetOut b p+        | b <- map docBinding xs+        | p <- map docPage xs+        ]     results = map docResult xs  packPages :: [Page] -> L.ByteString@@ -124,7 +140,8 @@ -- XXX - If data would expand, then maybe repeat the first page indefinitely? -- data DocBinding = MkDocBinding-    { docResult     :: !SheetResult+    { docValueMap   :: !ValueMap+    , docResult     :: !SheetResult     , docPage       :: !Page     , docBinding    :: !PageBinding     }@@ -133,31 +150,104 @@ bindDoc :: Sheet -> [Page] -> [SheetResult] -> [DocBinding] bindDoc sheetOut pagesOut resultsIn = case find (\(x, _, _) -> isJust x) pageBindings of     Just (Just bindings, boundPages, boundResults) -> -        [ MkDocBinding r p b+        [ MkDocBinding (makeValueMap r) r p b         | r <- boundResults         | b <- bindings         | p <- boundPages         ]     _  -> []     where-    resultsOut          = map (maybe (error "Roundtrip failed!") id . extractPage sheetOut) pagesOut+    resultsOut          = fromMaybe (error "Roundtrip failed!") . extractPage sheetOut+                      <$> pagesOut     orders              = sheetBlockOrderBys sheetOut-    capacity            = foldr (doCapacity sheetOut) [] resultsOut -    attempt             = foldr (doAttempt orders) mempty resultsIn+    capacity            = foldl (doCapacity sheetOut) [] resultsOut +    attempt             = foldl (doAttempt orders) mempty resultsIn     capacityTails       = repeatTails capacity     pageTails           = repeatTails pagesOut     resultTails         = repeatTails resultsIn     pageBindings        = -        [ (tryFit pc sortedAttempt, pages', results')+        [ (tryFit pc groupedAttempt, pages', results')         | pc        <- capacityTails         | pages'    <- pageTails         | results'  <- resultTails         ]-    sortedAttempt       = (`fmap` filteredAttempt) $ \dat ->+    groupedAttempt      = LM.mapWithKey (doGroupBlockData groups fields) sortedAttempt+        where+        groups              = sheetBlockGroupBys sheetOut+        fields              = sheetBlockFields sheetOut+    sortedAttempt       = (<$> filteredAttempt) $ \dat ->         dat{ dataAreas = sortBy (compare `on` fst) $ dataAreas dat }     filteredAttempt     = attempt `LM.intersection` capacityLabels     capacityLabels      = LM.fromList [ (k, ()) | k <- concatMap (concatMap slotBlocks) capacity ] +-- This is positively weird.+-- We want to somehow collapse all rows into one, and calculate their+-- variables "right here".+doGroupBlockData :: LabelMap [Label] -> LabelMap (LabelMap Field) -> Label -> BlockData -> BlockData+doGroupBlockData allGroups allFields lbl dat+    | Just groups@(_:_) <- LM.lookup lbl allGroups+    , Just fields <- LM.lookup lbl allFields+    = dat{ dataAreas = doGroupArea groups fields (dataAreas dat) }+    | Just fields <- LM.lookup lbl allFields+    = dat{ dataAreas = doExpandFields fields <$> dataAreas dat }+    | otherwise+    = dat{ dataAreas = sort $ dataAreas dat }++doExpandFields :: LabelMap Field -> Ordered FieldBinding -> Ordered FieldBinding+doExpandFields fields (order, (area, bounds)) = (order, (area, bounds'))+    where+    bounds' = LM.mapWithKey doExpandField fields+    doExpandField lbl fld+        | Just var   <- fieldVariable fld+        = case var of+            VLiteral lit        -> lit+            VSubStr label d t   -> maybe S.empty (S.take t . S.drop d)+                $ LM.lookup label bounds+            VReplace label mrs  -> maybe S.empty (\v -> foldl (flip $ uncurry replaceWith) v mrs)+                $ LM.lookup label bounds+            _               -> val+        | otherwise+        = val+        where+        val = fromMaybe S.empty (LM.lookup lbl bounds)+++replaceWith :: Value -> Value -> Value -> Value+replaceWith match replace str+    | match == S.singleton '$'+    = str `S.append` replace+    | match == S.singleton '^'+    = replace `S.append` str+    | otherwise+    = S.pack $ subRegex (mkRegex $ S.unpack match) (S.unpack str) (S.unpack replace)++-- type FieldBinding = (Area, LabelMap Bound)+doGroupArea :: [Label] -> LabelMap Field -> [Ordered FieldBinding] -> [Ordered FieldBinding]+doGroupArea groups fields xs = map (doGroupRows fields) $ groupBy ((==) `on` areaKeys) xs+    where+    -- XXX - Now groupby with "groups" and refill with vars! ("head" above is wrong)+    areaKeys (_, (_, bounds)) = (`LM.lookup` bounds) <$> groups++doGroupRows :: LabelMap Field -> [Ordered FieldBinding] -> Ordered FieldBinding+doGroupRows _ [] = error "Impossible"+doGroupRows fields xs@((order, (area, bounds)):_) = (order, (area, bounds'))+    where+    bounds' = LM.mapWithKey doGroupRow fields+    doGroupRow lbl fld+        | Just var   <- fieldVariable fld+        , len        <- fieldLen fld+        = case var of+            VLiteral lit        -> lit+            VCount{}            -> formatInt len $ length xs+            VSum{ vLabel = l }  -> formatFloat len . sum . map valToInt $ catMaybes+                [ LM.lookup l vs+                | (_, (_, vs)) <- xs+                ]+            _               -> val+        | otherwise = val+        where+        val = fromMaybe S.empty (LM.lookup lbl bounds)+ -- Given "abc", generate ["", "c", "bc", "abc", "aabc", "aaabc", "aaaabc"...] repeatTails :: [a] -> [[a]] repeatTails [] = []@@ -165,76 +255,102 @@     where     infinitePrefixes = inits (repeat x) +constToPattern :: Sheet -> Sheet+constToPattern sheet = sheet{ sheetFrames = doFrame <$> sheetFrames sheet }+    where+    doFrame frame = frame{ frameBlocks = doBlock <$> frameBlocks frame }+    doBlock block = block+        { blockPatterns = blockPatterns block `mappend` (LM.mapMaybe doField $ blockFields block) }+    doField field+        | Just (VLiteral lit) <- fieldVariable field+        = Just MkPattern+            { patternBox          = fieldBox field+            , patternMatch        = MkMatch lit+            , patternUseWildcards = False+            }+        | otherwise = Nothing+ -- We now calculate the applied vars for each page fillVariables :: Sheet -> [SheetResult] -> [Page] -> [Page] fillVariables sheet results pages =     [ fillPageVariables appliedVars p     | p             <- pages-    | appliedVars   <- map applyVariable ([1..] `zip` valueMaps)+    | appliedVars   <- applyVariable <$> [1..] `zip` valueMaps     ]     where-    vars        = sheetVariableFields sheet-    valueMaps   = map makeValueMap+    (fieldVars, frameVars) = sheetVariableFields sheet+    valueMaps   = makeValueMap <$>         [ MkSheetResult rf nr-        | MkSheetResult rf _  <- results-        | MkSheetResult _ nr <- newResults+        | MkSheetResult rf _    <- results+        | MkSheetResult _ nr    <- newResults         ]     docVals     = LM.unionsWith (++) valueMaps-    newResults  = map (maybe (error "1Roundtrip failed!") id . extractPage sheet) pages+    newResults  = fromMaybe err . extractPage sheet <$> pages+    err = error "Roundtrip failed during variable fill!"     applyVariable :: (Int, ValueMap) -> LabelMap AppliedVariable-    applyVariable (pageNum, pageVals) = LM.mapMaybeWithKey applyOneVariable vars+    applyVariable (pageNum, pageVals) =+        LM.mapMaybeWithKey (applyOneVariable False) fieldVars+            `LM.union`+        LM.mapMaybeWithKey (applyOneVariable True) frameVars         where-        applyOneVariable :: Label -> Field -> Maybe AppliedVariable-        applyOneVariable lbl MkField{ fieldBox, fieldVariable }+        applyOneVariable :: Bool -> Label -> Field -> Maybe AppliedVariable+        applyOneVariable isInFrame lbl fld@MkField{ fieldBox, fieldVariable }             | Just var <- fieldVariable-            , LM.member lbl pageVals  = Just MkAppliedVariable+            , if isInFrame then LM.member lbl pageVals else True+            = Just MkAppliedVariable                 { avRow     = boxTop fieldBox                 , avCol     = boxLeft fieldBox                 , avValue   = getVar var                 }             | otherwise                 = Nothing             where-            len = boxRight fieldBox - boxLeft fieldBox + 1+            len = fieldLen fld             valOf :: Scope -> Label -> [Value]-            valOf scope lbl = maybe [] id $ LM.lookup lbl vals+            valOf scope lbl = fromMaybe [] $ LM.lookup lbl vals                 where                 vals = case scope of                     SDoc    -> docVals                     SPage   -> pageVals-            getVar VPage                = formatNumber pageNum-            getVar (VSum scope label)   = formatDotted $ sum (map valToInt $ valOf scope label)-            getVar (VCount scope label) = formatNumber $ length (valOf scope label)+            getVar VPage                = formatInt len pageNum+            getVar (VSum scope label)   = case fieldFormat fld of+                FNumeric 2  -> (formatDotted (len-6) . show $ sum (valToInt <$> valOf scope label)) `S.append` S.pack ".00"+                _           -> formatInt len $ sum (valToInt <$> valOf scope label)+            getVar (VCount scope label) = formatInt len $ length (valOf scope label)             getVar (VLiteral lit)       = formatLiteral lit             getVar (VLabel label)       = formatLiteral $ case valOf SPage label of                 []      -> S.empty                 (x:_)   -> x+            getVar (VSubStr label d t)  = formatLiteral $ case valOf SPage label of+                []      -> S.empty+                (x:_)   -> S.take t (S.drop d x)+            getVar (VReplace label mrs)  = formatLiteral $ case valOf SPage label of+                []      -> S.empty+                (x:_)   -> foldl (flip $ uncurry replaceWith) x mrs             formatLiteral lit = lit `S.append` S.replicate (len - S.length lit) ' '-            formatNumber = S.pack . printf ("%" ++ show len ++ "d")-            formatDotted n = S.pack $ (replicate pad ' ') ++ dottedStr ++ ".00"-                where-                pad         = len - length dottedStr - 3-                str         = show n-                dottedStr   = reverse (addDot $ reverse str)-                addDot (x:y:z:rest@(_:_))   = (x:y:z:',':addDot rest)-                addDot xs                   = xs +formatInt :: Int -> Int -> Value+formatInt len = S.pack . printf ("%" ++ show len ++ "d")++formatFloat :: Int -> Int -> Value+formatFloat len = S.pack . printf ("%" ++ show (len - 3) ++ "d.00")+ makeValueMap :: SheetResult -> ValueMap makeValueMap MkSheetResult{ resultFields, resultBlocks } = LM.unionsWith (++) (fieldMap:blockMaps)     where-    fieldMap  = fmap (:[]) resultFields+    fieldMap  = (:[]) <$> resultFields     blockMaps :: [ValueMap]-    blockMaps = map makeBlockValueMap $ LM.elems resultBlocks+    blockMaps = makeBlockValueMap <$> LM.elems resultBlocks     makeBlockValueMap :: BlockResult -> ValueMap     makeBlockValueMap (MkBlockResult avs) = LM.unionsWith (++) maps         where         maps :: [LabelMap [Value]]-        maps = map (fmap (:[]) . snd) avs+        maps = [ (:[]) <$> bound | (_, bound) <- avs ]  -- Now comes the fun part. -- For each frame, see if any of its blocks are there. -- If yes, concat them and pain the rest white. makePage :: Sheet -> PageBinding -> Page -> Page-makePage MkSheet{ sheetFrames } binding page = foldl makeFrame page sheetFrames+makePage MkSheet{ sheetFrames, sheetUseBlockSortPriority = True } binding page = foldl makeFrame page sheetFrames     where     makeFrame page MkFrame{ frameBox, frameBlocks }         | null frameBindings = page@@ -243,13 +359,68 @@         pageCleared   = clearArea frameBox page         pageReplaced  = replaceArea frameBox (concat frameBindings) pageCleared         frameBindings = -            [ fromJust result+            [ fst <$> fromJust result             | frameLabel <- LM.keys frameBlocks             , let result = LM.lookup frameLabel binding             , isJust result             ] --- Start with +-- Otherwise we inspect individual vars and apply them.+-- Also: For GROUP BY fields, leave things blank!+-- Key here is blockGroupBy.+makePage MkSheet{ sheetFrames } binding page = foldl makeFrame page sheetFrames+    where+    makeFrame page MkFrame{ frameBox, frameBlocks }+        | null frameBindings = page+        | otherwise          = pageReplaced+        where+        (pageReplaced, _) = foldl replacePage (page, frameBox) (concatMap (reverse . doGroupBy . reverse) frameBindings)+        frameBindings = +            [ (\(_, bound) -> (block, bound)) <$> fromJust result+            | (frameLabel, block) <- LM.toList frameBlocks+            , let result = LM.lookup frameLabel binding+            , isJust result+            ]++-- If we group by some fields, the second time the same field occurs,+-- _and_ if all fields to the left also matches, then do not bother to display it.+doGroupBy :: [(Block, LabelMap Bound)] -> [(Block, LabelMap Bound)]+doGroupBy [] = []+doGroupBy [x] = [x]+doGroupBy ((xb@MkBlock{ blockGroupBy }, xs):rest@((_, ys):_)) = (xb, xs'):doGroupBy rest+    where+    -- For each kv in xs:+    -- If the k is in blockGroupBy+    -- and v is in ys as v'+    -- and v == v'+    -- and all preceding fields also match+    -- then replace the v with empty+    xs' = LM.mapWithKey doCollapse xs+    doCollapse lbl val+        | lbl `elem` blockGroupBy+        , Just val' <- LM.lookup lbl ys+        , val == val'+        , preds <- takeWhile (/= lbl) blockGroupBy+        , map (`LM.lookup` xs) preds == map (`LM.lookup` ys) preds+        = S.empty+        | otherwise+        = val++replacePage :: (Page, Box) -> (Block, LabelMap Bound) -> (Page, Box)+replacePage (page, box@MkBox{ boxTop, boxLeft }) (MkBlock{ blockLines, blockFields }, bounds) = (page', box')+    where+    box' = box{ boxTop = boxTop + blockLines }+    page' = foldl replaceField page+        [ (fromJust rv, fieldBox)+        | (lbl, MkField{ fieldBox }) <- LM.toList blockFields+        , let rv = LM.lookup lbl bounds+        , isJust rv+        ]+    rowOffset = boxTop - 1+    colOffset = boxLeft - 1+    replaceField p (val, MkBox{ boxTop, boxLeft, boxBottom, boxRight})+        = fillArea (boxTop + rowOffset) (boxLeft + colOffset) (valueToArea val) p+ replaceArea :: Box -> [Area] -> Page -> Page replaceArea _ [] page = page replaceArea box@MkBox{ boxTop, boxLeft } (area:rest) page = replaceArea box' rest page'@@ -262,7 +433,7 @@     where     (before, rest)  = splitAt (boxTop-1) lns     (middle, after) = splitAt (boxBottom-boxTop+1) rest-    cleared         = map (fillLine boxLeft . ((,) whiteSpace)) middle+    cleared         = fillLine boxLeft . ((,) whiteSpace) <$> middle     whiteSpace      = S.replicate (boxRight-boxLeft+1) ' '  {-# INLINE fillArea #-}@@ -271,7 +442,7 @@     where     (before, rest)  = splitAt (row-1) lns     (middle, after) = splitAt (length areaLines) rest-    replaced        = map (fillLine col) (areaLines `zip` middle)+    replaced        = fillLine col <$> areaLines `zip` middle  {-# INLINE fillLine #-} fillLine :: Col -> (Value, Value) -> Value@@ -279,14 +450,14 @@     where     origLinePadded         | padLength <= 0    = origLine-        | otherwise         = origLine `S.append` S.replicate padLength '-' +        | otherwise         = origLine `S.append` S.replicate padLength ' '     padLength       = (col + areaLength - 1) - S.length origLine     (before, rest)  = S.splitAt (col-1) origLinePadded     after           = S.drop areaLength rest     areaLength      = S.length areaLine -doCapacity :: Sheet -> SheetResult -> [PageCapacity] -> [PageCapacity]-doCapacity MkSheet{ sheetFrames } MkSheetResult{ resultBlocks } pcs = pc:pcs+doCapacity :: Sheet -> [PageCapacity] -> SheetResult -> [PageCapacity]+doCapacity MkSheet{ sheetFrames } pcs MkSheetResult{ resultBlocks } = pc:pcs     where     pc              = map frameToSlot framesOccured     framesOccured   = filter (labelOccured . frameBlocks) sheetFrames@@ -297,21 +468,24 @@         , slotBlocks = LM.keys frameBlocks         } -doAttempt :: LabelMap [OrderBy Label] -> SheetResult -> FitAttempt -> FitAttempt-doAttempt orders MkSheetResult{ resultBlocks } att = att'+doAttempt :: LabelMap [OrderBy Label] -> FitAttempt -> SheetResult -> FitAttempt+doAttempt orders att MkSheetResult{ resultBlocks } = att'     where     att'            = LM.unionsWith mappend [att, LM.mapMaybeWithKey blockToData resultBlocks]-    blockToData lbl (MkBlockResult lms) = case areas of +    blockToData lbl (MkBlockResult lms) = case lms of          []              -> Nothing-        ((_, area):_)   -> Just MkBlockData+        ((area, _):_)   -> Just MkBlockData             { dataSize  = areaRows area             , dataAreas = areas             }         where         orderFrom = case LM.lookup lbl orders of-            Just orderBys   -> \vals -> map (fmap (`LM.lookup` vals)) orderBys+            Just orderBys   -> \bound ->+                [ (`LM.lookup` bound) <$> orderBy+                | orderBy <- orderBys+                ]             Nothing         -> const []-        areas = [ (orderFrom vals, area) | (area, vals) <- lms ]+        areas = [ (orderFrom bound, area) | area@(_, bound) <- lms ]  areaRows :: Area -> Row areaRows (MkPage lns) = length lns@@ -325,17 +499,23 @@ valueToArea :: Value -> Area valueToArea val = MkPage [val] -sheetVariableFields :: Sheet -> LabelMap Field-sheetVariableFields MkSheet{ sheetFrames, sheetFields } = LM.unions filteredMaps+sheetVariableFields :: Sheet -> (LabelMap Field, LabelMap Field)+sheetVariableFields MkSheet{ sheetFrames, sheetFields } = (fieldMaps, LM.unions filteredMaps)     where-    filteredMaps = map (LM.filter (isJust . fieldVariable)) fieldMaps-    fieldMaps = LM.mapWithKey addVariable sheetFields : concatMap frameVariableFields sheetFrames+    fieldMaps = LM.mapWithKey addVariable sheetFields+    filteredMaps = LM.filter (fieldIsVariable . fieldVariable) <$> frameMaps+    frameMaps = concatMap frameVariableFields sheetFrames+    fieldIsVariable (Just VReplace{}) = False+    fieldIsVariable (Just VSubStr{}) = False+    fieldIsVariable Nothing = False+    fieldIsVariable _ = True     addVariable lbl fld@MkField{ fieldVariable } = case fieldVariable of         Just{}  -> fld         _       -> fld{ fieldVariable = Just (VLabel lbl) }     frameVariableFields frame@MkFrame{ frameBox } = map blockVariableFields blocks         where-        blocks    = LM.elems (frameBlocks frame)+        -- XXX - Special rule - blocks with GROUP BY is not filled in at this stage!+        blocks    = filter (null . blockGroupBy) $ LM.elems (frameBlocks frame)         rowOffset = boxTop frameBox - 1         colOffset = boxLeft frameBox - 1         blockVariableFields = fmap adjustField . blockFields@@ -349,10 +529,21 @@             }  sheetBlockOrderBys :: Sheet -> LabelMap [OrderBy Label]-sheetBlockOrderBys MkSheet{ sheetFrames } = LM.unions (map frameBlockOrderBys sheetFrames)+sheetBlockOrderBys = gatherForSheetBlock blockOrderBy++sheetBlockGroupBys :: Sheet -> LabelMap [Label]+sheetBlockGroupBys = gatherForSheetBlock blockGroupBy++sheetBlockFields :: Sheet -> LabelMap (LabelMap Field)+sheetBlockFields = gatherForSheetBlock blockFields++gatherForSheetBlock :: (Block -> a) -> Sheet -> LabelMap a+gatherForSheetBlock f MkSheet{ sheetFrames } = LM.unions (map doGather sheetFrames)     where-    frameBlockOrderBys MkFrame{ frameBlocks } = LM.fromList-        [ (lbl, order) | (lbl, order@(_:_)) <- LM.toList (fmap blockOrderBy frameBlocks) ]+    doGather MkFrame{ frameBlocks } = f <$> frameBlocks+    {-LM.fromList+        [ (lbl, vals) | (lbl, vals@(_:_)) <- LM.toList (f <$> frameBlocks) ]+        -}  -- Each try is on a list of Frame Capacities -- Each frame capacity is a number of rows, and the label of blocks it can consume@@ -381,7 +572,7 @@                 consumedSize = consumed * dataSize                 newData      = dat{ dataAreas = drop consumed dataAreas }                 newAttempt   = LM.insert b newData att-                newBinding   = LM.insertWith (++) b (map snd $ take consumed dataAreas) binding+                newBinding   = LM.insertWith (++) b (snd <$> take consumed dataAreas) binding                 newSlot      = slot                     { slotSize   = slotSize - consumedSize                     , slotBlocks = bs
src/Text/PageIO/Types.hs view
@@ -1,14 +1,16 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS -funbox-strict-fields #-} module Text.PageIO.Types (module Text.PageIO.Types, module Text.PageIO.LabelMap) where import Text.PageIO.LabelMap (LabelMap, Label, toLabel, fromLabel) import Data.ByteString.Internal (inlinePerformIO, memcmp, ByteString(..)) import Data.Char (isDigit)+import Data.Monoid import Foreign.Ptr import Foreign.ForeignPtr import qualified Data.ByteString.Char8 as S  newtype Page = MkPage { pageLines :: [Value] }-    deriving (Show, Eq, Ord)+    deriving (Show, Eq, Ord, Monoid)  type Col = Int type Row = Int@@ -34,8 +36,9 @@     , sheetFields   :: !(LabelMap Field)     , sheetFrames   :: ![Frame]     , sheetGroupBy  :: ![Label]+    , sheetOrderBy  :: ![Label] --  , sheetPositioning          :: CharPos | GridPos---  , sheetUseBlockSortPriority :: Bool+    , sheetUseBlockSortPriority :: !Bool     }     deriving (Show, Eq, Ord) @@ -54,6 +57,8 @@     | VSum{ vScope :: !Scope, vLabel :: !Label }     | VCount{ vScope :: !Scope, vLabel :: !Label }     | VLabel{ vLabel :: !Label }+    | VSubStr{ vLabel :: !Label, vDrop :: !Int, vTake :: !Int }+    | VReplace{ vLabel :: !Label, vMatchReplace :: ![(Value, Value)]}     | VLiteral{ vValue :: !Value }     deriving (Show, Eq, Ord) @@ -99,9 +104,11 @@     , blockPatterns         :: !(LabelMap Pattern)     , blockFields           :: !(LabelMap Field)     , blockOrderBy          :: ![OrderBy Label]+    , blockGroupBy          :: ![Label]     , blockFilterBy         :: ![Filter] --  , blockRule             :: Rule --  , blockSortPriority     :: Priority+    , blockUsePriority      :: !Bool     }     deriving (Show, Eq, Ord) @@ -132,3 +139,18 @@     Just (num, _)   -> num     _               -> 0 +{-# INLINE matchSubstring #-}+matchSubstring :: Value -> Match -> (Maybe Int)+matchSubstring (PS x1 s1 l1) (MkMatch (PS x2 s2 l2))+    | l2 > l1   = Nothing+    | otherwise = inlinePerformIO $+        withForeignPtr x1 $ \p1 ->+            withForeignPtr x2 $ \p2 -> do+                let valuePtr    = p1 `plusPtr` s1+                    matchPtr    = p2 `plusPtr` s2+                    sz          = fromIntegral l2+                    maxOffset   = l1 - l2+                    go n = if n > maxOffset then return Nothing else do+                        rv <- memcmp matchPtr (valuePtr `plusPtr` n) sz+                        if rv == 0 then return (Just n) else go (n+1)+                 in go 0