packages feed

hxt-7.1: src/Text/XML/HXT/RelaxNG/CreatePattern.hs

-- |
-- 
-- Creates the 'Pattern' datastructure from a simplified Relax NG schema. 
-- The created datastructure is used in the validation algorithm
-- (see also: "Text.XML.HXT.RelaxNG.Validation")

module Text.XML.HXT.RelaxNG.CreatePattern
  ( createPatternFromXmlTree
  , createNameClass
  , module Text.XML.HXT.RelaxNG.PatternFunctions
  )
where

import Control.Arrow.ListArrows

import Text.XML.HXT.Arrow.DOMInterface

import Text.XML.HXT.Arrow.XmlArrow hiding
    ( hasName )

import qualified Text.XML.HXT.Arrow.XmlArrow as A
    ( hasName )

import Text.XML.HXT.RelaxNG.DataTypes
import Text.XML.HXT.RelaxNG.PatternFunctions

import Data.Maybe
    ( fromMaybe )

import Data.List
    ( isPrefixOf )

-- ------------------------------------------------------------

hasName :: String -> LA XmlTree XmlTree
hasName s = A.hasName s 
            `orElse`
            (hasLocalPart s >>> hasNamespaceUri relaxNamespace)


-- | Creates the 'Pattern' datastructure from a simplified Relax NG schema.
createPatternFromXmlTree :: LA XmlTree Pattern
createPatternFromXmlTree = createPatternFromXml $< createEnv
 where
 -- | Selects all define-pattern and creates an environment list.
 -- Each list entry maps the define name to the children of the define-pattern.
 -- The map is used to replace a ref-pattern with the referenced define-pattern.
 createEnv :: LA XmlTree Env
 createEnv = listA $ deep (isElem >>> hasName "define") 
                     >>> 
                     (getAttrValue "name" &&& getChildren)


-- | Transforms each XML-element to the corresponding pattern
createPatternFromXml :: Env -> LA XmlTree Pattern
createPatternFromXml env
 = choiceA [
     isRoot                            :-> processRoot env,
     (isElem >>> hasName "empty")      :-> constA Empty,
     (isElem >>> hasName "notAllowed") :-> mkNotAllowed,
     (isElem >>> hasName "text")       :-> constA Text,
     (isElem >>> hasName "choice")     :-> mkRelaxChoice env,
     (isElem >>> hasName "interleave") :-> mkRelaxInterleave env,
     (isElem >>> hasName "group")      :-> mkRelaxGroup env,
     (isElem >>> hasName "oneOrMore")  :-> mkRelaxOneOrMore env,
     (isElem >>> hasName "list")       :-> mkRelaxList env,
     (isElem >>> hasName "data")       :-> mkRelaxData env,
     (isElem >>> hasName "value")      :-> mkRelaxValue,
     (isElem >>> hasName "attribute")  :-> mkRelaxAttribute env,
     (isElem >>> hasName "element")    :-> mkRelaxElement env,
     (isElem >>> hasName "ref")        :-> mkRelaxRef env,
     this                              :-> mkRelaxError ""
   ]

              
processRoot :: Env -> LA XmlTree Pattern
processRoot env
  = getChildren
    >>> 
    choiceA [
      (isElem >>> hasName "relaxError") :-> (mkRelaxError $< getAttrValue "desc"),
      (isElem >>> hasName "grammar")    :-> (processGrammar env),
      this                              :-> (mkRelaxError "no grammar-pattern in schema")
    ]


processGrammar :: Env -> LA XmlTree Pattern
processGrammar env
  = getChildren
    >>> 
    choiceA [
      (isElem >>> hasName "define")     :-> none,
      (isElem >>> hasName "relaxError") :-> (mkRelaxError $< getAttrValue "desc"),
      (isElem >>> hasName "start")      :-> (getChildren >>> createPatternFromXml env),
      this                              :-> (mkRelaxError "no start-pattern in schema")
    ]


