1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 module Symantic.XML.RelaxNG.Language where
6 import Data.Eq (Eq(..))
7 import Data.Function (($), (.))
8 import Data.Maybe (Maybe(..))
9 import Data.Semigroup (Semigroup(..))
10 import Data.String (String, IsString(..))
11 import Prelude (error)
12 import Text.Show (Show(..))
13 import qualified Data.List as List
14 import qualified Data.HashMap.Strict as HM
16 import Symantic.Base.Fixity
17 import Symantic.XML.Language
24 ) => RelaxNG repr where
25 default elementMatch ::
27 RelaxNG (UnTrans repr) =>
28 NameClass -> repr a k -> repr (QName -> a) k
29 -- | Like 'element' but with a matching pattern
30 -- instead of a specific 'QName'.
31 elementMatch :: NameClass -> repr a k -> repr (QName -> a) k
32 elementMatch nc = noTrans . elementMatch nc . unTrans
33 default attributeMatch ::
35 RelaxNG (UnTrans repr) =>
36 NameClass -> repr a k -> repr (QName -> a) k
37 -- | Like 'attribute' but with a matching pattern
38 -- instead of a specific 'QName'.
39 attributeMatch :: NameClass -> repr a k -> repr (QName -> a) k
40 attributeMatch nc = noTrans . attributeMatch nc . unTrans
43 class Definable repr where
44 -- | @(define name expr)@ declares a rule named @(name)@
45 -- and matching the 'RelaxNG' schema @(expr)@.
47 -- Useful for rendering the 'RelaxNG' schema,
48 -- and necessary to avoid infinite recursion when
49 -- printing a 'RelaxNG' schema calling itself recursively.
51 -- WARNING: 'DefineName's must be unique inside
52 -- a whole 'RelaxNG' schema.
53 define :: DefineName -> repr a k -> repr a k
55 Transformable repr => RelaxNG (UnTrans repr) =>
56 DefineName -> repr f k -> repr f k
57 define n = noTrans . define n . unTrans
59 -- ** Type 'DefineName'
60 type DefineName = String
65 | (:::) Namespace NCName
67 | (:-:) NameClass NameClass
68 | (:|:) NameClass NameClass
77 -- | @('matchNameClass' nc q)@ returns 'True' iif. the 'NameClass' @(nc)@ matches the 'QName' @(q)@.
78 matchNameClass :: NameClass -> QName -> Bool
79 matchNameClass NameClass_Any _q = True
80 matchNameClass (ns:::nl) q = qNameSpace q == ns && qNameLocal q == nl
81 matchNameClass ((:*) ns) q = qNameSpace q == ns
82 matchNameClass (x:|:y) q = matchNameClass x q || matchNameClass y q
83 matchNameClass (x:-:y) q = matchNameClass x q && not (matchNameClass y q)
85 -- | Return the namespaces used by the given 'NameClass'
86 namespacesNameClass :: NameClass -> HM.HashMap Namespace (Maybe NCName)
87 namespacesNameClass = \case
88 NameClass_Any -> HM.empty
89 ns ::: _ -> HM.singleton ns Nothing
90 (:*) ns -> HM.singleton ns Nothing
91 x :|: y -> namespacesNameClass x <> namespacesNameClass y
92 x :-: y -> namespacesNameClass x <> namespacesNameClass y
94 -- | Only parses "*", "{some-namespace}*", or "{some-namespace}some-localname".
95 instance IsString NameClass where
99 case List.break (== '}') rest of
100 (_, "") -> error $ "Invalid XML Clark notation: "<>show full
101 (ns, "*") -> (:*) (fromString ns)
102 (ns, local) -> fromString ns ::: fromString (List.drop 1 local)
103 s -> let QName ns nl = fromString s in ns:::nl
104 instance Textify (Namespaces NCName, (Infix,Side), NameClass) where
105 textify (nss,po,nc) = case nc of
106 NameClass_Any -> textify '*'
108 textify (prefixifyQName nss (QName ns nl))
110 case HM.lookup ns (namespaces_prefixes nss) of
111 Nothing -> "{"<>textify ns<>"}*"
112 Just p -> textify p <> ":*"
113 x :|: y -> pairIfNeeded pairParen po op $
114 textify (nss,(op,SideL),x) <> " | " <> textify (nss,(op,SideR),y)
117 pairIfNeeded pairParen po op $
118 textify (nss,(op,SideL),x) <> " - " <> textify (nss,(op,SideR),y)