1 {-# LANGUAGE DeriveGeneric #-}
2 {-# LANGUAGE DisambiguateRecordFields #-}
3 {-# LANGUAGE DuplicateRecordFields #-}
4 {-# LANGUAGE FlexibleInstances #-}
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE OverloadedStrings #-}
7 module Hdoc.DTC.Document
8 ( module Hdoc.DTC.Document
9 , Ident(..), URL(..), Nat(..), Nat1(..)
14 import Control.Applicative (Applicative(..))
16 import Data.Default.Class (Default(..))
17 import Data.Default.Instances.Containers ()
18 import Data.Eq (Eq(..))
19 import Control.Monad (Monad(..))
20 import Data.Foldable (Foldable(..))
21 import Data.Function (on, ($), (.))
22 import Data.Hashable (Hashable(..))
24 import Data.Maybe (Maybe(..))
25 import Data.Monoid (Monoid(..))
26 import Data.Ord (Ord(..), Ordering(..))
27 import Data.Semigroup (Semigroup(..))
28 -- import Data.Sequence (Seq(..))
29 import Data.String (IsString)
30 import GHC.Generics (Generic)
31 import System.FilePath (FilePath)
32 import Text.Show (Show)
33 import qualified Data.Char as Char
34 import qualified Data.HashMap.Strict as HM
35 import qualified Data.List as List
36 import qualified Data.Text.Lazy as TL
37 import qualified Data.TreeSeq.Strict as TS
38 import qualified Data.TreeMap.Strict as TM
39 import qualified Majority.Judgment as MJ
41 import Hdoc.Utils (Nat(..), Nat1(..), succNat, succNat1)
42 import Hdoc.XML (Ident(..), URL(..))
43 import qualified Hdoc.XML as XML
44 import qualified Hdoc.TCT.Cell as TCT
47 data Document = Document
51 instance Default Document where
59 { head_about :: !About
60 , head_judgments :: ![Judgment]
61 -- [(Judgment, [Tree.Tree (Maybe MJ.Share, [Choice])])]
63 instance Default Head where
66 , head_judgments = def
68 instance Ord Head where
73 { about_titles :: ![Title]
74 , about_url :: !(Maybe URL)
75 , about_authors :: ![Entity]
76 , about_editor :: !(Maybe Entity)
77 , about_date :: !(Maybe Date)
78 , about_tags :: ![TL.Text]
79 , about_links :: ![Link]
80 , about_series :: ![Serie]
81 , about_description :: !(Maybe Para)
82 , about_headers :: ![Header]
84 instance Default About where
94 , about_description = def
100 { header_name :: !TL.Text
101 , header_value :: !Plain
105 type Body = TS.Trees BodyNode
107 -- ** Type 'BodyNode'
109 = BodySection !Section -- ^ node
110 | BodyBlock !Block -- ^ leaf
114 data Section = Section
115 { section_posXML :: !XML.Pos
116 , section_attrs :: !CommonAttrs
117 , section_title :: !Title
118 , section_aliases :: ![Alias]
119 , section_judgments :: ![Judgment]
121 instance Ord Section where
122 compare = compare `on` section_posXML
127 | BlockBreak { attrs :: !CommonAttrs }
128 | BlockToC { posXML :: !XML.Pos
129 , attrs :: !CommonAttrs
130 , depth :: !(Maybe Nat)
132 | BlockToF { posXML :: !XML.Pos
133 , attrs :: !CommonAttrs
134 , types :: ![TL.Text]
136 | BlockAside { posXML :: !XML.Pos
137 , attrs :: !CommonAttrs
140 | BlockFigure { posXML :: !XML.Pos
142 , attrs :: !CommonAttrs
143 , mayTitle :: !(Maybe Title)
146 | BlockIndex { posXML :: !XML.Pos
147 , attrs :: !CommonAttrs
150 | BlockReferences { posXML :: !XML.Pos
151 , attrs :: !CommonAttrs
152 , refs :: ![Reference]
153 } -- FIXME: move to ParaReferences?
154 | BlockJudges !Judges
155 | BlockGrades { posXML :: !XML.Pos
156 , attrs :: !CommonAttrs
162 data Judgment = Judgment
163 { judgment_opinionsByChoice :: !(Maybe (MJ.OpinionsByChoice Choice Name (MJ.Ranked Grade)))
164 , judgment_judges :: !(Maybe Judges)
165 , judgment_grades :: !(Maybe (MJ.Grades (MJ.Ranked Grade)))
166 , judgment_posXML :: !XML.Pos
167 , judgment_locTCT :: !TCT.Location
168 , judgment_judgesId :: !Ident
169 , judgment_gradesId :: !Ident
170 , judgment_importance :: !(Maybe MJ.Share)
171 , judgment_question :: !(Maybe Title)
172 , judgment_choices :: ![Choice]
174 instance Eq Judgment where
176 judgment_judgesId x == judgment_judgesId y &&
177 judgment_gradesId x == judgment_gradesId y &&
178 judgment_question x == judgment_question y
179 instance Hashable Judgment where
180 hashWithSalt s Judgment{..} =
181 s`hashWithSalt`judgment_judgesId
182 `hashWithSalt`judgment_gradesId
183 `hashWithSalt`judgment_question
185 -- ** Type 'ErrorJudgment'
187 = ErrorJudgment_Judges
188 | ErrorJudgment_Grades
193 { judges_locTCT :: !TCT.Location
194 , judges_posXML :: !XML.Pos
195 , judges_attrs :: !CommonAttrs
196 , judges_byName :: !(HM.HashMap Name [Judge])
201 { judge_locTCT :: !TCT.Location
202 , judge_posXML :: !XML.Pos
203 , judge_name :: !Name
204 , judge_title :: !(Maybe Title)
205 , judge_defaultGrades :: !(HM.HashMap Ident [Name])
210 { grade_posXML :: !XML.Pos
211 , grade_name :: !Name
212 , grade_color :: !TL.Text
213 , grade_isDefault :: !Bool
214 , grade_title :: !(Maybe Title)
219 { choice_locTCT :: TCT.Location
220 , choice_posXML :: XML.Pos
221 , choice_title :: !(Maybe Title)
222 , choice_opinions :: ![Opinion]
224 instance Eq Choice where
225 (==) = (==)`on`choice_title
226 instance Hashable Choice where
227 hashWithSalt s Choice{..} =
228 hashWithSalt s choice_title
231 data Opinion = Opinion
232 { opinion_locTCT :: !TCT.Location
233 , opinion_posXML :: !XML.Pos
234 , opinion_judge :: !Name
235 , opinion_grade :: !Name
236 , opinion_importance :: !(Maybe MJ.Share)
237 , opinion_comment :: !(Maybe Title)
242 = ParaItem { item :: !ParaItem }
243 | ParaItems { posXML :: !XML.Pos
244 , attrs :: !CommonAttrs
245 , items :: ![ParaItem]
249 -- ** Type 'ParaItem'
252 | ParaComment !TL.Text
255 | ParaQuote { type_ :: !TL.Text
258 | ParaArtwork { type_ :: !TL.Text
261 | ParaJudgment !Judgment
264 -- *** Type 'ListItem'
265 data ListItem = ListItem
271 type Plain = TS.Trees PlainNode
273 -- ** Type 'PlainNode'
277 | PlainCode -- ^ Code (monospaced)
278 | PlainDel -- ^ Deleted (crossed-over)
280 | PlainGroup -- ^ Group subTrees (neutral)
282 | PlainSC -- ^ Small Caps
283 | PlainSub -- ^ Subscript
284 | PlainSup -- ^ Superscript
285 | PlainU -- ^ Underlined
286 | PlainEref { eref_href :: !URL } -- ^ External reference
287 | PlainIref { iref_term :: !Words
288 } -- ^ Index reference
289 | PlainRref { rref_locTCT :: !TCT.Location
290 , rref_posXML :: !XML.Pos
292 } -- ^ Reference reference
293 | PlainSpan { attrs :: !CommonAttrs } -- ^ Neutral node
295 | PlainBreak -- ^ Line break (\n)
297 | PlainNote { note_paras :: ![Para]
299 | PlainTag { tag_locTCT :: !TCT.Location
300 , tag_posXML :: !XML.Pos
301 , tag_ident :: !Ident
307 newtype Tag = Tag TL.Text
308 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
312 -- * Type 'ErrorTarget'
314 = ErrorTarget_Unknown !Nat1
315 | ErrorTarget_Ambiguous !(Maybe Nat1)
318 -- * Type 'ErrorAnchor'
320 = ErrorAnchor_Ambiguous !Nat1
324 -- * Type 'CommonAttrs'
325 data CommonAttrs = CommonAttrs
326 { attrs_id :: !(Maybe Ident)
327 , attrs_classes :: ![TL.Text]
328 } deriving (Eq,Ord,Show)
329 instance Default CommonAttrs where
332 , attrs_classes = def
337 { anchor_section :: !XML.Pos
338 , anchor_count :: !Nat1
339 } deriving (Eq,Ord,Show)
342 newtype Name = Name { unName :: TL.Text }
343 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
346 newtype Title = Title { unTitle :: Plain }
347 deriving (Show,Semigroup,Monoid,Default)
348 instance Eq Title where
349 (==) = (==) `on` similarPlain . unTitle
350 -- | Return a similar version of a 'Plain' by removing:
352 -- * parsing residues ('PlainGroup'),
353 -- * notes ('PlainNote'),
354 -- * and position specific annotations ('Ident' and 'Anchor').
355 similarPlain :: Plain -> Plain
356 similarPlain = foldMap $ \(TS.Tree n ts) ->
357 let skip = similarPlain ts in
358 let keep = pure $ TS.Tree n $ skip in
362 PlainIref{..} -> keep
363 PlainRref{..} -> pure $ TS.Tree PlainRref{ rref_locTCT = def
366 PlainSpan attrs -> pure $ TS.Tree n' skip
367 where n' = PlainSpan{attrs = CommonAttrs{ attrs_id = Nothing
368 , attrs_classes = List.sort $ attrs_classes attrs }}
378 PlainEref{..} -> keep
379 PlainTag{..} -> pure $ TS.Tree PlainTag{tag_locTCT=def, tag_posXML=def, ..} skip
382 -- | Return the same hash if 'similarPlain' is applied on the 'Title' before hashing.
384 -- Warning: when using the key of HashMap or HashSet,
385 -- only the data taken into account by this 'Hashable' instance is reliable.
386 instance Hashable Title where
387 hashWithSalt salt (Title ps) = hs salt ps
395 PlainIref{..} -> s`hashWithSalt`(0::Int)`hashWithSalt`iref_term
396 PlainTag{..} -> s`hashWithSalt`(1::Int)`hashWithSalt`tag_ident
397 PlainSpan{..} -> s`hashWithSalt`(2::Int)`hashWithSalt`List.sort (attrs_classes attrs)
398 PlainB -> s`hashWithSalt`(3::Int)
399 PlainCode -> s`hashWithSalt`(4::Int)
400 PlainDel -> s`hashWithSalt`(5::Int)
401 PlainI -> s`hashWithSalt`(6::Int)
402 PlainQ -> s`hashWithSalt`(7::Int)
403 PlainSC -> s`hashWithSalt`(8::Int)
404 PlainSub -> s`hashWithSalt`(9::Int)
405 PlainSup -> s`hashWithSalt`(10::Int)
406 PlainU -> s`hashWithSalt`(11::Int)
407 PlainEref{..} -> s`hashWithSalt`(12::Int)`hashWithSalt`eref_href
408 PlainRref{..} -> s`hashWithSalt`(13::Int)`hashWithSalt`rref_to
409 PlainBreak -> s`hashWithSalt`(14::Int)
410 PlainText t -> s`hashWithSalt`(15::Int)`hashWithSalt`t
414 { entity_name :: !TL.Text
415 , entity_street :: !TL.Text
416 , entity_zipcode :: !TL.Text
417 , entity_city :: !TL.Text
418 , entity_region :: !TL.Text
419 , entity_country :: !TL.Text
420 , entity_email :: !TL.Text
421 , entity_tel :: !TL.Text
422 , entity_fax :: !TL.Text
423 , entity_url :: !(Maybe URL)
424 , entity_org :: !(Maybe Entity)
426 instance Default Entity where
429 , entity_street = def
430 , entity_zipcode = def
432 , entity_region = def
433 , entity_country = def
442 newtype Include = Include
445 instance Default Include where
450 -- * Type 'Reference'
451 data Reference = Reference
452 { {-reference_error :: !(Maybe ErrorAnchor)
453 ,-} reference_posXML :: !XML.Pos
454 , reference_locTCT :: !TCT.Location
455 , reference_id :: !Ident
456 , reference_about :: !About
462 , month :: !(Maybe Nat1)
463 , day :: !(Maybe Nat1)
465 instance Default Date where
468 , month = Just (Nat1 01)
469 , day = Just (Nat1 01)
471 instance Semigroup Date where
479 , type_ :: !(Maybe TL.Text)
482 instance Default Link where
493 { alias_attrs :: !CommonAttrs
494 , alias_title :: !Title
499 { serie_name :: !Name
500 , serie_id :: !TL.Text
502 instance Default Serie where
508 -- | Builtins 'URL' recognized from |Serie|'s 'name'.
509 urlSerie :: Serie -> Maybe URL
512 "RFC" | TL.all Char.isDigit serie_id ->
513 Just $ URL $ "https://tools.ietf.org/html/rfc"<>serie_id
514 "DOI" -> Just $ URL $ "https://dx.doi.org/"<>serie_id
521 type Words = [WordOrSpace]
523 -- *** Type 'WordOrSpace'
527 deriving (Eq,Ord,Show,Generic)
528 instance Hashable WordOrSpace
531 type Terms = [Aliases]
533 -- *** Type 'Aliases'
534 type Aliases = [Words]
536 -- ** Type 'PathWord'
537 type PathWord = TM.Path Word
539 pathFromWords :: Words -> Maybe PathWord
541 case ws >>= unSpace of
542 p:ps | not (TL.null p) -> Just (TM.path p ps)
550 type Location = (TCT.Location, XML.Pos)