{- |
  Transforms a ref-element.
  The value of the name-attribute is looked up in the environment list
  to find the corresponding define-pattern. 
  Haskells lazy-evaluation is used to transform circular structures.
-}
mkRelaxRef :: Env -> LA XmlTree Pattern
mkRelaxRef e
 = getAttrValue "name"
   >>>
   arr (\n -> fromMaybe (NotAllowed $ "define-pattern with name " ++ n ++ " not found")
              . lookup n $ transformEnv e
       )
 where
 transformEnv :: [(String, XmlTree)] -> [(String, Pattern)]
 transformEnv env = [ (treeName, (transformEnvElem tree env)) | (treeName, tree) <- env]
 transformEnvElem :: XmlTree -> [(String, XmlTree)] -> Pattern
 transformEnvElem tree env = head $ runLA (createPatternFromXml env) tree 


-- | Transforms a notAllowed-element.
mkNotAllowed :: LA XmlTree Pattern
mkNotAllowed = constA $ NotAllowed "notAllowed-pattern in Relax NG schema definition"


-- | Creates an error message.
mkRelaxError :: String -> LA XmlTree Pattern
mkRelaxError errStr
 = choiceA [
     (isElem >>> hasName "relaxError") :-> (getAttrValue "desc" >>> arr NotAllowed),
     isElem  :-> ( getName
                   >>>
                   arr (\n -> NotAllowed $ "Pattern " ++ n ++ 
                                           " is not allowed in Relax NG schema"
                       )
                 ),
     isAttr  :-> ( getName
                   >>>
                   arr (\n -> NotAllowed $ "Attribute " ++ n ++ 
                                           " is not allowed in Relax NG schema"
                       )
                 ),
     isError :-> (getErrorMsg >>> arr NotAllowed),                          
     this    :-> (arr (\e -> NotAllowed $ if errStr /= ""
                                          then errStr
                                          else "Can't create pattern from " ++ show e)
                 )
   ]


-- | Transforms a choice-element.
mkRelaxChoice :: Env -> LA XmlTree Pattern
mkRelaxChoice env = listA getChildren
                    >>> 
                    ifP (\l -> length l == 1)
                      (arrL id >>> createPatternFromXml env)
                      (getTwoChildrenPattern env >>> arr2 Choice)

               
-- | Transforms a interleave-element.
mkRelaxInterleave :: Env -> LA XmlTree Pattern
mkRelaxInterleave env = listA getChildren
                        >>> 
                        getTwoChildrenPattern env
                        >>> 
                        arr2 Interleave


-- | Transforms a group-element.
mkRelaxGroup :: Env -> LA XmlTree Pattern
mkRelaxGroup env = listA getChildren
                   >>> 
                   getTwoChildrenPattern env
                   >>>
                   arr2 Group


-- | Transforms a oneOrMore-element.
mkRelaxOneOrMore :: Env -> LA XmlTree Pattern
mkRelaxOneOrMore env = getOneChildPattern env
                       >>> 
                       arr OneOrMore


-- | Transforms a list-element.
mkRelaxList :: Env -> LA XmlTree Pattern
mkRelaxList env = getOneChildPattern env >>> arr List


-- | Transforms a data- or dataExcept-element.
mkRelaxData :: Env -> LA XmlTree Pattern
mkRelaxData env 
  = ifA (getChildren >>> hasName "except")
     (processDataExcept >>> arr3 DataExcept)
     (processData >>> arr2 Data)
  where
  processDataExcept :: LA XmlTree (Datatype, (ParamList, Pattern))
  processDataExcept = getDatatype &&& getParamList &&& 
                      ( getChildren
                        >>> 
                        isElem >>> hasName "except"
                        >>> 
                        getChildren
                        >>>
                        createPatternFromXml env
                      )
  processData :: LA XmlTree (Datatype, ParamList)
  processData = getDatatype &&& getParamList
  getParamList :: LA XmlTree ParamList
  getParamList = listA $ getChildren
                         >>>
                         isElem >>> hasName "param"
                         >>> 
                         (getAttrValue "name" &&& (getChildren >>> getText))
         

