packages feed

ldif 0.0.10 → 0.0.12

raw patch · 22 files changed

+879/−551 lines, 22 filesdep +bytestringdep +rosezipperdep ~cmdargsdep ~parsecnew-component:exe:ldifdiffnew-component:exe:ldifundoPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: bytestring, rosezipper

Dependency ranges changed: cmdargs, parsec

API changes (from Hackage documentation)

- Text.LDIF.Parser: parseLDIFStrAs :: Maybe LDIFType -> String -> Either ParseError LDIF
- Text.LDIF.Types: DNi :: [AttrValue] -> DN
- Text.LDIF.Types: newtype Attribute
- Text.LDIF.Types: type Value = String
- Text.LDIF.Utils: dn2dnI :: DN -> DN
- Text.LDIF.Utils: getLDIFType :: LDIF -> LDIFType
- Text.LDIF.Utils: isChangeRecord :: LDIFRecord -> Bool
- Text.LDIF.Utils: isContentRecord :: LDIFRecord -> Bool
- Text.LDIF.Utils: ldif2tree :: LDIF -> Tree LDIFRecord
- Text.LDIF.Utils: sizeOfDN :: DN -> Int
+ Text.LDIF.Diff: compareLDIF :: LDIF -> LDIF -> ([LDIFRecord], [LDIFRecord])
+ Text.LDIF.Parser: LDIFParserConfig :: Maybe LDIFType -> Bool -> LDIFParserConfig
+ Text.LDIF.Parser: data LDIFParserConfig
+ Text.LDIF.Parser: defaulLDIFConf :: LDIFParserConfig
+ Text.LDIF.Parser: instance Show LDIFParserConfig
+ Text.LDIF.Parser: lpCaseSensitive :: LDIFParserConfig -> Bool
+ Text.LDIF.Parser: lpExpectedType :: LDIFParserConfig -> Maybe LDIFType
+ Text.LDIF.Parser: preproc :: ByteString -> (ByteString, PosTable)
+ Text.LDIF.Preproc: instance Show PosOp
+ Text.LDIF.Preproc: preproc :: ByteString -> (ByteString, PosTable)
+ Text.LDIF.Preproc: transposePos :: PosTable -> ParseError -> ParseError
+ Text.LDIF.Preproc: type PosTable = [PosOp]
+ Text.LDIF.Printer: ver2str :: Maybe ByteString -> [ByteString]
+ Text.LDIF.Tree: fromTree :: Tree LDIFRecord -> LDIF
+ Text.LDIF.Tree: sortTreeByName :: Tree LDIFRecord -> Tree LDIFRecord
+ Text.LDIF.Tree: toTree :: LDIF -> Bool -> Tree LDIFRecord
+ Text.LDIF.Types: Value :: ByteString -> Value
+ Text.LDIF.Types: ValueI :: ByteString -> Value
+ Text.LDIF.Types: aName :: Attribute -> ByteString
+ Text.LDIF.Types: aVal :: Value -> ByteString
+ Text.LDIF.Types: data Attribute
+ Text.LDIF.Types: data Value
+ Text.LDIF.Types: getLDIFType :: LDIF -> LDIFType
+ Text.LDIF.Types: instance Eq Value
+ Text.LDIF.Types: instance Ord DN
+ Text.LDIF.Types: instance Ord Value
+ Text.LDIF.Types: instance Show Value
+ Text.LDIF.Types: isChangeRecord :: LDIFRecord -> Bool
+ Text.LDIF.Types: isContentRecord :: LDIFRecord -> Bool
+ Text.LDIF.Undo: undoLDIF :: LDIF -> (LDIF, [[Warning]])
+ Text.LDIF.Utils: createLookupTable :: LDIF -> LDIFCache
+ Text.LDIF.Utils: getDNValue :: DN -> Int -> AttrValue
+ Text.LDIF.Utils: isParentRecordOf :: LDIFRecord -> LDIFRecord -> Bool
+ Text.LDIF.Utils: lengthOfDN :: DN -> Int
+ Text.LDIF.Utils: type LDIFCache = Map DN LDIFRecord
- Text.LDIF.Parser: parseDNStr :: String -> Either ParseError DN
+ Text.LDIF.Parser: parseDNStr :: LDIFParserConfig -> ByteString -> Either ParseError DN
- Text.LDIF.Parser: parseLDIFFile :: String -> IO (Either ParseError LDIF)
+ Text.LDIF.Parser: parseLDIFFile :: LDIFParserConfig -> FilePath -> IO (Either ParseError LDIF)
- Text.LDIF.Parser: parseLDIFStr :: String -> Either ParseError LDIF
+ Text.LDIF.Parser: parseLDIFStr :: LDIFParserConfig -> FilePath -> ByteString -> Either ParseError LDIF
- Text.LDIF.Printer: dn2str :: DN -> String
+ Text.LDIF.Printer: dn2str :: DN -> ByteString
- Text.LDIF.Printer: ldif2str :: LDIF -> String
+ Text.LDIF.Printer: ldif2str :: LDIF -> ByteString
- Text.LDIF.Printer: record2str :: LDIFRecord -> String
+ Text.LDIF.Printer: record2str :: LDIFRecord -> ByteString
- Text.LDIF.Types: Attribute :: String -> Attribute
+ Text.LDIF.Types: Attribute :: ByteString -> Attribute
- Text.LDIF.Types: ChangeAdd :: [AttrValue] -> Change
+ Text.LDIF.Types: ChangeAdd :: ![AttrValue] -> Change
- Text.LDIF.Types: ChangeModify :: [Modify] -> Change
+ Text.LDIF.Types: ChangeModify :: ![Modify] -> Change
- Text.LDIF.Types: ChangeRecord :: DN -> Change -> LDIFRecord
+ Text.LDIF.Types: ChangeRecord :: !DN -> !Change -> LDIFRecord
- Text.LDIF.Types: ContentRecord :: DN -> [AttrValue] -> LDIFRecord
+ Text.LDIF.Types: ContentRecord :: !DN -> ![AttrValue] -> LDIFRecord
- Text.LDIF.Types: DN :: [AttrValue] -> DN
+ Text.LDIF.Types: DN :: ![AttrValue] -> DN
- Text.LDIF.Types: LDIF :: Maybe String -> [LDIFRecord] -> LDIF
+ Text.LDIF.Types: LDIF :: Maybe ByteString -> ![LDIFRecord] -> LDIF
- Text.LDIF.Types: ModAdd :: Attribute -> [Value] -> Modify
+ Text.LDIF.Types: ModAdd :: !Attribute -> ![Value] -> Modify
- Text.LDIF.Types: ModDelete :: Attribute -> [Value] -> Modify
+ Text.LDIF.Types: ModDelete :: !Attribute -> ![Value] -> Modify
- Text.LDIF.Types: ModReplace :: Attribute -> [Value] -> Modify
+ Text.LDIF.Types: ModReplace :: !Attribute -> ![Value] -> Modify
- Text.LDIF.Types: chAttrVals :: Change -> [AttrValue]
+ Text.LDIF.Types: chAttrVals :: Change -> ![AttrValue]
- Text.LDIF.Types: chMods :: Change -> [Modify]
+ Text.LDIF.Types: chMods :: Change -> ![Modify]
- Text.LDIF.Types: chOp :: LDIFRecord -> Change
+ Text.LDIF.Types: chOp :: LDIFRecord -> !Change
- Text.LDIF.Types: coAttrVals :: LDIFRecord -> [AttrValue]
+ Text.LDIF.Types: coAttrVals :: LDIFRecord -> ![AttrValue]
- Text.LDIF.Types: dnAttrVals :: DN -> [AttrValue]
+ Text.LDIF.Types: dnAttrVals :: DN -> ![AttrValue]
- Text.LDIF.Types: lcEntries :: LDIF -> [LDIFRecord]
+ Text.LDIF.Types: lcEntries :: LDIF -> ![LDIFRecord]
- Text.LDIF.Types: lcVersion :: LDIF -> Maybe String
+ Text.LDIF.Types: lcVersion :: LDIF -> Maybe ByteString
- Text.LDIF.Types: modAttr :: Modify -> Attribute
+ Text.LDIF.Types: modAttr :: Modify -> !Attribute
- Text.LDIF.Types: modAttrVals :: Modify -> [Value]
+ Text.LDIF.Types: modAttrVals :: Modify -> ![Value]
- Text.LDIF.Types: reDN :: LDIFRecord -> DN
+ Text.LDIF.Types: reDN :: LDIFRecord -> !DN
- Text.LDIF.Utils: filterAttr :: String -> [AttrValue] -> [AttrValue]
+ Text.LDIF.Utils: filterAttr :: ByteString -> [AttrValue] -> [AttrValue]
- Text.LDIF.Utils: findRecordByDN :: LDIF -> DN -> Maybe LDIFRecord
+ Text.LDIF.Utils: findRecordByDN :: LDIFCache -> DN -> Maybe LDIFRecord
- Text.LDIF.Utils: lookupAttr :: String -> [AttrValue] -> Maybe Value
+ Text.LDIF.Utils: lookupAttr :: ByteString -> [AttrValue] -> Maybe Value

Files

