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 | PlainRef { ref_locTCT :: !TCT.Location
290 , ref_posXML :: !XML.Pos
291 , ref_ident :: !Ident
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
304 | PlainAt { at_locTCT :: !TCT.Location
305 , at_posXML :: !XML.Pos
313 newtype Tag = Tag TL.Text
314 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
318 -- * Type 'ErrorTarget'
320 = ErrorTarget_Unknown !Nat1
321 | ErrorTarget_Ambiguous !(Maybe Nat1)
324 -- * Type 'ErrorAnchor'
326 = ErrorAnchor_Ambiguous !Nat1
330 -- * Type 'CommonAttrs'
331 data CommonAttrs = CommonAttrs
332 { attrs_id :: !(Maybe Ident)
333 , attrs_classes :: ![TL.Text]
334 } deriving (Eq,Ord,Show)
335 instance Default CommonAttrs where
338 , attrs_classes = def
343 { anchor_section :: !XML.Pos
344 , anchor_count :: !Nat1
345 } deriving (Eq,Ord,Show)
348 newtype Name = Name { unName :: TL.Text }
349 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
352 newtype Title = Title { unTitle :: Plain }
353 deriving (Show,Semigroup,Monoid,Default)
354 instance Eq Title where
355 (==) = (==) `on` similarPlain . unTitle
356 -- | Return a similar version of a 'Plain' by removing:
358 -- * parsing residues ('PlainGroup'),
359 -- * notes ('PlainNote'),
360 -- * and position specific annotations ('Ident' and 'Anchor').
361 similarPlain :: Plain -> Plain
362 similarPlain = foldMap $ \(TS.Tree n ts) ->
363 let skip = similarPlain ts in
364 let keep = pure $ TS.Tree n $ skip in
368 PlainIref{..} -> keep
369 PlainRef{..} -> pure $ TS.Tree PlainRef{ ref_locTCT = def
372 PlainSpan attrs -> pure $ TS.Tree n' skip
373 where n' = PlainSpan{attrs = CommonAttrs{ attrs_id = Nothing
374 , attrs_classes = List.sort $ attrs_classes attrs }}
384 PlainEref{..} -> keep
385 PlainAt{..} -> pure $ TS.Tree PlainAt{at_locTCT=def, at_posXML=def, ..} skip
386 PlainTag{..} -> pure $ TS.Tree PlainTag{tag_locTCT=def, tag_posXML=def, ..} skip
389 -- | Return the same hash if 'similarPlain' is applied on the 'Title' before hashing.
391 -- Warning: when using the key of HashMap or HashSet,
392 -- only the data taken into account by this 'Hashable' instance is reliable.
393 instance Hashable Title where
394 hashWithSalt salt (Title ps) = hs salt ps
402 PlainIref{..} -> s`hashWithSalt`(0::Int)`hashWithSalt`iref_term
403 PlainTag{..} -> s`hashWithSalt`(1::Int)`hashWithSalt`tag_ident`hashWithSalt`tag_back
404 PlainAt{..} -> s`hashWithSalt`(2::Int)`hashWithSalt`at_ident`hashWithSalt`at_back
405 PlainSpan{..} -> s`hashWithSalt`(3::Int)`hashWithSalt`List.sort (attrs_classes attrs)
406 PlainB -> s`hashWithSalt`(4::Int)
407 PlainCode -> s`hashWithSalt`(5::Int)
408 PlainDel -> s`hashWithSalt`(6::Int)
409 PlainI -> s`hashWithSalt`(7::Int)
410 PlainQ -> s`hashWithSalt`(8::Int)
411 PlainSC -> s`hashWithSalt`(9::Int)
412 PlainSub -> s`hashWithSalt`(10::Int)
413 PlainSup -> s`hashWithSalt`(11::Int)
414 PlainU -> s`hashWithSalt`(12::Int)
415 PlainEref{..} -> s`hashWithSalt`(13::Int)`hashWithSalt`eref_href
416 PlainRef{..} -> s`hashWithSalt`(14::Int)`hashWithSalt`ref_ident
417 PlainBreak -> s`hashWithSalt`(15::Int)
418 PlainText t -> s`hashWithSalt`(16::Int)`hashWithSalt`t
422 { entity_name :: !TL.Text
423 , entity_street :: !TL.Text
424 , entity_zipcode :: !TL.Text
425 , entity_city :: !TL.Text
426 , entity_region :: !TL.Text
427 , entity_country :: !TL.Text
428 , entity_email :: !TL.Text
429 , entity_tel :: !TL.Text
430 , entity_fax :: !TL.Text
431 , entity_url :: !(Maybe URL)
432 , entity_org :: !(Maybe Entity)
434 instance Default Entity where
437 , entity_street = def
438 , entity_zipcode = def
440 , entity_region = def
441 , entity_country = def
450 newtype Include = Include
453 instance Default Include where
458 -- * Type 'Reference'
459 data Reference = Reference
460 { {-reference_error :: !(Maybe ErrorAnchor)
461 ,-} reference_posXML :: !XML.Pos
462 , reference_locTCT :: !TCT.Location
463 , reference_id :: !Ident
464 , reference_about :: !About
470 , month :: !(Maybe Nat1)
471 , day :: !(Maybe Nat1)
473 instance Default Date where
476 , month = Just (Nat1 01)
477 , day = Just (Nat1 01)
479 instance Semigroup Date where
487 , type_ :: !(Maybe TL.Text)
490 instance Default Link where
501 { alias_attrs :: !CommonAttrs
502 , alias_title :: !Title
507 { serie_name :: !Name
508 , serie_id :: !TL.Text
510 instance Default Serie where
516 -- | Builtins 'URL' recognized from |Serie|'s 'name'.
517 urlSerie :: Serie -> Maybe URL
520 "RFC" | TL.all Char.isDigit serie_id ->
521 Just $ URL $ "https://tools.ietf.org/html/rfc"<>serie_id
522 "DOI" -> Just $ URL $ "https://dx.doi.org/"<>serie_id
529 type Words = [WordOrSpace]
531 -- *** Type 'WordOrSpace'
535 deriving (Eq,Ord,Show,Generic)
536 instance Hashable WordOrSpace
539 type Terms = [Aliases]
541 -- *** Type 'Aliases'
542 type Aliases = [Words]
544 -- ** Type 'PathWord'
545 type PathWord = TM.Path Word
547 pathFromWords :: Words -> Maybe PathWord
549 case ws >>= unSpace of
550 p:ps | not (TL.null p) -> Just (TM.path p ps)
558 type Location = (TCT.Location, XML.Pos)