-- | Transforms a value-element.
mkRelaxValue :: LA XmlTree Pattern         
mkRelaxValue = getDatatype &&& getValue &&& getContext
               >>>
               arr3 Value 
  where
  getContext :: LA XmlTree Context
  getContext = getAttrValue contextBaseAttr &&& getMapping
  getMapping :: LA XmlTree [(Prefix, Uri)]
  getMapping = listA $ getAttrl >>> 
                       ( (getName >>> isA (contextAttributes `isPrefixOf`))
                         `guards`
                         ( (getName >>> arr (drop $ length contextAttributes)) 
                           &&&
                           (getChildren >>> getText)
                         )
                       )
  getValue :: LA XmlTree String
  getValue = (getChildren >>> getText) `orElse` (constA "")


getDatatype :: LA XmlTree Datatype
getDatatype = getAttrValue "datatypeLibrary"
              &&&
              getAttrValue "type"


-- | Transforms a attribute-element.
-- The first child is a 'NameClass', the second one a pattern.
mkRelaxAttribute :: Env -> LA XmlTree Pattern
mkRelaxAttribute env
 = listA getChildren
   >>> 
   ((arr head >>> createNameClass) &&& secondPattern env)
   >>>
   arr2 Attribute


-- | Transforms a element-element.
-- The first child is a 'NameClass', the second one a pattern.
mkRelaxElement :: Env -> LA XmlTree Pattern
mkRelaxElement env
 = listA getChildren
   >>> 
   ((arr head >>> createNameClass) &&& secondPattern env)
   >>>
   arr2 Element


-- | Creates a 'NameClass' from an \"anyName\"-, \"nsName\"- or  \"name\"-Pattern, 
createNameClass :: LA XmlTree NameClass
createNameClass
 = choiceA [   
     (isElem >>> hasName "anyName") :-> processAnyName,
     (isElem >>> hasName "nsName")  :-> processNsName,
     (isElem >>> hasName "name")    :-> processName,
     (isElem >>> hasName "choice")  :-> processChoice,
     this                           :-> mkNameClassError
   ]
 where
 processAnyName :: LA XmlTree NameClass
 processAnyName = ifA (getChildren >>> hasName "except")
                    ( getChildren >>> getChildren >>>
                      createNameClass >>> arr AnyNameExcept
                    )
                    (constA AnyName)
 processNsName :: LA XmlTree NameClass
 processNsName = ifA (getChildren >>> hasName "except")
                   ( ( getAttrValue "ns" 
                       &&&
                       (getChildren >>> getChildren >>> createNameClass)
                     )
                     >>> 
                     arr2 NsNameExcept
                   )
                   (getAttrValue "ns" >>> arr NsName) 
 processName :: LA XmlTree NameClass
 processName = (getAttrValue "ns" &&& (getChildren >>> getText)) >>> arr2 Name
 processChoice :: LA XmlTree NameClass
 processChoice = listA getChildren
                 >>>
                 (arr head >>> createNameClass) &&& (arr last >>> createNameClass)
                 >>>
                 arr2 NameClassChoice
                        

mkNameClassError :: LA XmlTree NameClass
mkNameClassError 
 = choiceA [
     (isElem >>> hasName "relaxError") :-> (getAttrValue "desc" >>> arr NCError), 
     isElem  :-> ( getName
                   >>>
                   arr (\n -> NCError ("Can't create name class from element " ++ n))
                 ),
     isAttr  :-> ( getName
                   >>>
                   arr (\n -> NCError ("Can't create name class from attribute: " ++ n))
                 ),
     isError :-> (getErrorMsg >>> arr NCError),                          
     this    :-> (arr (\e ->  NCError $ "Can't create name class from " ++ show e))      
   ]


getOneChildPattern :: Env -> LA XmlTree Pattern
getOneChildPattern env = getChildren >>> createPatternFromXml env


getTwoChildrenPattern :: Env -> LA XmlTrees (Pattern, Pattern)
getTwoChildrenPattern env = firstPattern env &&& secondPattern env


firstPattern :: Env -> LA XmlTrees Pattern
firstPattern env = arr head >>> createPatternFromXml env


-- | After simplification, each choice, group, etc. pattern has exactly two children,
-- so @last@ selects the second one
secondPattern :: Env -> LA XmlTrees Pattern
secondPattern env = arr last >>> createPatternFromXml env