− TODO.org
@@ -1,28 +0,0 @@-* Tasks-** error handling, modify add: <attrX> and than <attrY>:<value>,-          need to be asserted that <attrX> and <attrY> are the same.-** Implement the base64 parsing-** Implement modifyDN parsing-** update serializers back to LDIF for base64 & all DN notations-** add module tests for versioned LDIFs-** add module tests for base64 parsing-** update the API for correct support of LDIF RFC-multiple valued attribute in RDN-binary data -** DONE update parseDNStr to parse all kind of DN notations-CLOSED: [2011-04-17 Sun 15:28]-** DONE Use getopt for input parameters on diffLDIF cmd-** DONE Implement ldifmodify (like ldapmodify)-    offline applying of Change LDIF on Content LDIF-    ldifmodify -f <contentLDIF> -o <outputLDIF> [<modifyLDIF> ... ] -* Archive-** DONE Change interface for providing parser method with expected type of LDIF-it allows parse changeldif as contentldif regardless the content.--** DONE Add ldif2html which generate hypertext LDIF representation-** DONE Update unwrap method in preprocessor for feasible resource consuption-** DONE replace error call in mkMod with parser error-    (Not needed)-** DONE create preprocessor for concat split long lines & remove comments-** DONE refactoring of packages, create Types, Parser and Proc-** DONE Implement Attribute Name case-insensitive matching at DN
ldif.cabal view
@@ -1,35 +1,31 @@ Name:            ldif-Version:         0.0.10+Version:         0.0.12 License:         BSD3 License-File:    LICENSE-Synopsis:        The LDAP Data Interchange Format (LDIF) parser +Synopsis:        The LDAP Data Interchange Format (LDIF) tools  Author:          Radoslav Dorcik <radoslav.dorcik@gmail.com> Homepage:        http://rampa.sk/static/ldif.html Maintainer:      radoslav.dorcik@gmail.com Description:     - LDIF files parser implementation using Parsec and based- on RFC 2849 - The LDAP Data Interchange Format (LDIF).+ LDIF files parser implementation using Parsec.+ The LDAP Data Interchange Format (LDIF) is defined by RFC 2849.  .- Current implementation is unfinished and need to be enhanced- for base64 encoded values and various DN escaping. + Current implementation is not complete and compliant with RFC.   .- It includes following tool:+ Package includes following command line tools:  .- - diffLDIF command generates change LDIF between two -   content LDIF files.+ - ldifdiff - calculates delta LDIF between two content LDIF files.  .- - ldif2html command generates hypertext HTML browsable-   LDIF file. + - ldif2html - produces HTML/browsable LDIF file.  .- - ldifmodify commmand apply change LDIF on contenct LDIF+ - ldifmodify - replays delta LDIF operations on content LDIF (similar to ldapmodify).  . Category:        Text Stability:       experimental Build-Type:      Simple-Cabal-Version:   >= 1.6+Cabal-Version:   >= 1.8 Extra-Source-Files:-    run_tests.sh-    TODO.org+    runTests.sh     doc/rfc2253.txt     doc/rfc2849.txt     tests/TestMain.hs@@ -52,46 +48,86 @@   description: Build test program.   default:     False +flag cmd+  description: Build command line programs.+  default:     False+ Library   Build-Depends:   base         < 5,                    filepath,+                   bytestring,                    containers,-                   cmdargs      >= 0.2,+                   cmdargs,                    directory,-                   parsec       >= 2.1.0+                   rosezipper,+                   parsec       == 3.*   Hs-Source-Dirs:  src   Extensions:      CPP, PatternGuards-  Ghc-Options:      -Wall -fno-warn-orphans+  Ghc-Options:      -Wall -fno-warn-orphans -O2    Exposed-modules:         Text.LDIF         Text.LDIF.Types         Text.LDIF.Parser+        Text.LDIF.Preproc         Text.LDIF.Printer         Text.LDIF.Utils         Text.LDIF.Diff         Text.LDIF.Apply         Text.LDIF.Consts+        Text.LDIF.Tree+        Text.LDIF.Undo -Executable diffLDIF+Executable ldifdiff   Hs-Source-Dirs:  src-  Main-Is:         diffLDIF.hs+  Main-Is:         ldifdiff.hs+  if flag(cmd)+    Buildable:     True+  else+    Buildable:     False+  if flag(test)+    Buildable:     False  Executable ldif2html   Hs-Source-Dirs:  src   Main-Is:         ldif2html.hs+  if flag(cmd)+    Buildable:     True+  else+    Buildable:     False+  if flag(test)+    Buildable:     False  Executable ldifmodify   Hs-Source-Dirs:  src   Main-Is:         ldifmodify.hs+  if flag(cmd)+    Buildable:     True+  else+    Buildable:     False+  if flag(test)+    Buildable:     False +Executable ldifundo+  Hs-Source-Dirs:  src+  Main-Is:         ldifundo.hs+  if flag(cmd)+    Buildable:     True+  else+    Buildable:     False+  if flag(test)+    Buildable:     False+ Executable ldifparse   Hs-Source-Dirs:  src   Main-Is:         ldifparse.hs--Executable test-  Hs-Source-Dirs:  src, tests-  Main-Is:         TestMain.hs-  Build-Depends:   base, HUnit == 1.2.*   if !flag(test)     Buildable:     False++Test-Suite ldif-test+    type:           exitcode-stdio-1.0+    Main-Is:        TestMain.hs+    Ghc-Options:      -Wall -fno-warn-orphans -O2+    Build-Depends:  base, HUnit == 1.2.*+    Hs-Source-Dirs:  src, tests+
+ runTests.sh view
@@ -0,0 +1,22 @@+#!/bin/sh++function showUsage()+{+   echo "Usage: $1 [ build | rebuild | run ]"+}++if [ -z "$1" ]; then+   showUsage $0+   exit 1;+fi++if [ "$1" = "run" ]; then+    cabal test+elif [ "$1" = "build" ]; then+    cabal configure --enable-tests && cabal build && cabal test+elif [ "$1" = "rebuild" ]; then+    cabal clean+    cabal configure --enable-tests && cabal build && cabal test+else+    showUsage $0+fi
− run_tests.sh
@@ -1,3 +0,0 @@-#!/bin/sh--cabal configure -ftest && cabal build && cd tests && ../dist/build/test/test
src/Text/LDIF.hs view
@@ -4,7 +4,9 @@   module Text.LDIF.Printer,   module Text.LDIF.Utils,   module Text.LDIF.Diff,-  module Text.LDIF.Apply+  module Text.LDIF.Apply,+  module Text.LDIF.Tree,+  module Text.LDIF.Undo ) where @@ -14,3 +16,6 @@ import Text.LDIF.Utils import Text.LDIF.Diff import Text.LDIF.Apply+import Text.LDIF.Tree+import Text.LDIF.Undo+
src/Text/LDIF/Apply.hs view
@@ -1,29 +1,36 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}+ module Text.LDIF.Apply (         applyLDIF ) where+import Prelude import Text.LDIF.Types import Text.LDIF.Utils import Text.LDIF.Printer import Data.List (nub, foldl')+import qualified Data.ByteString.Char8 as BC --- | Apply one LDIF to another LDIF. The destination LDIF has--- | to be Content LDIF++-- | Apply operations in second LDIF on content of first LDIF. The destination LDIF has+-- to be Content LDIF. applyLDIF :: LDIF -> LDIF -> LDIF applyLDIF dst (LDIF _ xs) = foldl' (\ld chg -> applyRecord2LDIF chg ld) dst xs  -- | Apply one LDIF Content/Change Record into LDIF and produce Changed LDIF applyRecord2LDIF :: LDIFRecord -> LDIF -> LDIF applyRecord2LDIF (ContentRecord dn vals) dst = applyRecord2LDIF (ChangeRecord dn (ChangeAdd vals)) dst-applyRecord2LDIF (ChangeRecord  dn op)   dst = applyChange2Record op dn dst (findRecordByDN dst dn)+applyRecord2LDIF (ChangeRecord  dn op)   dst = applyChange2Record op dn dst (findRecordByDN dstc dn)+  where+    dstc = createLookupTable dst  -- | Apply one LDIF Change (add/del/modf) for given DN within LDIF Content  applyChange2Record :: Change -> DN -> LDIF -> Maybe LDIFRecord -> LDIF applyChange2Record (ChangeAdd vals)   dn ld Nothing  = LDIF (lcVersion ld) ((lcEntries ld)++[ContentRecord dn vals]) -applyChange2Record (ChangeAdd _)      dn _  (Just _) = error ("ADD: Already exists: "++(dn2str dn))+applyChange2Record (ChangeAdd _)      dn _  (Just _) = error ("ADD: Already exists: "++(BC.unpack $ dn2str dn)) applyChange2Record ChangeDelete       dn ld (Just _) = LDIF (lcVersion ld) (filter (\x -> dn /= (reDN x)) (lcEntries ld))-applyChange2Record ChangeDelete       dn _  Nothing  = error ("DELETE: Entry not found: "++(dn2str dn))-applyChange2Record (ChangeModify _)   dn _  Nothing  = error ("MODIFY: Entry not found: "++(dn2str dn))+applyChange2Record ChangeDelete       dn _  Nothing  = error ("DELETE: Entry not found: "++(BC.unpack $ dn2str dn))+applyChange2Record (ChangeModify _)   dn _  Nothing  = error ("MODIFY: Entry not found: "++(BC.unpack $ dn2str dn)) applyChange2Record (ChangeModify ops) dn ld (Just r) = let pre  = takeWhile (\x -> dn /= (reDN x)) (lcEntries ld)                                                            post = filter (\x -> dn /= (reDN x)) $ dropWhile (\x -> dn /= (reDN x)) (lcEntries ld)                                                            rn   = foldr applyMod2Record r ops@@ -36,15 +43,15 @@ applyMod2Record (ModAdd      name vals) (ContentRecord dn av) = let nav = map (\v -> (name,v)) vals  -- new attr/values                                                                     mav = av ++ nav                  -- merged attr/values                                                                     verified = if (length $ nub mav) == (length mav) then mav-                                                                               else error ("ModAdd: Values already exists: "++(show av)++" vs "++(show vals)++" DN:"++(dn2str dn))+                                                                               else error ("ModAdd: Values already exists: "++(show av)++" vs "++(show vals)++" DN:"++(BC.unpack $ dn2str dn))                                                                     in ContentRecord dn verified applyMod2Record (ModDelete   name [])   (ContentRecord dn av) = let nav = filter (\(n,_) -> n /= name) av -- new attr/values                                                                     verified = if (length $ nub nav) /= (length av) then nav-                                                                               else error ("ModDel: Attribute not found: "++(show name)++" DN:"++(dn2str dn))+                                                                               else error ("ModDel: Attribute not found: "++(show name)++" DN:"++(BC.unpack $ dn2str dn))                                                                 in ContentRecord dn verified applyMod2Record (ModDelete   name vals) (ContentRecord dn av) = let mav = filter (\(n,v) -> n /= name || v `notElem` vals) av                                                                     verified = if (length av) - (length mav) == (length vals) then mav-                                                                               else error ("ModDel: Attribute/Value not found: "++(show name)++"vals"++(show vals)++" DN:"++(dn2str dn))+                                                                               else error ("ModDel: Attribute/Value not found: "++(show name)++"vals"++(show vals)++" DN:"++(BC.unpack $ dn2str dn))                                                                 in ContentRecord dn verified applyMod2Record (ModReplace  name vals) (ContentRecord dn av) = let nav = map (\v -> (name,v)) vals -- new attr/values                                                                     mav = (filter (\(n,_) -> n /= name) av) ++ nav -- merged
src/Text/LDIF/Consts.hs view
@@ -1,6 +1,9 @@ module Text.LDIF.Consts where --- | Chars necessary to be escaped when are within RDN values-specialDNChars, escapedDNChars :: [Char]-specialDNChars = [',','=','+','<','>','#',';']+-- | Chars with special meaning in DN+specialDNChars :: [Char]+specialDNChars = [',','=','+','<','>','#',';','/']++-- | Chars necessary to be escaped in DN when they are part of value+escapedDNChars :: [Char] escapedDNChars = ['\\', '"'] ++ specialDNChars
src/Text/LDIF/Diff.hs view
@@ -1,20 +1,16 @@ module Text.LDIF.Diff (         diffLDIF,-        diffRecord+        diffRecord,+        compareLDIF ) where import Text.LDIF.Types import Text.LDIF.Utils import Data.Maybe+import Data.List (foldl', sortBy) --- | Create Change LDIF between to LDIF contents. If any--- | of input argument is not LDIFContent it returns Nothing. --- | If there is not difference the Change LDIF with empty--- | change list is returned.--- |--- | Unsing following strategy: --- | 1. Iterate over L1 DN's and Modify / Remove Content --- | 2. Iterate over L2 and Add Content not in L1+-- | Calculate Change LDIF between two LDIF contents.+-- If there is not difference the empty change list is returned. diffLDIF :: LDIF -> LDIF -> Either String LDIF diffLDIF l1 l2 | getLDIFType l1 == LDIFContentType && getLDIFType l2 == LDIFContentType = Right (diffLDIF' l1 l2)                | otherwise = Left ("Diff supported only on Content LDIFs but SRC="@@ -22,24 +18,28 @@                                          ++(show $ getLDIFType l2))  diffLDIF' :: LDIF -> LDIF -> LDIF-diffLDIF' l1@(LDIF _ c1) l2@(LDIF v2 c2) = LDIF v2 (changes ++ adds)+diffLDIF' l1@(LDIF _ c1) l2@(LDIF v2 c2) = LDIF v2 (changes ++ deletes ++ adds)    where -      adds = map (content2add) $ filter (not . isEntryIn l1) c2-      changes = filter (not . isDummyRecord) $ foldl (processEntry) [] c1-      processEntry xs e1 = let me2 = findRecordByDN l2 (reDN e1) -                               change = case me2 of-					   Nothing -> ChangeRecord (reDN e1) ChangeDelete-                                           Just e2 -> fromJust $ diffRecord e1 e2-                           in xs ++ [change]-      isEntryIn ll ex = let mex = findRecordByDN ll (reDN ex)-                        in case mex of-                          Nothing -> False-                          Just _  -> True-      content2add (ContentRecord dn vals) = ChangeRecord dn (ChangeAdd vals)-      content2add (ChangeRecord _ _)      = error "Unexpected record type"+      adds = map content2add $ filter (not . isEntryIn) c2+        where+          llc = createLookupTable l1+          isEntryIn ex = case findRecordByDN llc (reDN ex) of+            Nothing                   -> False+            Just (ContentRecord _ _)  -> True+            Just (ChangeRecord _ _)   -> error "Unexpected record type"+          content2add (ContentRecord dn vals) = ChangeRecord dn (ChangeAdd vals)+          content2add (ChangeRecord _ _)      = error "Unexpected record type"+      (changes,deletes) = (reverse $ fnu changes', fnu deletes')+        where+          fnu = filter (not . isDummyRecord)+          l2c = createLookupTable l2+          (changes',deletes') = foldl' processEntry ([],[]) c1+          processEntry (cx,dx) e1 = procEntry' $ findRecordByDN l2c (reDN e1)+            where+              procEntry' Nothing   = (cx, (ChangeRecord (reDN e1) ChangeDelete):dx)+              procEntry' (Just e2) = ((fromJust $ diffRecord e1 e2):cx, dx) --- | Diff two AttrVal Records if any of provided. --- | Implementation uses inefficient algorithm for large count of attributes within ContentRecord.+-- | Calculate difference between two LDIF Records diffRecord :: LDIFRecord -> LDIFRecord -> Maybe LDIFRecord diffRecord r1 r2 | (reDN r1) /= (reDN r2) = Nothing                  | otherwise = Just (ChangeRecord (reDN r1) (ChangeModify mods))@@ -49,3 +49,22 @@       delMods = map (\x -> ModDelete (fst x) [(snd x)]) delVals       addVals = filter (\x -> not $ elem x (coAttrVals r1)) (coAttrVals r2) :: [AttrValue]       delVals = filter (\x -> not $ elem x (coAttrVals r2)) (coAttrVals r1) :: [AttrValue]++-- | Compare two LDIFs and provide list of the Records for each input LDIF,+-- which are different or not present in the other LDIF.+compareLDIF :: LDIF -> LDIF -> ([LDIFRecord], [LDIFRecord])+compareLDIF l1@(LDIF _ c1) l2@(LDIF _ c2) = (sortBy cmpByDN r1, sortBy cmpByDN $ r2 ++ adds)+   where +      cmpByDN a b = (reDN a) `compare` (reDN b)+      adds = filter (not . isEntryIn) c2+        where+          llc = createLookupTable l1+          isEntryIn ex = case findRecordByDN llc (reDN ex) of+            Nothing                   -> False+            (Just _)                  -> True+      (r1,r2) = foldl' processEntry ([],[]) c1+        where+          l2c = createLookupTable l2+          processEntry (a1,a2) e1 = case findRecordByDN l2c (reDN e1) of+            Nothing -> (e1:a1,a2)+            Just e2 -> if e1 == e2 then (a1,a2) else (e1:a1,e2:a2)
src/Text/LDIF/Parser.hs view
@@ -1,278 +1,277 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}+ module Text.LDIF.Parser ( 	parseLDIFStr,-        parseLDIFStrAs, 	parseLDIFFile,-        parseDNStr+        parseDNStr,+        preproc,+        defaulLDIFConf,+        LDIFParserConfig(..) ) where+import Prelude import Text.LDIF.Types import Text.LDIF.Consts-import Text.ParserCombinators.Parsec+import Text.LDIF.Preproc+import Text.Parsec as PR+import Text.Parsec.ByteString+import Text.Parsec.Pos (initialPos)+import Text.Parsec.Error (Message(..), newErrorMessage)+import qualified Data.ByteString.Char8 as BC import Data.Char-import Data.List (isPrefixOf)+import Data.Maybe (fromJust, isNothing) import Numeric (readHex) --- | Parse string as LDIF content and return LDIF or ParseError-parseLDIFStr :: String -> Either ParseError LDIF-parseLDIFStr = parseLDIFStrAs Nothing ---- | Read and parse provided file and return LDIF or ParseError-parseLDIFFile :: String -> IO (Either ParseError LDIF)-parseLDIFFile name = do-	input <- readFile name-        return $ parseLDIFStrAs' name Nothing input---- | Read and parse provided string and return LDIF or ParserError--- | If LDIF type is specified than given type is expected for parsing --- | and mismatch generates ParseError-parseLDIFStrAs' :: String -> Maybe LDIFType -> String -> Either ParseError LDIF-parseLDIFStrAs' nm Nothing                = parse pLdif        nm . preproc-parseLDIFStrAs' nm (Just LDIFMixedType)   = parse pLdif        nm . preproc-parseLDIFStrAs' nm (Just LDIFContentType) = parse pLdifContent nm . preproc-parseLDIFStrAs' nm (Just LDIFChangesType) = parse pLdifChanges nm . preproc--parseLDIFStrAs :: Maybe LDIFType -> String -> Either ParseError LDIF-parseLDIFStrAs = parseLDIFStrAs' "(param)"---- | Parse string as DN and return DN type or ParseError-parseDNStr :: String -> Either ParseError DN-parseDNStr = parse pDN "(param)" ---- | Preprocessing for concat wrapped lines and remove comment lines-preproc :: String -> String-preproc = stripComments . unwrap+-- | LDIF Parser configuration+data LDIFParserConfig = LDIFParserConfig { lpExpectedType :: Maybe LDIFType  -- ^ Type of LDIF expected+                                         , lpCaseSensitive :: Bool } -- ^ Parse as Case Sensitive LDIF+                      deriving Show --- | Remove Comment Lines-stripComments :: String -> String-stripComments input = unlines $ filter (not . isPrefixOf "#") $ lines input+-- | Default configuration for parser (Any LDIF Type, Case Sensitive)+defaulLDIFConf :: LDIFParserConfig+defaulLDIFConf = LDIFParserConfig Nothing True --- | Unwrap lines, lines with space at begin is continue of previous line -unwrap :: String -> String-unwrap xs = unlines $ takeLines $ lines xs+-- | Parse LDIF content+parseLDIFStr :: LDIFParserConfig -> FilePath -> BC.ByteString -> Either ParseError LDIF+parseLDIFStr conf name xs = case eldif of +                               Left err  -> Left $ transposePos ptab err+                               Right ldf -> checkExpectedType ldf+  where +    (input, ptab) = preproc xs+    eldif = parse (pLdif conf) name input+    checkExpectedType ldf | (isNothing $ lpExpectedType conf) = Right ldf+                          | (getLDIFType ldf) == (fromJust $ lpExpectedType conf) = Right ldf+                          | otherwise = Left $ newErrorMessage (UnExpect "Invalid LDIF Type") (initialPos name)                                                                                     -takeLines :: [String] -> [String]-takeLines [] = []-takeLines xs = let (ln,ys) = takeLine xs-               in ln:takeLines ys+-- | Parse LDIF file+parseLDIFFile :: LDIFParserConfig -> FilePath -> IO (Either ParseError LDIF)+parseLDIFFile conf name = do+	input <- BC.readFile name+        return $ parseLDIFStr conf name input -takeLine :: [String] -> (String, [String])-takeLine []     = ([],[])-takeLine (x:[]) = (x,[])-takeLine (x:xs) = let isCont z = " " `isPrefixOf` z-                  in (x ++ (concat $ map (tail) $ takeWhile (isCont) xs), dropWhile (isCont) xs) +-- | Parse DN+parseDNStr :: LDIFParserConfig -> BC.ByteString -> Either ParseError DN+parseDNStr conf = parse (pDN conf) "(param)"   -- | Parsec ldif parser-pLdif :: CharParser st LDIF-pLdif = try pLdifChanges <|> try pLdifMixed--pLdifChanges :: CharParser st LDIF-pLdifChanges = do-    pSEPs-    ver <- optionMaybe pVersionSpec-    pSEPs-    recs <- sepEndBy pChangeRec pSEPs-    eof-    return $ LDIF ver recs--pLdifMixed:: CharParser st LDIF-pLdifMixed = do-    pSEPs-    ver <- optionMaybe pVersionSpec-    pSEPs-    recs <- sepEndBy pRec pSEPs-    eof-    return $ LDIF ver recs--pLdifContent :: CharParser st LDIF-pLdifContent = do-    pSEPs+pLdif :: LDIFParserConfig -> Parser LDIF+pLdif conf = do+    pSEPs conf     ver <- optionMaybe pVersionSpec-    pSEPs-    recs <- sepEndBy pAttrValRec pSEPs+    recs <- sepEndBy (pRec conf) (pSEPs1 conf)+    _ <- optionMaybe pSearchResult     eof-    return $ LDIF ver recs--pAttrValRec ::  CharParser st LDIFRecord-pAttrValRec = do-    dn <- pDNSpec-    pSEP-    attrVals <- sepEndBy1 pAttrValSpec pSEP-    return $ ContentRecord dn attrVals--pRec :: CharParser st LDIFRecord-pRec = try pChangeRec <|> pAttrValRec--pChangeRec :: CharParser st LDIFRecord-pChangeRec = try pChangeAdd-         <|> try pChangeDel-         <|> try pChangeMod-         <|> pChangeModDN+    recs `seq` return $ LDIF ver recs+  where+    pVersionSpec :: Parser BC.ByteString+    pVersionSpec = do+      _ <- string "version:"+      pFILL conf+      xs <- many1 digit+      pSEPs1 conf+      let ys = xs `seq` BC.pack xs+      ys `seq` return $ ys+    pSearchResult :: Parser ()+    pSearchResult = do+      _ <- string "search:"+      pFILL conf+      _ <- many1 digit+      pSEP conf+      _ <- string "result:"+      pFILL conf+      _ <- pSafeString conf+      pSEPs conf+      return () -pChangeAdd :: CharParser st LDIFRecord-pChangeAdd = do-    dn <- pDNSpec-    pSEP-    _ <- string "changetype:"-    pFILL+pRec :: LDIFParserConfig -> Parser LDIFRecord+pRec conf = do +  dn <- pDNSpec+  pSEP conf+  try (pChangeRec dn) <|> (pAttrValRec dn)+    where+      pDNSpec :: Parser DN+      pDNSpec = do+        _ <- string "dn:"+        pDN conf+      pAttrValRec :: DN -> Parser LDIFRecord+      pAttrValRec dn = do+        attrVals <- sepEndBy1 (pAttrValSpec conf) (pSEP conf)+        attrVals `seq` return $ ContentRecord dn attrVals+      pChangeRec :: DN -> Parser LDIFRecord+      pChangeRec dn = do+        _ <- string "changetype:"+        pFILL conf+        try (pChangeAdd conf dn)+          <|> try (pChangeDel conf dn)+          <|> try (pChangeMod conf dn)+          <|> (pChangeModDN conf dn)+                      +pChangeAdd :: LDIFParserConfig -> DN -> Parser LDIFRecord+pChangeAdd conf dn  = do     _ <- string "add"-    pSEP-    vals <- sepEndBy1 pAttrValSpec pSEP+    pSEP conf+    vals <- sepEndBy1 (pAttrValSpec conf) (pSEP conf)     return $ ChangeRecord dn (ChangeAdd vals) -pChangeDel :: CharParser st LDIFRecord-pChangeDel = do-    dn <- pDNSpec-    pSEP-    _ <- string "changetype:"-    pFILL+pChangeDel :: LDIFParserConfig -> DN -> Parser LDIFRecord+pChangeDel conf dn = do     _ <- string "delete"-    pSEP+    pSEP conf     return $ ChangeRecord dn ChangeDelete -pChangeMod :: CharParser st LDIFRecord-pChangeMod = do-    dn <- pDNSpec-    pSEP-    _ <- string "changetype:"-    pFILL+pChangeMod :: LDIFParserConfig -> DN -> Parser LDIFRecord+pChangeMod conf dn = do     _ <- string "modify"-    pSEP-    mods <- sepEndBy1 pModSpec (char '-' >> pSEP)+    pSEP conf+    mods <- sepEndBy1 (pModSpec conf) (char '-' >> pSEP conf)     return $ ChangeRecord dn (ChangeModify mods) -pChangeModDN :: CharParser st LDIFRecord-pChangeModDN = do-    dn <- pDNSpec-    pSEP-    _ <- string "changetype:"-    pFILL+pChangeModDN :: LDIFParserConfig -> DN -> Parser LDIFRecord+pChangeModDN conf dn = do     _ <- string "modrdn" -    pSEP+    pSEP conf     _ <- string "newrdn:"-    pFILL -    _ <- pRDN-    pSEP+    pFILL conf+    _ <- pRDN conf+    pSEP conf     _ <- string "deleteoldrdn:"-    pFILL+    pFILL conf     _ <- oneOf "01"-    pSEP+    pSEP conf     return $ ChangeRecord dn ChangeModDN -pRDN :: CharParser st String-pRDN = pSafeString--pDNSpec :: CharParser st DN-pDNSpec = do-    _ <- string "dn:"-    pDN+pRDN :: LDIFParserConfig -> Parser BC.ByteString+pRDN conf = pSafeString conf -pDN :: CharParser st DN-pDN = do-   pFILL-   avals <- sepEndBy pAttrEqValue (char ',')  -   return $ DN avals+pDN :: LDIFParserConfig -> Parser DN+pDN conf = do+   pFILL conf+   avals <- sepEndBy (pAttrEqValue conf) (char ',')+   avals `seq` return $ DN avals -pAttrEqValue :: CharParser st AttrValue-pAttrEqValue = do-   pFILL-   att <- pAttributeType+pAttrEqValue :: LDIFParserConfig -> Parser AttrValue+pAttrEqValue conf = do+   pFILL conf+   att <- pAttributeType conf    _ <- char '='-   val <- pAttrValueDN-   return (att,val)+   val <- pAttrValueDN conf+   att `seq` val `seq` return (att,val) -pAttrValueDN :: CharParser st Value-pAttrValueDN = do-   many allChar+pAttrValueDN :: LDIFParserConfig -> Parser Value+pAttrValueDN conf = do+   xs <- many1 allChar+   let ys = xs `seq` (mkVal conf $ BC.pack xs)+   ys `seq` return $ ys    where -     allChar = try (escChar) <|> try (hexChar) <|> (noneOf (escapedDNChars ++ "\n\r"))+     allChar = noneOf (escapedDNChars ++ "\n")+               <|> try (hexChar)+               <|> (escChar)      escChar = do        _ <- char '\\'        oneOf escapedDNChars      hexChar = do        _ <- char '\\'-       hval <- count 2 hexDigit+       hval <- PR.count 2 hexDigit        case readHex hval of          [(val,[])] -> return $ chr val          _          -> fail $ "invalid hex value: " ++ hval -pVersionSpec :: CharParser st String-pVersionSpec = do-   _ <- string "version:"-   pFILL-   many1 digit--pModSpec :: CharParser st Modify-pModSpec = do-   modType <- pModType-   pFILL-   att <- pAttributeDescription -   pSEP -   vals <- sepEndBy pAttrValSpec pSEP+pModSpec :: LDIFParserConfig -> Parser Modify+pModSpec conf = do+   modType <- pModType conf+   pFILL conf+   att <- pAttributeDescription conf+   pSEP conf+   vals <- sepEndBy (pAttrValSpec conf) (pSEP conf)    return $ mkMod modType att vals  mkMod :: String -> Attribute -> [AttrValue] -> Modify mkMod modType att vals | modType == "add:" = ModAdd att (map (snd) vals)                        | modType == "delete:" = ModDelete att (map (snd) vals)                        | modType == "replace:" = ModReplace att (map (snd) vals)-                       | otherwise = error $ "unexpected mod:" ++ modType +                       | otherwise = error $ "unexpected mod:" ++ modType                          -- error can not be reached because pModType -pModType :: CharParser st String-pModType = try (string "add:")+pModType :: LDIFParserConfig -> Parser String+pModType _ = try (string "add:")        <|> try (string "delete:")        <|> string "replace:" -pAttributeDescription :: CharParser st Attribute-pAttributeDescription = pAttributeType+pAttributeDescription :: LDIFParserConfig -> Parser Attribute+pAttributeDescription conf = pAttributeType conf -pAttributeType :: CharParser st Attribute-pAttributeType = try pLdapOid-             <|> (do { l <- letter; o <- pAttrTypeChars; return (Attribute $ l:o) } )+pAttributeType :: LDIFParserConfig -> Parser Attribute+pAttributeType _ = try pLdapOid+             <|> pCharType+   where+      pDotOid = do+         _ <- string "." +         n <- many1 digit+         let xs = n `seq` '.':n+         xs `seq` return xs+      pLdapOid = do+        num <- many1 digit+        rest <- many1 pDotOid+        let xs = num `seq` rest `seq` num ++ concat rest+        xs `seq` return (Attribute $ BC.pack xs)+      pCharType = do+         l <- letter +         o <- pAttrTypeChars+         let xs = l `seq` o `seq` l `BC.cons` o+         xs `seq` return $ Attribute xs+           where+             pAttrTypeChars :: Parser BC.ByteString+             pAttrTypeChars = do +               xs <- many (satisfy (\x -> isAlphaNum x || x == '-'))+               let ys = xs `seq` BC.pack xs+               ys `seq` return ys -pAttrValSpec :: CharParser st AttrValue-pAttrValSpec = do-   name <- pAttributeDescription-   val  <- pValueSpec-   return (name, val) -pValueSpec :: CharParser st Value-pValueSpec = try (char ':' >> char ':' >> pFILL >> pBase64String)-         <|> try (char ':' >> pFILL >> pSafeString') -         <|> (char ':' >> char '<' >> pFILL >> pURL)+pAttrValSpec :: LDIFParserConfig -> Parser AttrValue+pAttrValSpec conf = do+   name <- pAttributeDescription conf+   val  <- pValueSpec+   name `seq` val `seq` return (name, val)+     where+       pValueSpec :: Parser Value+       pValueSpec = try (char ':' >> pFILL conf >> pSafeString' conf >>= (\x -> return $ mkVal conf x))+                    <|> try (char ':' >> char ':' >> pFILL conf >> pBase64String conf >>= (\x -> return $ mkVal conf x))+                    <|> (char ':' >> char '<' >> pFILL conf >> pURL conf >>= (\x -> return $ mkVal conf x)) -pURL :: CharParser st String-pURL = pSafeString+pURL :: LDIFParserConfig -> Parser BC.ByteString+pURL conf = pSafeString conf -pSafeString :: CharParser st String-pSafeString = do-   c <- noneOf "\n\r :<"-   r <- many (noneOf "\n\r")-   return $ c:r+pSafeString :: LDIFParserConfig -> Parser BC.ByteString+pSafeString _ = do+   c <- noneOf "\n :<"+   r <- many (noneOf "\n")   +   let xs = r `seq` c:r+   let ys = xs `seq` BC.pack xs+   ys `seq` return ys -pSafeString' :: CharParser st String-pSafeString' = do-   r <- many (noneOf "\n\r")-   return r+pSafeString' :: LDIFParserConfig -> Parser BC.ByteString+pSafeString' _ = do+   r <- many (noneOf "\n")+   let ys = r `seq` BC.pack r+   ys `seq` return ys  -pBase64String :: CharParser st String-pBase64String = pSafeString--pAttrTypeChars :: CharParser st String-pAttrTypeChars = many (satisfy (\x -> isAlphaNum x || x == '-'))+pBase64String :: LDIFParserConfig -> Parser BC.ByteString+pBase64String conf = pSafeString conf -pLdapOid :: CharParser st Attribute-pLdapOid = do-   num <- many1 digit-   rest <- many (do { _ <- string "."; n <- many1 digit; return $ '.':n})-   return (Attribute $ num ++ concat rest)+pFILL :: LDIFParserConfig -> Parser ()+pFILL _ = skipMany (oneOf [' ', '\t']) -pFILL :: CharParser st ()-pFILL = skipMany (oneOf [' ', '\t'])+pSEP :: LDIFParserConfig -> Parser ()+pSEP _ = do+         _ <- newline+         return () -pSEP :: CharParser st ()-pSEP = try (char '\r' >> char '\n' >> return () )-   <|> (char '\n' >> return () )+pSEPs :: LDIFParserConfig -> Parser ()+pSEPs conf = many (pSEP conf) >> return () -pSEPs :: CharParser st ()-pSEPs = many pSEP >> return ()+pSEPs1 :: LDIFParserConfig -> Parser ()+pSEPs1 conf = many1 (pSEP conf) >> return () +mkVal :: LDIFParserConfig -> BC.ByteString -> Value+mkVal conf v | (lpCaseSensitive conf) = Value v+             | otherwise              = ValueI v
+ src/Text/LDIF/Preproc.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}++module Text.LDIF.Preproc ( preproc +                         , transposePos +                         , PosTable )+where+import Text.Parsec+import Text.Parsec.Error (setErrorPos)+import qualified Data.ByteString.Char8 as BC+import Data.List (foldl', sortBy)++-- | Opaque data necessary for relation between text after preprocessing and original+type PosTable = [ PosOp ]++data PosOp = PosOpAddLine { psLine :: Int }+           | PosOpWrap    { psLine :: Int, psW :: Int, psWP :: Int } deriving Show++data LdifLine = LdifLine     { llNum :: Int, llStr :: BC.ByteString }+              | LdifLineCont { llNum :: Int, llStr :: BC.ByteString }+              | LdifComment  { llNum :: Int, llStr :: BC.ByteString }++-- | Convert error position to original text before preprocessing+transposePos :: PosTable -> ParseError -> ParseError+transposePos ptab oe = setErrorPos npos oe+  where+    opos = errorPos oe+    npos = setSourceColumn (setSourceLine opos nlin) ncol+      where+        opIdx a b = (psLine a) `compare` (psLine b)+        ocord = (sourceLine opos,sourceColumn opos)+        (nlin,ncol) = calcPos (sortBy opIdx ptab) ocord++calcPos :: PosTable -> (Int, Int) -> (Int, Int)+calcPos xs cord = foldl' updatePos cord xs+  where+    updatePos (l0,c0) (PosOpAddLine l)    | l0 >= l        = (l0+1,c0)+    updatePos (l0,c0) (PosOpWrap l _ _)  | l0 >= l        = (l0+1,c0)+    updatePos (l0,c0) (PosOpWrap l w wp)  | (l0+1) == l && c0 > w && (c0-1-w) > wp  = (l0+1,c0)+    updatePos (l0,c0) (PosOpWrap l w wp)  | (l0+1) == l && c0 > w && (c0-1-w) <= wp = (l0+1,c0-w)+    updatePos x _ = x+      +-- | Preprocessing of LDIF file, concat wrapped lines and remove comment lines+preproc :: BC.ByteString -> (BC.ByteString, PosTable)+preproc xs = (str, ptab)+  where +    str = BC.unlines $ map llStr ys    +    (ys, ptab) = lns xs+      where+        lns zs = stripComments $ unwrap $ (tokenizeLines $ specLines zs, [])++specLines :: BC.ByteString -> [BC.ByteString]+specLines xs = map cleanLine $ BC.lines xs+  where+    isCR c = c == '\r'+    cleanLine l | BC.null l        = l+                | isCR (BC.last l) = cleanLine $ BC.init l+                | otherwise        = l++tokenizeLines :: [BC.ByteString] -> [LdifLine]+tokenizeLines xs = map tokenizeLine $ zip xs [1..]+  where+    tokenizeLine (x,i) | BC.null x        = LdifLine     i BC.empty+                       | BC.head x == '#' = LdifComment  i x+                       | BC.head x == ' ' = LdifLineCont i $ BC.tail x+                       | otherwise        = LdifLine     i x+                                            +-- | Remove Comment Lines+stripComments :: ([LdifLine],PosTable) -> ([LdifLine],PosTable)+stripComments (xs,pt) = foldl' procLine ([],pt) xs+  where+    procLine (v,p) (LdifComment i _) = (v,(PosOpAddLine i):p)+    procLine (v,p) o                 = (o:v,p)++-- | Unwrap lines, lines with space at begin is continue of previous line +unwrap :: ([LdifLine],PosTable) -> ([LdifLine],PosTable)+unwrap (xs,pt) = foldl' procLine ([],pt) xs+  where+    procLine ([],p) o = (o:[],p)+    procLine (v,p) (LdifLineCont i s) = let (z,r) = splitAt 1 v+                                            o = head z+                                            o' = o { llStr = (llStr o) `BC.append` s }+                                            p' = (PosOpWrap i (BC.length $ llStr o) (BC.length s)):p+                                        in (o':r,p')+    procLine (v,p) o                  = (o:v,p)
src/Text/LDIF/Printer.hs view
@@ -1,54 +1,59 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}+ -- | LDIF serializers module Text.LDIF.Printer ( 	ldif2str,+        ver2str,         dn2str, 	record2str ) where+import Prelude import Text.LDIF.Types import Text.LDIF.Consts-import Data.List+import qualified Data.ByteString.Char8 as BC+import Data.List as L import Data.Char import Numeric (showHex)  -- | Serialize LDIF in LDIF Format-ldif2str :: LDIF -> String-ldif2str (LDIF v xs) = unlines $ (ver2str v) ++ (map (record2str) xs)+ldif2str :: LDIF -> BC.ByteString+ldif2str (LDIF v xs) = BC.unlines $ (ver2str v) ++ (map (record2str) xs)  -- | Serialize version to LDIF Format Lines-ver2str :: Maybe String -> [String]+ver2str :: Maybe BC.ByteString -> [BC.ByteString] ver2str Nothing = []-ver2str (Just v) = ["version: "++v]+ver2str (Just v) = ["version: " `BC.append` v]  -- | Serialize DN to LDIF Format-dn2str :: DN -> String-dn2str xs = intercalate "," $ map (\((Attribute n),v) -> n++"="++(escapeDNVals v)) (dnAttrVals xs)--escapeDNVals :: String -> String-escapeDNVals vs = concat $ map escapeDNVal vs+dn2str :: DN -> BC.ByteString+dn2str xs = BC.intercalate "," $ map (\((Attribute n),v) -> n `BC.append` "="  `BC.append` (escapeDNVals $ aVal v)) (dnAttrVals xs)   where-    escapeDNVal x | not $ isPrint x          = '\\':(showHex (ord x) "")-                  | elem x escapedDNChars    = '\\':[x]-                  | otherwise                = [x]+    escapeDNVals :: BC.ByteString -> BC.ByteString+    escapeDNVals vs = BC.concat $ map escapeDNVal (BC.unpack vs)+      where+        escapeDNVal x | not $ isPrint x          = BC.pack $ '\\':(showHex (ord x) "")+                      | elem x escapedDNChars  = BC.pack $ '\\':[x]+                      | otherwise                = BC.pack $ [x]  -- | Serialize Content Record in LDIF Format-record2str :: LDIFRecord -> String-record2str (ContentRecord dn xs)                = unlines $ [ "dn: "++(dn2str dn) ] ++ (attrVals2Ln xs)-record2str (ChangeRecord dn (ChangeDelete))     = unlines   [ "dn: "++(dn2str dn), "changetype: delete" ]-record2str (ChangeRecord dn (ChangeAdd xs))     = unlines $ [ "dn: "++(dn2str dn), "changetype: add"    ] ++ (attrVals2Ln xs)-record2str (ChangeRecord dn (ChangeModify xs))  = unlines $ [ "dn: "++(dn2str dn), "changetype: modify" ] ++ (mods2Ln xs)-record2str (ChangeRecord dn (ChangeModDN))      = unlines $ [ "dn: "++(dn2str dn), "changetype: moddn"  ]+record2str :: LDIFRecord -> BC.ByteString+record2str (ContentRecord dn xs)                = BC.unlines $ [ "dn: " `BC.append` (dn2str dn) ] ++ (attrVals2Ln xs)+record2str (ChangeRecord dn (ChangeDelete))     = BC.unlines   [ "dn: " `BC.append` (dn2str dn), "changetype: delete" ]+record2str (ChangeRecord dn (ChangeAdd xs))     = BC.unlines $ [ "dn: " `BC.append` (dn2str dn), "changetype: add"    ] ++ (attrVals2Ln xs)+record2str (ChangeRecord dn (ChangeModify xs))  = BC.unlines $ [ "dn: " `BC.append` (dn2str dn), "changetype: modify" ] ++ (mods2Ln xs)+record2str (ChangeRecord dn (ChangeModDN))      = BC.unlines $ [ "dn: " `BC.append` (dn2str dn), "changetype: moddn"  ] -attrVals2Ln :: [AttrValue] -> [String]+attrVals2Ln :: [AttrValue] -> [BC.ByteString] attrVals2Ln xs = map (attrVal2Ln) xs -attrVal2Ln :: AttrValue -> String-attrVal2Ln ((Attribute n),v) = n ++ ": "++v--mods2Ln :: [Modify] -> [String]-mods2Ln xs = intercalate ["-"] $ map (mod2Ln) xs+attrVal2Ln :: AttrValue -> BC.ByteString+attrVal2Ln ((Attribute n),v) = BC.concat [ n,": ", aVal v ] -mod2Ln :: Modify -> [String]-mod2Ln (ModAdd     a@(Attribute nm) xs) = [ attrVal2Ln ((Attribute "add"),nm)     ] ++ (map (\v -> attrVal2Ln (a,v)) xs) -mod2Ln (ModDelete  a@(Attribute nm) xs) = [ attrVal2Ln ((Attribute "delete"),nm)  ] ++ (map (\v -> attrVal2Ln (a,v)) xs)-mod2Ln (ModReplace a@(Attribute nm) xs) = [ attrVal2Ln ((Attribute "replace"),nm) ] ++ (map (\v -> attrVal2Ln (a,v)) xs)+mods2Ln :: [Modify] -> [BC.ByteString]+mods2Ln xs = L.intercalate ["-"] $ map (mod2Ln) xs+  where+    mod2Ln :: Modify -> [BC.ByteString]+    mod2Ln (ModAdd     a zs) = [ attrVal2Ln ((Attribute "add"),Value $ aName a)     ] ++ (map (\v -> attrVal2Ln (a,v)) zs) +    mod2Ln (ModDelete  a zs) = [ attrVal2Ln ((Attribute "delete"),Value $ aName a)  ] ++ (map (\v -> attrVal2Ln (a,v)) zs)+    mod2Ln (ModReplace a zs) = [ attrVal2Ln ((Attribute "replace"),Value $ aName a) ] ++ (map (\v -> attrVal2Ln (a,v)) zs)
+ src/Text/LDIF/Tree.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}++-- | LDIF representation in Data.Tree structure+module Text.LDIF.Tree ( toTree, fromTree, sortTreeByName )+       where+import Prelude+import Text.LDIF.Types+import Text.LDIF.Utils+import Data.Tree+import Data.Maybe+import Data.List+import qualified Data.Set as S+import qualified Data.Tree.Zipper as Z++-- | Flatten Tree of Records to LDIF+fromTree :: Tree LDIFRecord -> LDIF+fromTree !xs = ys `seq` LDIF Nothing ys+  where+    ys = (filter (not . isFakeEntry) $ flatten xs)+      where+        isFakeEntry (ContentRecord _ []) = True+        isFakeEntry _ = False++-- | Convert LDIF to Tree of Records using their DNs. Can insert dummy parents.+toTree :: LDIF -> Bool -> Tree LDIFRecord+toTree (LDIF _ xs) False = fromRecords xs+toTree (LDIF _ xs) True  = fromRecords $ addFakeParents xs+        +addFakeParents :: [ LDIFRecord ] -> [ LDIFRecord ]        +addFakeParents entries = fakeParents ++ entries+  where +    fakeParents = map fakeParent missingDNs+      where+        fakeParent dn = ContentRecord dn []+        missingDNs = filter ((flip S.notMember) allDNs) $ S.toList parentDNs+          where+            allDNs = S.fromList $ map reDN entries+            parentDNs =  S.fromList $ map DN $ filter (not . null) $ concatMap (tails . dnAttrVals) $ S.toList allDNs+    +rootEntry :: Tree LDIFRecord    +rootEntry = Node (ContentRecord (DN []) []) []++fromRecords :: [LDIFRecord] -> Tree LDIFRecord+fromRecords xs = Z.toTree $ foldl' addEntry (Z.fromTree rootEntry) $ sortBy compareByDNLen xs+      where+        compareByDNLen a b = (lengthOfDN $ reDN a) `compare` (lengthOfDN $ reDN b)+        addEntry tree entry = Z.root $ Z.insert (Node entry []) $ findParent tree+          where+            findParent z | not $ Z.hasChildren z = Z.children z -- No children; put it here+                         | isNothing child       = Z.children z -- No matching child; put it here+                         | otherwise             = findParent $ fromJust child -- found matching child, continue+              where+                child = findChild $ Z.firstChild z -- Traverse all childs+                  where+                    findChild Nothing  = Nothing -- Nothing found+                    findChild (Just c)  | (Z.label c) `isParentRecordOf` entry = Just c  -- Found+                                        | otherwise                            = findChild $ Z.next c -- Continue++-- | Sort recursively children Records by DNs+sortTreeByName :: Tree LDIFRecord -> Tree LDIFRecord+sortTreeByName (Node n []) = Node n []+sortTreeByName (Node n xs) = let ys = sortBy cmpDN xs+                                 cmpDN a b = (reDN $ rootLabel a) `compare` (reDN $ rootLabel b)+                             in Node n (map sortTreeByName ys)
src/Text/LDIF/Types.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}+ -- | LDIF related types module Text.LDIF.Types (  	LDIF(..),   @@ -6,26 +8,44 @@         Modify(..),          DN(..),          LDIFType(..),-        Attribute(..), Value, AttrValue+        Attribute(..), Value(..), AttrValue,+        isContentRecord,+        isChangeRecord,+        getLDIFType ) where+import qualified Data.ByteString.Char8 as BC import Data.Char -newtype Attribute = Attribute String deriving Show+-- | Attribute name is case-insensitive string+data Attribute = Attribute { aName :: BC.ByteString } deriving Show  instance Eq Attribute where-    (Attribute xs) == (Attribute ys)  = (map toUpper xs) == (map toUpper ys)+    (Attribute xs) == (Attribute ys)  = (BC.map toUpper xs) == (BC.map toUpper ys)  instance Ord Attribute where-    (Attribute xs) `compare` (Attribute ys)  = (map toUpper xs) `compare` (map toUpper ys)+    (Attribute xs) `compare` (Attribute ys)  = (BC.map toUpper xs) `compare` (BC.map toUpper ys) -type Value = String+-- | Attribute value is either case sensitive or insensitive string+data Value = Value  { aVal :: BC.ByteString }+           | ValueI { aVal :: BC.ByteString } deriving Show+             +instance Eq Value where+    (Value xs) == (Value ys)  = xs == ys+    xs == ys  = (BC.map toUpper $ aVal xs) == (BC.map toUpper $ aVal ys)++instance Ord Value where+    (Value xs) `compare` (Value ys)  = xs `compare` ys+    xs `compare` ys = (BC.map toUpper $ aVal xs) `compare` (BC.map toUpper $ aVal ys)++-- | Pair of Atribute and Value type AttrValue = (Attribute, Value) --- | Type of LDIF Files (Content, Changes)-data LDIFType = LDIFContentType -              | LDIFChangesType -              | LDIFMixedType deriving Eq+-- | Enumeration LDIF Types+data LDIFType = LDIFContentType -- ^ LDIF with Content Records+              | LDIFChangesType -- ^ LDIF with Changes Records+              | LDIFMixedType   -- ^ LDIF with both Content and Changes Records+              deriving Eq  instance Show LDIFType where     show LDIFChangesType = "Delta"@@ -33,30 +53,60 @@     show LDIFMixedType   = "Mixed"  -- | Represents LDIF structure, it can be either simply LDIF data dump or--- | changes LDIF with LDAP operations -data LDIF = LDIF { lcVersion :: Maybe String, lcEntries :: [LDIFRecord] } deriving (Show, Eq)+-- changes LDIF with LDAP operations +data LDIF = LDIF { lcVersion :: Maybe BC.ByteString, lcEntries :: ![LDIFRecord] } deriving (Show, Eq) --- | Represents one data record within LDIF file with DN and content--- | Represents one change record within LDIF file with DN and content-data LDIFRecord = ContentRecord { reDN :: DN, coAttrVals :: [AttrValue] } -                | ChangeRecord  { reDN :: DN, chOp :: Change } deriving (Show, Eq)+data LDIFRecord+  -- | Represents one data record within LDIF file with DN and content+  = ContentRecord { reDN :: !DN, coAttrVals :: ![AttrValue] } +  -- | Represents one change record within LDIF file with DN and content+  | ChangeRecord  { reDN :: !DN, chOp :: !Change } deriving (Show, Eq)  -- | Represents one LDAP operation within changes LDIF-data Change = ChangeAdd     { chAttrVals :: [AttrValue] }+data Change = ChangeAdd     { chAttrVals :: ![AttrValue] }             | ChangeDelete -            | ChangeModify  { chMods :: [Modify] }+            | ChangeModify  { chMods :: ![Modify] }             | ChangeModDN  deriving (Show, Eq)  -- | Represents ChangeModify operations upon one entry within given DN-data Modify = ModAdd     { modAttr :: Attribute, modAttrVals :: [Value] }-            | ModDelete  { modAttr :: Attribute, modAttrVals :: [Value] }-            | ModReplace { modAttr :: Attribute, modAttrVals :: [Value] } deriving (Show, Eq)+data Modify = ModAdd     { modAttr :: !Attribute, modAttrVals :: ![Value] }+            | ModDelete  { modAttr :: !Attribute, modAttrVals :: ![Value] }+            | ModReplace { modAttr :: !Attribute, modAttrVals :: ![Value] } deriving (Show, Eq)  -- | Represents Distinguished Name (DN)-data DN = DN { dnAttrVals :: [AttrValue] } -        | DNi { dnAttrVals :: [AttrValue] } deriving (Show)+data DN = DN { dnAttrVals :: ![AttrValue] } deriving (Eq, Show) -instance Eq DN where-    (DN xs)  == (DN ys)   = xs == ys-    (DNi xs) == (DNi ys)  = (map (\(n,v) -> (n,(map toUpper v)))  xs) == (map (\(n,v) -> (n,(map toUpper v))) ys)-    x        == y         = (DNi (dnAttrVals x)) == (DNi (dnAttrVals y))+-- | Ord Instance for DN+instance Ord DN where+  (DN xs1) `compare` (DN xs2)  = let cmpAV ((a1,v1),(a2,v2)) = let ca = a1 `compare` a2+                                                                   cv = v1 `compare` v2+                                                               in if ca == EQ then cv else ca+                                     dx = map cmpAV $ zip (reverse $ xs1) (reverse $ xs2)+                                     lx | length xs1 > length xs2 = GT+                                        | length xs1 < length xs2 = LT+                                        | otherwise = EQ+                                 in case filter (EQ /=) dx of+                                   []    -> lx+                                   (x:_) -> x++-- | Check if LDIFRecord is Content Record+isContentRecord :: LDIFRecord -> Bool+isContentRecord (ContentRecord _ _) = True+isContentRecord _ = False++-- | Check if LDIFRecord is Change Record+isChangeRecord :: LDIFRecord -> Bool+isChangeRecord (ChangeRecord _ _) = True+isChangeRecord _ = False++-- | Dettect from LDIF content the Type (Content, Changes, Mixed)+getLDIFType :: LDIF -> LDIFType+getLDIFType (LDIF _ []) = LDIFContentType+getLDIFType (LDIF _ xs) = getLDIFType' con chg+    where+      con = filter (isContentRecord) xs+      chg = filter (not . isContentRecord) xs+      getLDIFType' [] [] = LDIFContentType -- Fallback Empty LDIF as an Content LDIF+      getLDIFType' [] _  = LDIFChangesType+      getLDIFType' _  [] = LDIFContentType+      getLDIFType' _  _  = LDIFMixedType
+ src/Text/LDIF/Undo.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}++module Text.LDIF.Undo (+        undoLDIF+)+where+import Data.Maybe+import Text.LDIF.Types++-- | Warning message when undo can not be calculated+type Warning = String++-- | Calculate undo LDIF+undoLDIF :: LDIF -> (LDIF,[[Warning]])+undoLDIF (LDIF v xs) = let (ys,w) = let zs = map undoRecord $ reverse xs+                                    in (map fst zs, map snd zs)+                       in (LDIF v ys, filter (not . null) w)++-- | Calculate undo Record with possible warnings+undoRecord :: LDIFRecord -> (LDIFRecord,[Warning])+undoRecord (ContentRecord dn _) = (ChangeRecord dn ChangeDelete,[])+undoRecord (ChangeRecord  dn (ChangeAdd _))     = (ChangeRecord dn ChangeDelete,[])+undoRecord (ChangeRecord  dn ChangeDelete)      = (ChangeRecord dn (ChangeAdd []), [wrnO dn "delete"])+undoRecord (ChangeRecord  dn (ChangeModify xs)) = let (x, w) = let ys = map undoMod $ reverse xs+                                                               in (map fst ys, map snd ys)+                                                  in (ChangeRecord dn (ChangeModify x), catMaybes w)+undoRecord x = (x, ["Unsupported operation"])++-- | Calculate undo Modification with possible warning+undoMod :: Modify -> (Modify,Maybe Warning)+undoMod (ModAdd a v)     = (ModDelete a v, Nothing)+undoMod (ModDelete a [])  = (ModAdd a [], Just $ wrnA a "delete")+undoMod (ModDelete a zs)  = (ModAdd a zs, Nothing)+undoMod (ModReplace a _) = (ModReplace a [], Just $ wrnA a "replace")++wrnA :: Attribute -> String -> String+wrnA a op = concat [show $ aName a, " ", op]++wrnO :: DN -> String -> String+wrnO dn op = concat [show dn, " ", op]
src/Text/LDIF/Utils.hs view
@@ -1,43 +1,32 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-}+ -- | LDIF related operations-module Text.LDIF.Utils (-        findRecordsByDN,-	findRecordByDN,-        isDNPrefixOf,-        sizeOfDN,-        takeDNPrefix,-        leafOfDN,-        rootOfDN,-        lookupAttr,-        filterAttr,-        isDummyRecord,-        ldif2tree,-        getLDIFType,-        isContentRecord,-        isChangeRecord,-        dn2dnI,-        ldif2ldifI-)-where+module Text.LDIF.Utils +       where+import Prelude import Text.LDIF.Types-import Data.Tree+import qualified Data.ByteString.Char8 as BC+import qualified Data.Map as M +type LDIFCache = M.Map DN LDIFRecord++createLookupTable :: LDIF -> LDIFCache+createLookupTable (LDIF _ xs) = M.fromList $ map (\x -> (reDN x, x)) xs+ -- | Find all Contents with given DN findRecordsByDN :: LDIF -> DN -> [LDIFRecord] findRecordsByDN (LDIF _ entries) dn = filter (\x -> (reDN x) == dn) entries  -- | Find first Content with given DN-findRecordByDN :: LDIF -> DN -> Maybe LDIFRecord-findRecordByDN ldif dn = case findRecordsByDN ldif dn of-                                 []   -> Nothing-                                 xs   -> Just (head xs)-+findRecordByDN :: LDIFCache -> DN -> Maybe LDIFRecord+findRecordByDN cache dn = M.lookup dn cache  -- | Find fist Attribute within attributes pairs list-lookupAttr :: String -> [AttrValue] -> Maybe Value+lookupAttr :: BC.ByteString -> [AttrValue] -> Maybe Value lookupAttr attr xs = lookup (Attribute attr) xs  -- | Filter Attribute Value list according Attribute name-filterAttr :: String -> [AttrValue] -> [AttrValue]+filterAttr :: BC.ByteString -> [AttrValue] -> [AttrValue] filterAttr attr xs  = filter (\x -> (Attribute attr) == fst x) xs  -- | Change record without any impact@@ -49,66 +38,27 @@ leafOfDN xs = getDNValue xs 0  rootOfDN :: DN -> AttrValue-rootOfDN xs = getDNValue xs ((sizeOfDN xs)-1)+rootOfDN xs = getDNValue xs ((lengthOfDN xs)-1) -sizeOfDN :: DN -> Int-sizeOfDN xs = length (dnAttrVals xs)+lengthOfDN :: DN -> Int+lengthOfDN xs = length (dnAttrVals xs)  getDNValue :: DN -> Int -> AttrValue getDNValue xs idx = (dnAttrVals xs) !! idx  takeDNPrefix :: DN -> Int -> DN takeDNPrefix (DN vals) n  = (DN (reverse $ take n (reverse vals)))-takeDNPrefix (DNi vals) n = (DNi (reverse $ take n (reverse vals)))  -- | Check if the dn1 is prefix of dn2 isDNPrefixOf :: DN -> DN -> Bool-isDNPrefixOf dn1 dn2 | (sizeOfDN dn1) >= (sizeOfDN dn2) = False-                     | otherwise = let n = (sizeOfDN dn1)+isDNPrefixOf dn1 dn2 | (lengthOfDN dn1) >= (lengthOfDN dn2) = False+                     | otherwise = let n = (lengthOfDN dn1)                                    in (takeDNPrefix dn2 n) == dn1 -dummyRootDN :: DN-dummyRootDN = DN [(Attribute "dc", "root")]--ldif2tree :: LDIF -> Tree LDIFRecord-ldif2tree (LDIF _ entries) = Node (ChangeRecord dummyRootDN (ChangeAdd [])) (ldifRecs2tree entries)- isParentRecordOf :: LDIFRecord -> LDIFRecord -> Bool isParentRecordOf a b = isDNPrefixOf (reDN a) (reDN b) -ldifRoots :: [LDIFRecord] -> [LDIFRecord]-ldifRoots xs = let isRoot x = all (\y -> not $ isParentRecordOf y x) xs-               in filter (isRoot) xs -ldifRecs2tree :: [LDIFRecord] -> [Tree LDIFRecord]-ldifRecs2tree xs = let roots = (ldifRoots xs)-                       subtr x = ldifRecs2tree $ filter (isParentRecordOf x) xs-                   in map (\x -> Node x (subtr x)) roots--isContentRecord :: LDIFRecord -> Bool-isContentRecord (ContentRecord _ _) = True-isContentRecord _ = False--isChangeRecord :: LDIFRecord -> Bool-isChangeRecord (ChangeRecord _ _) = True-isChangeRecord _ = False--getLDIFType :: LDIF -> LDIFType-getLDIFType (LDIF _ []) = LDIFContentType-getLDIFType (LDIF _ xs) = getLDIFType' con chg-    where-      con = filter (isContentRecord) xs-      chg = filter (not . isContentRecord) xs-      getLDIFType' [] [] = error "Unexpected"-      getLDIFType' [] _  = LDIFChangesType-      getLDIFType' _  [] = LDIFContentType-      getLDIFType' _  _  = LDIFMixedType--dn2dnI :: DN -> DN-dn2dnI (DN xs) = (DNi xs)-dn2dnI xs = xs-+-- | Make LDIF Values case-insensitive ldif2ldifI :: LDIF -> LDIF-ldif2ldifI (LDIF v xs) = LDIF v ys-    where-      ys = map (\x -> x { reDN = dn2dnI (reDN x) } )  xs+ldif2ldifI = undefined
− src/diffLDIF.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--- | Make Change LDIF based on two Content LDIFs-import Data.List-import Data.Either-import Data.Maybe-import Control.Monad -import System.FilePath -import System.Environment-import Text.LDIF-import System.Console.CmdArgs--progDesc = "Create delta LDIF between Source LDIF and Target LDIF"--data DiffLdif = DiffLdif { srcFile :: FilePath-                         , dstFile :: FilePath } deriving (Show, Data, Typeable)--defaultCfg = DiffLdif { srcFile = def &= typFile &= name "s" &= help "Source LDIF File"-                      , dstFile = def &= typFile &= name "t" &= help "Target LDIF File" }--main = do-  cfg <- cmdArgs defaultCfg-  execute cfg--execute (DiffLdif []  _  ) = putStrLn "Error: -s source file is mandatory" -execute (DiffLdif _ []   ) = putStrLn "Error: -t target file is mandatory"-execute (DiffLdif src dst) = do-  ml1 <- parseLDIFFile src-  ml2 <- parseLDIFFile dst-  case rights [ml1,ml2] of -       [l1,l2] -> case diffLDIF l1 l2 of-                    Left err -> putStrLn err-                    Right delta -> putStrLn $ ldif2str delta-       _       -> print $ lefts [ml1,ml2] 
src/ldif2html.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns, OverloadedStrings #-} -- | Make Change LDIF based on two Content LDIFs import Data.List import Data.Either@@ -7,69 +8,81 @@ import System.Environment import Text.LDIF import qualified Data.Set as Set--ldif2html :: Set.Set String -> LDIF -> String-ldif2html idx (LDIF v xs) = unlines $ (ver2str v) ++ (map (record2html idx) xs)---- | Serialize version to LDIF Format Lines-ver2str :: Maybe String -> [String]-ver2str Nothing = []-ver2str (Just v) = ["version: "++v]+import qualified Data.ByteString.Char8 as BC  -- | Serialize DN to LDIF Format-dn2html :: DN -> String-dn2html ys@(DN xs) = concat [ "<div id=\"abc"++nm++"\"></div><b>dn:</b> <font color=\"green\"><b>", dnstr, "</b></font>"]+dn2html :: DN -> BC.ByteString+dn2html ys@(DN xs) = BC.concat [ "<div id=\"abc" +                               , nm +                               , "\"></div><b>dn:</b> <font color=\"green\"><b>" +                               , dnstr +                               , "</b></font>" ]       where-        dnstr = intercalate "," $ map (\((Attribute n),v) -> n++"="++v) xs-        nm = dn2last ys--dn2last (DN xs) = snd $ head xs+        dnstr = BC.intercalate "," $ map (\((Attribute n),v) -> n `BC.append` "=" `BC.append` (aVal v)) xs+        nm = aVal $ dn2last ys  -- | Serialize Change Record in LDIF Format-record2html :: Set.Set String -> LDIFRecord -> String-record2html idx (ChangeRecord dn (ChangeDelete))     = unlines   [ (dn2str dn), "changetype: delete" ]-record2html idx (ChangeRecord dn (ChangeAdd xs))    = unlines $ [ (dn2html dn), "changetype: add"    ] ++ (attrVals2Ln idx xs)-record2html idx (ChangeRecord dn (ChangeModify xs))  = unlines $ [ (dn2str dn), "changetype: modify" ] ++ (mods2Ln idx xs)-record2html idx (ChangeRecord dn (ChangeModDN))      = unlines $ [ (dn2str dn), "changetype: moddn"  ]-record2html idx (ContentRecord dn xs) = unlines $ [ (dn2html dn) ] ++ (attrVals2Ln idx xs)+record2html :: Set.Set Value -> LDIFRecord -> BC.ByteString+record2html idx (ChangeRecord dn (ChangeDelete))     = BC.unlines   [ (dn2str dn), "changetype: delete" ]+record2html idx (ChangeRecord dn (ChangeAdd xs))     = BC.unlines $ [ (dn2html dn), "changetype: add"    ] ++ (attrVals2Ln idx xs)+record2html idx (ChangeRecord dn (ChangeModify xs))  = BC.unlines $ [ (dn2str dn), "changetype: modify" ] ++ (mods2Ln idx xs)+record2html idx (ChangeRecord dn (ChangeModDN))      = BC.unlines $ [ (dn2str dn), "changetype: moddn"  ]+record2html idx (ContentRecord dn xs) = BC.unlines $ [ (dn2html dn) ] ++ (attrVals2Ln idx xs) -attrVals2Ln :: Set.Set String -> [AttrValue] -> [String]+attrVals2Ln :: Set.Set Value -> [AttrValue] -> [BC.ByteString] attrVals2Ln idx xs = map (attrVal2Ln idx) xs -attrVal2Ln :: Set.Set String -> AttrValue -> String-attrVal2Ln idx ((Attribute n),v) = if Set.member v idx then "<b>"++n++"</b> : <a href=\"#abc"++v++"\">"++v++"</a>"-                          else "<b>"++n++"</b> : "++v+attrVal2Ln :: Set.Set Value -> AttrValue -> BC.ByteString+attrVal2Ln idx ((Attribute n),v) = if Set.member v idx then BC.concat [ "<b>"+                                                                      , n +                                                                      , "</b> : <a href=\"#abc"+                                                                      , aVal v+                                                                      , "\">" +                                                                      , aVal v+                                                                      , "</a>"]+                          else BC.concat [ "<b>",n,"</b> : ",aVal v] -mods2Ln :: Set.Set String -> [Modify] -> [String]+mods2Ln :: Set.Set Value -> [Modify] -> [BC.ByteString] mods2Ln idx xs = intercalate ["-"] $ map (mod2Ln idx) xs -mod2Ln :: Set.Set String -> Modify -> [String]-mod2Ln idx (ModAdd     a@(Attribute nm) xs) = [ attrVal2Ln idx ((Attribute "add"),nm)     ] ++ (map (\v -> attrVal2Ln idx (a,v)) xs) -mod2Ln idx (ModDelete  a@(Attribute nm) xs) = [ attrVal2Ln idx ((Attribute "delete"),nm)  ] ++ (map (\v -> attrVal2Ln idx (a,v)) xs)-mod2Ln idx (ModReplace a@(Attribute nm) xs) = [ attrVal2Ln idx ((Attribute "replace"),nm) ] ++ (map (\v -> attrVal2Ln idx (a,v)) xs)+mod2Ln :: Set.Set Value -> Modify -> [BC.ByteString]+mod2Ln idx mod = [ attrVal2Ln idx (modName,attrName) ] ++ valLst+  where+    attrName = Value $ aName $ modAttr mod+    valLst = map (\v -> attrVal2Ln idx (modAttr mod,v)) $ modAttrVals mod+    modName = case mod of +      (ModAdd _ _)     -> Attribute "add"+      (ModDelete _ _)  -> Attribute "delete"+      (ModReplace _ _) -> Attribute "replace" --- Dummy ldif2html implementation but it should--- display LDIF file as HTML with values as href to--- DN which has the value as the leaf RDN value+dns :: LDIF -> Set.Set Value+dns (LDIF _ xs) = Set.fromList $ nub $ map (dn2last . reDN) xs++dn2last (DN xs) = snd $ head xs+ main = do   args <- getArgs   case args of       []         -> putStrLn "Usage: ldif2html <input.ldif> [<input2.ldif> <input3.ldif> ... <inputN.ldif> <output.ldif>]"      [inp]      -> do-                      doLDIF2HTML [inp] (putStrLn)+                      doLDIF2HTML [inp] (BC.putStrLn)                       putStrLn "# Done."      xs         -> do-                      doLDIF2HTML (init xs) (writeFile (last xs)) +                      doLDIF2HTML (init xs) (BC.writeFile (last xs))                        putStrLn $ "## Processed : " ++ (unlines $ init xs)                       putStrLn $ "## Written   : " ++ (last xs)+  where+    doLDIF2HTML names outF = do+      mls <- mapM (parseLDIFFile defaulLDIFConf) names+      case lefts mls of +        []   -> do+                let idx = Set.unions $ map dns $ rights mls+                outF $ BC.concat [ "<html><body bgcolor=lightyellow><pre>"+                                 , BC.concat $ map (ldif2html idx) (rights mls) +                                 , "</pre></body></html>" ]+        xs  -> mapM_ print xs+      -- | Serialize LDIF to HTML+    ldif2html :: Set.Set Value -> LDIF -> BC.ByteString+    ldif2html idx (LDIF v xs) = BC.unlines $ (ver2str v) ++ (map (record2html idx) xs) -doLDIF2HTML names outF = do-  mls <- mapM (parseLDIFFile) names-  case lefts mls of -       []   -> do-                 let idx = Set.unions $ map (dns) (rights mls)-                 outF $ concat ["<html><body bgcolor=lightyellow><pre>", concat $ map (ldif2html idx) (rights mls), "</pre></body></html>" ]-       xs  -> mapM_ print xs -dns :: LDIF -> Set.Set String-dns (LDIF _ xs) = Set.fromList $ nub $ map (dn2last . reDN) xs
+ src/ldifdiff.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveDataTypeable #-}+-- | Make Change LDIF based on two Content LDIFs+import Data.List+import Data.Either+import Data.Maybe+import Control.Monad +import System.FilePath +import System.Environment+import Text.LDIF+import System.Console.CmdArgs+import qualified Data.ByteString.Char8 as BC++progDesc = "Create delta LDIF between Source LDIF and Target LDIF"++data LdifDiff = LdifDiff { srcFile :: FilePath+                         , dstFile :: FilePath } deriving (Show, Data, Typeable)++defaultCfg = LdifDiff { srcFile = def &= typFile &= name "s" &= help "Source LDIF File"+                      , dstFile = def &= typFile &= name "t" &= help "Target LDIF File" }++main = do+  cfg <- cmdArgs defaultCfg+  execute cfg++execute (LdifDiff []  _  ) = putStrLn "Error: -s source file is mandatory" +execute (LdifDiff _ []   ) = putStrLn "Error: -t target file is mandatory"+execute (LdifDiff src dst) = do+  ml1 <- parseLDIFFile defaulLDIFConf src+  ml2 <- parseLDIFFile defaulLDIFConf dst+  case rights [ml1,ml2] of +       [l1,l2] -> case diffLDIF l1 l2 of+                    Left err -> putStrLn err+                    Right delta -> BC.putStrLn $ ldif2str delta+       _       -> print $ lefts [ml1,ml2] 
src/ldifmodify.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-} -- | Apply LDAP operations within LDIF on another LDIF. -- | Without schema related verification like syntax, cardinality etc. import Data.List@@ -9,6 +9,7 @@ import System.Environment import Text.LDIF import System.Console.CmdArgs+import qualified Data.ByteString.Char8 as BC  progDesc = "Apply LDAP operations from LDIF to LDIF (like ldapmodify)" @@ -32,10 +33,10 @@   modLDIFs <- mapM (safeParseLDIFFile) (modFiles cfg)   let outLDIF = foldr (flip applyLDIF) baseLDIF modLDIFs   if length (outFile cfg) == 0 then do-      putStrLn (ldif2str outLDIF)+      BC.putStrLn (ldif2str outLDIF)     else do-      writeFile (outFile cfg) (ldif2str outLDIF)+      BC.writeFile (outFile cfg) (ldif2str outLDIF)       putStrLn $ (outFile cfg) ++ " written."  safeParseLDIFFile :: FilePath -> IO LDIF-safeParseLDIFFile name = liftM (either (\e -> error $ "Can not parse: "++(show e)) (id)) (parseLDIFFile name)+safeParseLDIFFile name = liftM (either (\e -> error $ "Can not parse: "++(show e)) (id)) (parseLDIFFile defaulLDIFConf name)
src/ldifparse.hs view
@@ -6,13 +6,12 @@ import System.FilePath  import System.Environment import Text.LDIF+import qualified Data.ByteString.Char8 as BC  main = do   args <- getArgs-  ml1 <- parseLDIFFile (args !! 0)+  let name = args !! 0+  ml1 <- parseLDIFFile defaulLDIFConf name   case ml1 of -       Right l1 -> do-             print l1-             putStrLn "========================================================"-             putStrLn $ ldif2str l1+       Right l1 -> BC.putStrLn $ ldif2str l1        Left err -> print err
+ src/ldifundo.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}+import Data.List+import Data.Either+import Data.Maybe+import Control.Monad+import System.FilePath+import System.Environment+import Text.LDIF+import System.Console.CmdArgs+import qualified Data.ByteString.Char8 as BC++progDesc = "Calculate undo LDIF (rollback LDIF)"++data LdifUndo = LdifUndo { inFile   :: FilePath+                         , outFile  :: FilePath } deriving (Show, Data, Typeable)++defaultCfg = LdifUndo { inFile = def &= typFile &= name "f" &= help "Input LDIF File"+                      , outFile = def &= typFile &= name "o" &= help "Output LDIF File" }++main = do+  cfg <- cmdArgs defaultCfg+  execute cfg++execute (LdifUndo [] _) = putStrLn "Error: -f base LDIF File is mandatory"+execute cfg = do+  inLDIF <- safeParseLDIFFile (inFile cfg)+  let (outLDIF, wrn) = undoLDIF inLDIF+  when (not $ null wrn) (putStrLn $ "Finished with warnings: " ++ (unlines $ map unlines wrn))+  if length (outFile cfg) == 0 then do+      BC.putStrLn (ldif2str outLDIF)+    else do+      BC.writeFile (outFile cfg) (ldif2str outLDIF)+      putStrLn $ (outFile cfg) ++ " written."++safeParseLDIFFile :: FilePath -> IO LDIF+safeParseLDIFFile name = liftM (either (\e -> error $ "Can not parse: "++(show e)) (id)) (parseLDIFFile defaulLDIFConf name)
tests/TestMain.hs view
@@ -5,64 +5,89 @@ import System.Directory import System.FilePath import Control.Monad (liftM)+import qualified Data.ByteString.Char8 as BC -ldifDir = "data"+-- | Directory with input LDIF files for tests+ldifDir = "tests/data"  main = do     ls <- getLDIFs ldifDir     runTestTT (tests ls)--tests ls = TestList $ (testCasesParseOK ls) ++ testCasesDIFF ++ testCasesUtils ++ (testCasesPrintOK ls)+  where+    tests ls = TestList $ (testCasesParseOK ls) +               ++ testCasesDiff+               ++ testCasesApply+               ++ testCasesTree+               ++ testCasesUtils +               ++ (testCasesPrintOK ls)  -- -- Test Cases ---testCasesDIFF = [TestCase (assertEqual "dummy" True True)]-testCasesParseOK ls = map (\x -> TestCase (checkParsing x)) $ filter (isOK) ls+testCasesDiff  = [ TestCase (assertEqual "dummy" True True) ] -- TODO: Implement!+testCasesApply = [ TestCase (assertEqual "dummy" True True) ] -- TODO: Implement!+testCasesTree  = [ TestCase (assertEqual "dummy" True True) ] -- TODO: Implement!++-- | Parser Tests+testCasesParseOK ls = map (TestCase . checkParsing) $ filter isSuccessFile ls     where       checkParsing fname = do-        ret <- parseLDIFFile fname+        ret <- parseLDIFFile defaulLDIFConf fname         assertParsedOK fname ret "Parsing test" -testCasesPrintOK ls = map (\x -> TestCase (checkParsing x)) $ filter (isOK) ls+-- | Printer Tests+testCasesPrintOK ls = map (TestCase . checkParsing) $ filter isSuccessFile ls     where       checkParsing fname = do-        ret <- parseLDIFFile fname-        case ret of -          Left _     -> return ()-          Right ldif -> do-              let ret2 = parseLDIFStr (ldif2str ldif)+        ret <- parseLDIFFile defaulLDIFConf fname+        either (\_ -> return ()) printAndParseAgain ret+          where+            printAndParseAgain ldif = do+              let ret2 = parseLDIFStr defaulLDIFConf fname (ldif2str ldif)               assertParsedOK fname ret2 "Printing test" -testCasesUtils = [ TestCase (assertBool "DN1Root is Prefix of DN2Root" (not $ isDNPrefixOf dn1root dn2root))-                 , TestCase (assertBool "DN1Root is Prefix of DN1Child" (isDNPrefixOf dn1root dn1child))-                 , TestCase (assertBool "DN Size 1" (1 == sizeOfDN dn1root))-                 , TestCase (assertBool "DN Size 2" (2 == sizeOfDN dn1child))]+-- | Utils Tests+testCasesUtils = [ TestCase $ assertBool "DN1Root is Prefix of DN2Root"  $ not $ dn1root `isDNPrefixOf` dn2root+                 , TestCase $ assertBool "DN1Root is Prefix of DN1Child" $ dn1root `isDNPrefixOf` dn1child+                 , TestCase $ assertBool "DN Size 1" $ 1 == lengthOfDN dn1root+                 , TestCase $ assertBool "DN Size 2" $ 2 == lengthOfDN dn1child +                 ]     where-      dn1root = head $ rights [ parseDNStr "dc=sk" ]-      dn2root = head $ rights [ parseDNStr "dc=de" ]-      dn1child = head $ rights [ parseDNStr "o=green,dc=sk" ]------ Support Methods---+      dn1root  = parseRightDN "dc=sk"+      dn2root  = parseRightDN "dc=de"+      dn1child = parseRightDN "o=green,dc=sk"+      parseRightDN dstr = fromRight $ parseDNStr defaulLDIFConf $ BC.pack dstr+        where+          fromRight (Left e)  = error $ show e+          fromRight (Right d) = d++-- | Support Methods getLDIFs :: String -> IO [String] getLDIFs dr = do     liftM (map (dr </>)) $ liftM (filter isLDIF) $ getDirectoryContents dr-  -isOK x = isPrefixOf "OK" (takeFileName x)-isLDIF x = isSuffixOf ".ldif" x--assertParsedOK filename ret msg  = case ret of-  Left e -> assertFailure $ msg ++ " " ++ (show e)-  Right ldif -> assertParsedType filename ldif+      where+        isLDIF x = isSuffixOf ".ldif" x  +        +-- | Assert that file is parsed successfully and correct LDIF type+assertParsedOK name ret msg  = either onError onSuccess ret+  where+    onSuccess l = assertParsedType name l+    onError   e = assertFailure $ msg ++ " " ++ show e  -assertParsedType name ldif | (isSuffixOf ".modify.ldif" name) = assertTypeChanges name ldif-                           | (isSuffixOf ".content.ldif" name) = assertTypeContent name ldif-                           | otherwise = assertFailure $ "Unexpected filename: (not .modify.ldif or .content.ldif " ++ name+-- | Assert that file is parsed with expected LDIF type+assertParsedType name l = assertType $ contentTypeFile name+  where+    assertType t | getLDIFType l == t = assertBool "Valid Content Type" True+                 | otherwise          = let msg = name ++ " is not type of " ++ (show t) ++ " but " ++ (show $ getLDIFType l)+                                        in assertFailure msg -assertTypeContent n l  | (getLDIFType l == LDIFContentType) = assertBool "Valid Content Type" True -- >> (putStrLn $ "\n\n" ++ n ++ "\n\n" ++ (show l))-                       | otherwise          = assertFailure $ n ++ " is not type of LDIFContent"+-- | Files which names begin with "OK" are expected to be parsed Successfully+isSuccessFile x = isPrefixOf "OK" (takeFileName x) -assertTypeChanges n l | (getLDIFType l /= LDIFContentType) = assertBool "Valid Changes Type" True -- >> (putStrLn $ "\n\n" ++ n ++ "\n\n" ++ (show l))-                      | otherwise          = assertFailure $ n ++ " is not type of LDIFChanges"-  +-- | Files suffixes defines what is expected parsed content type+contentTypeFile x | isModifyFile  x = LDIFChangesType+                  | isContentFile x = LDIFContentType+                  | otherwise       = error $ "Invalid filename format in tests: " ++ x+  where+    isModifyFile x  = isSuffixOf ".modify.ldif"  x+    isContentFile x = isSuffixOf ".content.ldif" x