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
303 | PlainAref { aref_locTCT :: !TCT.Location
304 , aref_posXML :: !XML.Pos
305 , aref_ident :: !Ident
311 newtype Tag = Tag TL.Text
312 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
316 -- * Type 'ErrorTarget'
318 = ErrorTarget_Unknown !Nat1
319 | ErrorTarget_Ambiguous !(Maybe Nat1)
322 -- * Type 'ErrorAnchor'
324 = ErrorAnchor_Ambiguous !Nat1
328 -- * Type 'CommonAttrs'
329 data CommonAttrs = CommonAttrs
330 { attrs_id :: !(Maybe Ident)
331 , attrs_classes :: ![TL.Text]
332 } deriving (Eq,Ord,Show)
333 instance Default CommonAttrs where
336 , attrs_classes = def
341 { anchor_section :: !XML.Pos
342 , anchor_count :: !Nat1
343 } deriving (Eq,Ord,Show)
346 newtype Name = Name { unName :: TL.Text }
347 deriving (Eq,Ord,Show,Semigroup,Monoid,Default,IsString,Hashable)
350 newtype Title = Title { unTitle :: Plain }
351 deriving (Show,Semigroup,Monoid,Default)
352 instance Eq Title where
353 (==) = (==) `on` similarPlain . unTitle
354 -- | Return a similar version of a 'Plain' by removing:
356 -- * parsing residues ('PlainGroup'),
357 -- * notes ('PlainNote'),
358 -- * and position specific annotations ('Ident' and 'Anchor').
359 similarPlain :: Plain -> Plain
360 similarPlain = foldMap $ \(TS.Tree n ts) ->
361 let skip = similarPlain ts in
362 let keep = pure $ TS.Tree n $ skip in
366 PlainIref{..} -> keep
367 PlainRef{..} -> pure $ TS.Tree PlainRef{ ref_locTCT = def
370 PlainSpan attrs -> pure $ TS.Tree n' skip
371 where n' = PlainSpan{attrs = CommonAttrs{ attrs_id = Nothing
372 , attrs_classes = List.sort $ attrs_classes attrs }}
382 PlainEref{..} -> keep
383 PlainAt{..} -> pure $ TS.Tree PlainAt{at_locTCT=def, at_posXML=def, ..} skip
384 PlainTag{..} -> pure $ TS.Tree PlainTag{tag_locTCT=def, tag_posXML=def, ..} skip
387 -- | Return the same hash if 'similarPlain' is applied on the 'Title' before hashing.
389 -- Warning: when using the key of HashMap or HashSet,
390 -- only the data taken into account by this 'Hashable' instance is reliable.
391 instance Hashable Title where
392 hashWithSalt salt (Title ps) = hs salt ps
400 PlainIref{..} -> s`hashWithSalt`(0::Int)`hashWithSalt`iref_term
401 PlainTag{..} -> s`hashWithSalt`(1::Int)`hashWithSalt`tag_ident`hashWithSalt`tag_back
402 PlainAt{..} -> s`hashWithSalt`(2::Int)`hashWithSalt`at_ident`hashWithSalt`at_back
403 PlainSpan{..} -> s`hashWithSalt`(3::Int)`hashWithSalt`List.sort (attrs_classes attrs)
404 PlainB -> s`hashWithSalt`(4::Int)
405 PlainCode -> s`hashWithSalt`(5::Int)
406 PlainDel -> s`hashWithSalt`(6::Int)
407 PlainI -> s`hashWithSalt`(7::Int)
408 PlainQ -> s`hashWithSalt`(8::Int)
409 PlainSC -> s`hashWithSalt`(9::Int)
410 PlainSub -> s`hashWithSalt`(10::Int)
411 PlainSup -> s`hashWithSalt`(11::Int)
412 PlainU -> s`hashWithSalt`(12::Int)
413 PlainEref{..} -> s`hashWithSalt`(13::Int)`hashWithSalt`eref_href
414 PlainRef{..} -> s`hashWithSalt`(14::Int)`hashWithSalt`ref_ident
415 PlainBreak -> s`hashWithSalt`(15::Int)
416 PlainText t -> s`hashWithSalt`(16::Int)`hashWithSalt`t
420 { entity_name :: !TL.Text
421 , entity_street :: !TL.Text
422 , entity_zipcode :: !TL.Text
423 , entity_city :: !TL.Text
424 , entity_region :: !TL.Text
425 , entity_country :: !TL.Text
426 , entity_email :: !TL.Text
427 , entity_tel :: !TL.Text
428 , entity_fax :: !TL.Text
429 , entity_url :: !(Maybe URL)
430 , entity_org :: !(Maybe Entity)
432 instance Default Entity where
435 , entity_street = def
436 , entity_zipcode = def
438 , entity_region = def
439 , entity_country = def
448 newtype Include = Include
451 instance Default Include where
456 -- * Type 'Reference'
457 data Reference = Reference
458 { {-reference_error :: !(Maybe ErrorAnchor)
459 ,-} reference_posXML :: !XML.Pos
460 , reference_locTCT :: !TCT.Location
461 , reference_id :: !Ident
462 , reference_about :: !About
468 , month :: !(Maybe Nat1)
469 , day :: !(Maybe Nat1)
471 instance Default Date where
474 , month = Just (Nat1 01)
475 , day = Just (Nat1 01)
477 instance Semigroup Date where
485 , type_ :: !(Maybe TL.Text)
488 instance Default Link where
499 { alias_attrs :: !CommonAttrs
500 , alias_title :: !Title
505 { serie_name :: !Name
506 , serie_id :: !TL.Text
508 instance Default Serie where
514 -- | Builtins 'URL' recognized from |Serie|'s 'name'.
515 urlSerie :: Serie -> Maybe URL
518 "RFC" | TL.all Char.isDigit serie_id ->
519 Just $ URL $ "https://tools.ietf.org/html/rfc"<>serie_id
520 "DOI" -> Just $ URL $ "https://dx.doi.org/"<>serie_id
527 type Words = [WordOrSpace]
529 -- *** Type 'WordOrSpace'
533 deriving (Eq,Ord,Show,Generic)
534 instance Hashable WordOrSpace
537 type Terms = [Aliases]
539 -- *** Type 'Aliases'
540 type Aliases = [Words]
542 -- ** Type 'PathWord'
543 type PathWord = TM.Path Word
545 pathFromWords :: Words -> Maybe PathWord
547 case ws >>= unSpace of
548 p:ps | not (TL.null p) -> Just (TM.path p ps)
556 type Location = (TCT.Location, XML.Pos)