{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} module Database.Design.Ampersand.Output.ToPandoc.ChapterNatLangReqs where import Data.Char hiding (Space) import Data.List import Data.List.Split import GHC.Exts (sortWith) import Data.Maybe import Database.Design.Ampersand.Basics import Database.Design.Ampersand.Output.ToPandoc.SharedAmongChapters hiding (sortWith) import Database.Design.Ampersand.ADL1 import Database.Design.Ampersand.Classes import Database.Design.Ampersand.Output.PandocAux import Text.Pandoc.Builder fatal :: Int -> String -> a fatal = fatalMsg "Output.ToPandoc.ChapterNatLangReqs" {- TODO: This module needs to be rewritten from scratch. Instead of deciding on the fly what should be included, a datastructure needs to be added to the fSpec, which contains per theme the concepts, rules and relations that need to be printed. -} chpNatLangReqs :: Int -> FSpec -> Blocks chpNatLangReqs lev fSpec = -- *** Header *** chptHeader (fsLang fSpec) SharedLang <> -- *** Intro *** case fsLang fSpec of Dutch -> para ( "Dit hoofdstuk beschrijft een natuurlijke taal, waarin functionele eisen ten behoeve van " <> (singleQuoted.str.name) fSpec <> " kunnen worden besproken en uitgedrukt. " <> "Hiermee wordt beoogd dat verschillende belanghebbenden hun afspraken op dezelfde manier begrijpen. " <> "De taal van " <> (singleQuoted. str. name) fSpec <> " bestaat uit begrippen en basiszinnen, " <> "waarin afspraken worden uitgedrukt. " <> "Wanneer alle belanghebbenden afspreken dat zij deze basiszinnen gebruiken, " <> "althans voor zover het " <> (singleQuoted. str. name) fSpec <> " betreft, " <> "delen zij precies voldoende taal om afspraken op dezelfde manier te begrijpen. " <> "Alle definities zijn genummerd omwille van de traceerbaarheid. " ) English -> para ( "This chapter defines the natural language, in which functional requirements of " <> (singleQuoted.str.name) fSpec <> " can be discussed and expressed. " <> "The purpose of this chapter is to create shared understanding among stakeholders. " <> "The language of " <> (singleQuoted.str.name) fSpec <> " consists of concepts and basic sentences. " <> "All functional requirements are expressed in these terms. " <> "When stakeholders can agree upon this language, " <> "at least within the scope of " <> (singleQuoted.str.name) fSpec <> ", they share precisely enough language to have meaningful discussions about functional requirements. " <> "All definitions have been numbered for the sake of traceability. " ) <> -- *** Requirements *** fromList dpRequirementsOld -- dpRequirementesNew <> -- *** Legal Refs *** if genLegalRefs (getOpts fSpec) then legalRefs else mempty where legalRefs :: Blocks legalRefs = (header (lev+2) sectionTitle) <> table caption' [(AlignLeft,1/4),(AlignLeft,3/4)] [plain lawHeader, plain articleHeader] --headers [ [(para.str.aOlLaw) art , (para.str.unscanRef.aOlArt) art] | art <-(sort.nub.concatMap getArticlesOfLaw.getRefs) fSpec ] where (sectionTitle, lawHeader, articleHeader, caption') = case fsLang fSpec of Dutch -> ("Referentietabel", "Wet", "Artikel", "Referentietabel van de wetsartikelen") English -> ("Reference table", "Law", "Article", "Reference table of articles of law") getRefs ::FSpec -> [LawRef] getRefs f = concatMap catMaybes ((map (map toLawRef).map explRefIds.explanations) f) --TODO: Deze nieuwe functie moet de oude dpRequirementsOld vervangen, waardoor een effectieve scheiding --wordt gerealiseerd van WAT er wordt afgedrukt en de VOLGORDE van afdrukken. (code disabled until it will be used, to prevent warning) {- dpRequirementesNew :: Blocks dpRequirementesNew = let (_,blocks) = foldl printTheme (newCounter,mempty) (orderingByTheme fSpec) in blocks where printTheme :: (Counter,Blocks) -> ( Maybe Theme -- A theme is about either a pattern or a process. , [Rule] -- The rules of that theme , [Declaration] -- The relations that are used in a rule of this theme, but not in any rule of a previous theme. , [A_Concept] -- The concepts that are used in a rule of this theme, but not in any rule of a previous theme. ) -> (Counter,Blocks) printTheme (counter,bs) themeStuff = let (blocks,count') = printOneTheme themeStuff counter in (count',bs <> blocks) -} dpRequirementsOld :: [Block] dpRequirementsOld = theBlocks where (theBlocks,_) = printThemes toBeProcessedStuff newCounter [pat | pat<-vpatterns fSpec, null (themes fSpec) || name pat `elem` themes fSpec ] toBeProcessedStuff = ( conceptsWith , allRelsThatMustBeShown , [r | r<-vrules fSpec, r_usr r == UserDefined] ) -- All user declared rules where conceptsWith -- All concepts that have at least one non-empty definition (must be the first) = [ c | c@PlainConcept{} <- concs fSpec , (not.null) (concDefs fSpec c) ] allRelsThatMustBeShown -- All relations declared in this specification that have at least one user-defined purpose. = [ d | d <- vrels fSpec , decusr d , not . null $ purposesDefinedIn fSpec (fsLang fSpec) d ] printThemes :: ( [A_Concept] -- all concepts that have one or more definitions or purposes. These are to be used into this section and the sections to come , [Declaration] -- all relations to be processed into this section and the sections to come , [Rule]) -- all rules to be processed into this section and the sections to come -> Counter -- unique definition counters -> [Pattern] -- the patterns that must be processed into this specification -> ([Block],Counter) -- The blocks that define the resulting document and the last used unique definition number printThemes (still2doCPre, still2doRelsPre, still2doRulesPre) iPre allThemes = case allThemes of [] -> if null still2doCPre && null still2doRelsPre && null still2doRelsPre then ([],iPre) else let (a,b) = printOneTheme (Nothing, still2doRulesPre, still2doRelsPre, still2doCPre) iPre in (toList a,b) _ -> (toList blocksOfOneTheme ++ blocksOfThemes,iPost) where (thm:thms) = allThemes (blocksOfOneTheme,iPostFirst) = printOneTheme thisThemeStuff iPre (blocksOfThemes,iPost) = printThemes stuff2PrintLater iPostFirst thms thisThemeStuff = (Just thm, [r | r<-thisThemeRules, r_usr r == UserDefined], thisThemeRels, thisThemeCs) thisThemeRules = [r | r<-still2doRulesPre, r_env r == name thm ] -- only user defined rules, because generated rules are documented in whatever caused the generation of that rule. rules2PrintLater = still2doRulesPre >- thisThemeRules thisThemeRels = [ d | d <- still2doRelsPre , decpat d == name thm || -- all relations declared in this theme, combined d `eleM` relsMentionedIn thisThemeRules] -- all relations used in this theme's rules rels2PrintLater = [x | x <-still2doRelsPre, (not.or) [ x==y | y <- thisThemeRels ]] thisThemeCs = [c | c <- still2doCPre, c `eleM` (concs thisThemeRules ++ concs thisThemeRels)] -- relations are rules ('Eis') too concs2PrintLater = still2doCPre >- thisThemeCs stuff2PrintLater = (concs2PrintLater, rels2PrintLater, rules2PrintLater) -- | printOneTheme tells the story in natural language of a single theme. -- For this purpose, Ampersand authors should take care in composing explanations. -- Each explanation should state the purpose (and nothing else). printOneTheme :: ( Maybe Pattern -- The theme to process (if any) , [Rule] -- Rules to print in this section , [Declaration] -- Relations to print in this section , [A_Concept] -- Concepts, to print in this section ) -> Counter -- first free number to use for numbered items -> (Blocks,Counter)-- the resulting blocks and first free number after processing of the theme. printOneTheme (mTheme, rules2print, rels2print, concs2print) counter0 = case (mTheme, themes fSpec) of (Nothing, _:_) -> ( mempty, counter0 ) -- The document is partial (because themes have been defined), so we don't print loose ends. _ -> ( ( -- *** Header of the theme: *** headerWithLabel (XRefNaturalLanguageTheme mTheme) (lev+2) (case (mTheme,fsLang fSpec) of (Nothing, Dutch ) -> "Losse eindjes..." (Nothing, English) -> "Loose ends..." _ -> text themeName ) <> -- *** Purpose of the theme: *** case mTheme of Nothing -> case fsLang fSpec of Dutch -> para $ "Deze paragraaf beschrijft de relaties en concepten die " <> "niet in voorgaande secties zijn beschreven." English -> para $ "This paragraph shows remaining fact types and concepts " <> "that have not been described in previous paragraphs." Just pat -> purposes2Blocks (getOpts fSpec) (purposesDefinedIn fSpec (fsLang fSpec) pat) <> -- *** Introduction text of the theme: *** printIntro (filter isDefined concs2print) <> fromList reqdefs ) , Counter (getEisnr counter0 + length reqs) ) where isDefined :: A_Concept -> Bool isDefined = not.null.concDefs fSpec -- the concepts for which one of the relations of this theme contains a source or target definition -- (these will be printed, regardless whether the concept was printed before) -- sort the requirements by file position reqs = sortWith fst [ ((i,filenm org, linenr org,colnr org), bs) | (i,org,bs)<- addIndex 0 (printConcepts concs2print) ++ addIndex 2 (printRels rels2print) ++ addIndex 3 (printRules rules2print)] where addIndex i ps = [ (i::Int,fs, sn) | (fs,sn) <- ps ] -- add an index to sort first on category (concept, rel, ..) -- make blocks for requirements reqblocks = [(pos,req (Counter cnt)) | (cnt,(pos,req))<-zip [(getEisnr counter0)..] reqs] reqdefs = concatMap snd reqblocks themeName = case mTheme of Nothing -> "" Just pat -> name pat -- The following paragraph produces an introduction of one theme (i.e. pattern or process). printIntro :: [A_Concept] -> Blocks printIntro [] = mempty printIntro ccds = case fsLang fSpec of Dutch -> (case ([(emph.str.unCap) cname | cname<-map name ccds] , length [p |p <- (vpatterns fSpec) , name p == themeName] ) of ([] ,_) -> mempty ([_],1) -> case mTheme of Nothing -> mempty Just tme -> para ( "In het volgende wordt de taal geïntroduceerd ten behoeve van " <> (str.name) tme <> "." ) (cs ,1) -> para ( "Nu volgen definities van de concepten " <> commaNLPandoc' ("en") cs <> ".") ([c],_) -> para ( "Deze sectie introduceert het concept " <> c ) (cs ,_) -> para ( "Deze sectie introduceert de concepten " <> commaNLPandoc' "en" cs <> ". ") ) <> (let cs = [(c,cds) | (c)<-ccds, let cds = concDefs fSpec c,length cds>1] in case (cs, length cs==length ccds) of ([] , _ ) -> mempty ([(c,_)] , False) -> para ("Eén daarvan, " <> (str.name) c <>", heeft meerdere definities. ") (_ , False) -> para ("Daarvan hebben "<>commaNLPandoc' "en" (map (str. name . fst) cs)<> " meerdere definities. ") ([(_,cds)], True ) -> para ("Deze heeft "<>str(count Dutch (length cds) "definitie")<>". ") (_ , True ) -> para ("Elk daarvan heeft meerdere definities. ") ) English -> (case ([(emph.str.unCap) cname | cname<-map name ccds] , length [p |p <- vpatterns fSpec, name p == themeName] ) of ([] ,_) -> mempty ([_],1) -> case mTheme of Nothing -> mempty Just tme -> para ( "The sequel introduces the language of " <> (str.name) tme <> "." ) (cs ,1) -> para ( "At this point, the definitions of " <> commaEngPandoc' "and" cs <>" are given.") ([c],_) -> para ("This section introduces concept " <> c ) (cs ,_) -> para ("This section introduces concepts " <> commaEngPandoc' "and" cs <> ". ") )<> (let cs = [(c,cds) | c <- ccds, let cds = concDefs fSpec c, length cds>1] in case (cs, length cs==length ccds) of ([] , _ ) -> mempty ([(c,_)] , False) -> para ("One of these concepts, " <> (str.name) c <>", has multiple definitions. ") (_ , False) -> para ("Of those concepts "<>commaEngPandoc' "and" (map (str. name . fst) cs)<> " have multiple definitions. ") ([(_,cds)], True ) -> para ("It has "<>str(count English (length cds) "definition")<>". ") (_ , True ) -> para ("Each one has several definitions. ") ) -- | the origin of c is the origin of the head of uniquecds c -- after sorting by origin the counter will be applied printConcepts :: [A_Concept] -> [(Origin, Counter -> [Block])] printConcepts cs = map printConcept cs printConcept :: A_Concept -> (Origin, Counter -> [Block]) printConcept c = (case uniquecds fSpec c of [] -> OriginUnknown (_,x):_ -> origin x , cptBlock c ) -- | make a block for a concept with all its purposes and definitions cptBlock :: A_Concept -> Counter -> [Block] cptBlock cpt cnt = concat [amPandoc (explMarkup e) | e<-exps] ++ zipWith (cdBlock cpt) (if length (uniquecds fSpec cpt) == 1 then [(cnt, "")] else [(cnt, '.' : show i) | i <- [(1 :: Int) ..]]) (uniquecds fSpec cpt) where exps = case purposeOf fSpec (fsLang fSpec) cpt of Nothing -> [] Just ps -> ps -- | make a block for a concept definition cdBlock :: A_Concept -> (Counter,String) -> (String,ConceptDef) -> Block cdBlock cpt (cnt,xcnt) tup = DefinitionList [( toList (definitionListItemLabel (XRefNaturalLanguageConcept cpt) ((case fsLang fSpec of Dutch -> "Definitie " English -> "Definition " ) ++ show (getEisnr cnt)++xcnt++":" ) ) , [toList $ makeDefinition (getOpts fSpec) tup] ) ] makeDefinition :: Options -> (String,ConceptDef) -> Blocks makeDefinition opts (nm,cd) = para ( rawInline "latex" ("\\newglossaryentry{"++escapeNonAlphaNum nm ++"}\n"++ " { name={"++latexEscShw nm ++"}\n"++ " , description={"++latexEscShw (cddef cd)++"}}\n") <> case fspecFormat opts of FLatex -> rawInline "latex" (insertAfterFirstWord refStr defStr) <> if null ref then mempty else rawInline "latex" (latexEscShw (" ["++ref++"]")) _ -> str (cddef cd) <> if null ref then mempty else str (" ["++ref++"]") ) where refStr = "\\marge{\\gls{"++escapeNonAlphaNum nm++"}}" defStr = latexEscShw (cddef cd) ref = cdref cd -- by putting the ref after the first word of the definition, it aligns nicely with the definition insertAfterFirstWord s wordsStr = let (fstWord, rest) = break (==' ') wordsStr in fstWord ++ s ++ rest -- | sctds prints the requirements related to relations that are introduced in this theme. printRels :: [Declaration] -> [(Origin, Counter -> [Block])] printRels = map (\dcl -> (origin dcl, printRel dcl)) printRel :: Declaration -> Counter -> [Block] printRel dcl cnt = Plain [RawInline (Text.Pandoc.Builder.Format "latex") "\\bigskip"] : toList (purposes2Blocks (getOpts fSpec) purps) ++ [ DefinitionList [( (toList (definitionListItemLabel (XRefNaturalLanguageDeclaration dcl) ((case fsLang fSpec of Dutch -> "Afspraak " English -> "Agreement " )++show(getEisnr cnt) ++if development (getOpts fSpec) && name dcl/="" then (" ("++name dcl++")") else "" ) ) ) , [ meaning2Blocks (fsLang fSpec) dcl] ) ] ]++ ( case (fsLang fSpec, length samplePop) of (_ , 0) -> [] (Dutch , 1) -> [Para [Str "Een frase die hiermee gemaakt kan worden is bijvoorbeeld:"]] (English, 1) -> [Para [Str "A phrase that can be formed is for instance:"]] (Dutch , _) -> [Para [Str "Frasen die hiermee gemaakt kunnen worden zijn bijvoorbeeld:"]] (English, _) -> [Para [Str "Phrases that can be made are for instance:"]] ) ++ sampleSentences where purps = purposesDefinedIn fSpec (fsLang fSpec) dcl samplePop = (take 3 . fullContents (vgens fSpec) (initialPops fSpec) . EDcD) dcl sampleSentences = [ Para $ mkSentence (development (getOpts fSpec)) dcl srcViewAtom tgtViewAtom | p <-samplePop , let srcViewAtom = showViewAtom fSpec (Just dcl) (source dcl) (srcPaire p) , let tgtViewAtom = showViewAtom fSpec Nothing (target dcl) (trgPaire p) ] ++ (if null samplePop then [] else [Plain [RawInline (Text.Pandoc.Builder.Format "latex") "\\medskip"]]) printRules :: [Rule] -> [(Origin,Counter -> [Block])] printRules = map (\rul -> (origin rul, printRule rul)) printRule :: Rule -> Counter -> [Block] printRule rul cnt = Plain [RawInline (Text.Pandoc.Builder.Format "latex") "\\bigskip"] : toList (purposes2Blocks (getOpts fSpec) purps) ++ [ DefinitionList [(toList (definitionListItemLabel (XRefNaturalLanguageRule rul) ((case fsLang fSpec of Dutch -> "Afspraak " English -> "Agreement " )++show(getEisnr cnt) ++if development (getOpts fSpec) && name rul/="" then (" ("++name rul++")") else "" ) ) , [ meaning2Blocks (fsLang fSpec) rul] ) ] | not (null$meaning2Blocks (fsLang fSpec) rul)] where purps = purposesDefinedIn fSpec (fsLang fSpec) rul mkSentence :: Bool -> Declaration -> String -> String -> [Inline] mkSentence isDev decl srcAtom tgtAtom = case decl of Sgn{} | null (prL++prM++prR) -> [str' (upCap srcAtom), Space] ++ devShow (source decl) ++ [Str "corresponds",Space,Str "to",Space,str' tgtAtom, Space] ++ devShow (target decl) ++[Str "in",Space,Str "relation",Space,str' (name decl),Str "."] | otherwise -> leftHalf prL ++ rightHalf where prL = decprL decl prM = decprM decl prR = decprR decl leftHalf "" = devShow (source decl) leftHalf prLft = [str' (upCap prLft), Space] ++ devShow (source decl) rightHalf = [str' srcAtom,Space,str' prM, Space] ++ devShow (target decl) ++ [str' tgtAtom]++(if null prR then [] else [Space,str' prR]) ++ [str' "."] Isn{} -> devShow (source decl) ++ [str' (upCap srcAtom),Space,Str "equals",Space,str' tgtAtom,Str "."] Vs{} -> [Str "True"] where str' = if fspecFormat (getOpts fSpec)==FLatex then RawInline (Text.Pandoc.Builder.Format "latex") . latexEscShw else Str devShow c | isDev = [Str "(", str' $ name c, Str ") "] -- only show the concept when --dev option is given | otherwise = [] -- TODO: fix showing/not showing based on relation -- TODO: what about relations in the target view? -- TODO: move these to some auxiliaries or utils showViewAtom :: FSpec -> Maybe Declaration -> A_Concept -> String -> String showViewAtom fSpec mDec cncpt atom = case mapMaybe (getView fSpec) (cncpt : largerConcepts (vgens fSpec) cncpt) of [] -> atom view:_ -> case mDec of Nothing -> concatMap showViewSegment (vdats view) Just md -> if (not.null) [() | ViewExp objDef <- vdats view, EDcD d<-[objctx objDef], d==md] then atom else concatMap showViewSegment (vdats view) -- if we are showing one of the view relations, don't expand the view where showViewSegment (ViewText str') = str' showViewSegment (ViewHtml str') = str' showViewSegment (ViewExp objDef) = case [ trgPaire p | p <- fullContents (vgens fSpec) (initialPops fSpec) (objctx objDef), atom == srcPaire p ] of [] -> "" viewAtom:_ -> viewAtom -- justViewRels = map (Just . objctx) [objDef | ViewExp objDef <- vdats view] {- getIdentity :: FSpec -> A_Concept -> Maybe IdentityDef getIdentity fSpec cncpt = case filter ((== cncpt) . idCpt) (vIndices fSpec) of [] -> Nothing identity:_ -> Just identity -} getView :: FSpec -> A_Concept -> Maybe ViewDef getView fSpec cncpt = case filter ((== cncpt) . vdcpt) (vviews fSpec) of [] -> Nothing viewDef:_ -> Just viewDef data LawRef = LawRef { lawRef :: String} data ArticleOfLaw = ArticleOfLaw { aOlLaw :: String , aOlArt :: [Either String Int] } deriving Eq toLawRef:: String -> Maybe LawRef toLawRef s = case s of [] -> Nothing _ -> (Just . LawRef) s getLaw :: ArticleOfLaw -> Inlines getLaw x = (str.aOlLaw) x -- the article is everything but the law (and we also drop any trailing commas) getArticlesOfLaw :: LawRef -> [ArticleOfLaw] getArticlesOfLaw ref = map buildLA ((splitOn ", ".unwords.init.words.lawRef) ref) where buildLA art = ArticleOfLaw ((last.words.lawRef) ref) (scanRef art) where -- group string in number and text sequences, so "Art 12" appears after "Art 2" when sorting (unlike in normal lexicographic string sort) scanRef :: String -> [Either String Int] scanRef "" = [] scanRef str'@(c:_) | isDigit c = scanRefInt str' | otherwise = scanRefTxt str' scanRefTxt "" = [] scanRefTxt str' = let (txt, rest) = break isDigit str' in Left txt : scanRefInt rest scanRefInt "" = [] scanRefInt str' = let (digits, rest) = break (not . isDigit) str' in Right (read digits) : scanRefTxt rest instance Ord ArticleOfLaw where compare a b = case compare (aOlLaw a) (aOlLaw b) of EQ -> compare (aOlArt a) (aOlArt b) ord' -> ord' unscanRef :: [Either String Int] -> String unscanRef scannedRef = concat $ map (either id show) scannedRef