@@ -0,0 +1,579 @@
1+{-# LANGUAGE OverloadedStrings #-}
13+import qualified Data.ByteString.Lazy as Lazy (ByteString)
14+import Data.Text (Text)
15+import qualified Data.Text as Text
17+import qualified Data.Map as Map
19+import Control.Monad.Trans
20+import Control.Monad.Trans.Reader
21+import Control.Monad.Trans.Except
22+import Text.Read (readEither)
24+import Text.XML.Cursor
26+import Xsd.Types (Xsd(Xsd))
27+import qualified Xsd.Types as Xsd
29+parseSchema :: Cursor -> P Xsd
31+ let targetNamespace = Xsd.Namespace <$> anAttribute "targetNamespace" c
33+ children <- local (\env -> env {envTargetNamespace = targetNamespace}) $
36+ { Xsd.targetNamespace = targetNamespace
37+ , Xsd.children = children
40+parseChildren :: Cursor -> P [Xsd.Child]
42+ catMaybes <$> mapM parseChild (c $/ anyElement)
44+-- | Parse top level schema node
46+-- Returns Nothing for unknown/unsupported nodes
47+parseChild :: Cursor -> P (Maybe Xsd.Child)
49+ name <- getElementName c
51+ "element" -> Just . Xsd.ChildElement <$> parseElementOrRef c
52+ "simpleType" -> Just <$> parseTopSimpleType c
53+ "complexType" -> Just <$> parseTopComplexType c
54+ "include" -> Just . Xsd.ChildInclude <$> parseInclude c
55+ "import" -> Just . Xsd.ChildImport <$> parseImport c
58+parseInclude :: Cursor -> P Xsd.Include
60+ location <- theAttribute "schemaLocation" c
62+ { Xsd.includeLocation = location
65+parseImport :: Cursor -> P Xsd.Import
68+ location = anAttribute "schemaLocation" c
69+ namespace = Xsd.Namespace <$> anAttribute "namespace" c
71+ tns <- asks envTargetNamespace
72+ case (namespace, tns) of
73+ (Nothing, Nothing) -> parseError c "Expected namespace"
76+ -> parseError c "Namespace equals target namespace"
80+ { Xsd.importLocation = location
81+ , Xsd.importNamespace = namespace
84+parseElementOrRef :: Cursor -> P (Xsd.RefOr Xsd.Element)
85+parseElementOrRef c = do
86+ case anAttribute "ref" c of
87+ Nothing -> Xsd.Inline <$> parseElement c
88+ Just ref -> Xsd.Ref <$> makeQName c ref
90+parseElement :: Cursor -> P Xsd.Element
91+parseElement c = handleNamespaces c $ do
92+ name <- theAttribute "name" c
94+ tp <- case anAttribute "type" c of
95+ Just t -> Xsd.Ref <$> makeQName c t
96+ _ -> maybe (Xsd.Ref anyType) Xsd.Inline <$> parseType c
98+ minOccurs <- case anAttribute "minOccurs" c of
100+ Just val -> case readEither (Text.unpack val) of
101+ Left err -> parseError c ("Can't parse minOccurs: " <> Text.pack err)
104+ maxOccurs <- case anAttribute "maxOccurs" c of
105+ Nothing -> return (Xsd.MaxOccurs 1)
106+ Just "unbounded" -> return Xsd.MaxOccursUnbound
107+ Just val -> case readEither (Text.unpack val) of
108+ Left err -> parseError c ("Can't parse maxOccurs" <> Text.pack err)
109+ Right v -> return (Xsd.MaxOccurs v)
111+ annotations <- parseAnnotations c
113+ qname <- makeTargetQName name
115+ nillable <- case anAttribute "nillable" c of
116+ Nothing -> return False
117+ Just "true" -> return True
118+ Just "false" -> return False
119+ _ -> parseError c "Unexpected value of nillable attribute"
122+ { Xsd.elementName = qname
123+ , Xsd.elementType = tp
124+ , Xsd.elementAnnotations = annotations
125+ , Xsd.elementOccurs = (minOccurs, maxOccurs)
126+ , Xsd.elementNillable = nillable
129+ anyType = Xsd.QName (Just (Xsd.Namespace Xsd.schemaNamespace)) "anyType"
131+-- | Parse inline type, simple or complex
132+parseType :: Cursor -> P (Maybe Xsd.Type)
134+ simpleTypeAxis <- makeElemAxis "simpleType"
135+ complexTypeAxis <- makeElemAxis "complexType"
136+ case (c $/ simpleTypeAxis, c $/ complexTypeAxis) of
137+ ([], []) -> return Nothing
138+ ([t], []) -> Just . Xsd.TypeSimple <$> parseSimpleType t
139+ ([], [t]) -> Just . Xsd.TypeComplex <$> parseComplexType t
140+ _ -> parseError c "Multiple types"
142+-- | Top level simple type
143+parseTopSimpleType :: Cursor -> P Xsd.Child
144+parseTopSimpleType c = do
145+ name <- theAttribute "name" c >>= makeTargetQName
146+ t <- parseSimpleType c
147+ return $ Xsd.ChildType name (Xsd.TypeSimple t)
149+-- | Top level complex type
150+parseTopComplexType :: Cursor -> P Xsd.Child
151+parseTopComplexType c = do
152+ name <- theAttribute "name" c >>= makeTargetQName
153+ t <- parseComplexType c
154+ return $ Xsd.ChildType name (Xsd.TypeComplex t)
156+parseSimpleType :: Cursor -> P Xsd.SimpleType
157+parseSimpleType c = do
158+ restrictionAxis <- makeElemAxis "restriction"
159+ listAxis <- makeElemAxis "list"
160+ unionAxis <- makeElemAxis "union"
161+ case (c $/ restrictionAxis, c $/ listAxis, c $/ unionAxis) of
162+ ([e], [], []) -> Xsd.AtomicType
163+ <$> parseSimpleRestriction e
164+ <*> parseAnnotations c
165+ ([], [l], []) -> Xsd.ListType
167+ <*> parseAnnotations c
168+ ([], [], [u]) -> Xsd.UnionType
170+ <*> parseAnnotations c
171+ _ -> parseError c "Expected exactly of of restriction, list or union"
173+-- | Restriction for simple type
174+parseSimpleRestriction :: Cursor -> P Xsd.SimpleRestriction
175+parseSimpleRestriction c = do
176+ -- XXX: generalaze RefOr parsing
177+ tp <- case anAttribute "base" c of
179+ simpleTypeAxis <- makeElemAxis "simpleType"
180+ case c $/ simpleTypeAxis of
181+ [] -> parseError c "Expected simpleType"
182+ [s] -> Xsd.Inline <$> parseSimpleType s
183+ _ -> parseError c "Multiple types"
184+ Just t -> Xsd.Ref <$> makeQName c t
186+ constraints <- parseConstrains c
188+ return Xsd.SimpleRestriction
189+ { Xsd.simpleRestrictionBase = tp
190+ , Xsd.simpleRestrictionConstraints = constraints
193+parseConstrains :: Cursor -> P [Xsd.Constraint]
194+parseConstrains c = do
195+ enumerationAxis <- makeElemAxis "enumeration"
196+ forM (c $/ enumerationAxis) $ \e -> do
197+ Xsd.Enumeration <$> theAttribute "value" e
199+parseList :: Cursor -> P (Xsd.RefOr Xsd.SimpleType)
201+ -- XXX: generalaze RefOr parsing
202+ case anAttribute "itemType" c of
204+ simpleTypeAxis <- makeElemAxis "simpleType"
205+ case c $/ simpleTypeAxis of
206+ [] -> parseError c "Expected simpleType"
207+ [s] -> Xsd.Inline <$> parseSimpleType s
208+ _ -> parseError c "Multiple types"
209+ Just t -> Xsd.Ref <$> makeQName c t
211+parseUnion :: Cursor -> P [Xsd.RefOr Xsd.SimpleType]
213+ case anAttribute "memberTypes" c of
215+ mapM (fmap Xsd.Ref . makeQName c) (Text.splitOn " " list)
217+ simpleTypeAxis <- makeElemAxis "simpleType"
218+ mapM (fmap Xsd.Inline . parseSimpleType) (c $/ simpleTypeAxis)
220+parseComplexType :: Cursor -> P Xsd.ComplexType
221+parseComplexType c = do
222+ annotations <- parseAnnotations c
223+ cont <- parseContent c
224+ return Xsd.ComplexType
225+ { Xsd.complexAnnotations = annotations
226+ , Xsd.complexContent = cont
229+parseContent :: Cursor -> P Xsd.Content
231+ simpleContentAxis <- makeElemAxis "simpleContent"
232+ complexContentAxis <- makeElemAxis "complexContent"
233+ case (c $/ simpleContentAxis, c $/ complexContentAxis) of
234+ ([], []) -> Xsd.ContentPlain <$> parsePlainContent c
235+ ([s], []) -> Xsd.ContentSimple <$> parseSimpleContent s
236+ ([], [s]) -> Xsd.ContentComplex <$> parseComplexContent s
238+ "Expected one of simpleContent, complextContent or model group"
240+parsePlainContent :: Cursor -> P Xsd.PlainContent
241+parsePlainContent c = do
242+ attributes <- parseAttributes c
243+ model <- parseModelGroup c
244+ return Xsd.PlainContent
245+ { Xsd.plainContentModel = model
246+ , Xsd.plainContentAttributes = attributes
249+parseSimpleContent :: Cursor -> P Xsd.SimpleContent
250+parseSimpleContent c = do
251+ restrictionAxis <- makeElemAxis "restriction"
252+ extenstionAxis <- makeElemAxis "extension"
253+ case (c $/ restrictionAxis, c $/ extenstionAxis) of
254+ ([r], []) -> Xsd.SimpleContentRestriction <$> parseSimpleRestriction r
255+ ([], [e]) -> Xsd.SimpleContentExtension <$> parseSimpleExtension e
256+ _ -> parseError c "Expected one of restriction or extension"
258+parseSimpleExtension :: Cursor -> P Xsd.SimpleExtension
259+parseSimpleExtension c = handleNamespaces c $ do
260+ base <- theAttribute "base" c >>= makeQName c
261+ attributes <- parseAttributes c
262+ return Xsd.SimpleExtension
263+ { Xsd.simpleExtensionBase = base
264+ , Xsd.simpleExtensionAttributes = attributes
267+parseComplexContent :: Cursor -> P Xsd.ComplexContent
268+parseComplexContent c = do
269+ restrictionAxis <- makeElemAxis "restriction"
270+ extenstionAxis <- makeElemAxis "extension"
271+ case (c $/ restrictionAxis, c $/ extenstionAxis) of
272+ ([r], []) -> Xsd.ComplexContentRestriction <$> parseComplexRestriction r
273+ ([], [e]) -> Xsd.ComplexContentExtension <$> parseComplexExtension e
274+ _ -> parseError c "Expected one of restriction or extension"
276+parseComplexExtension :: Cursor -> P Xsd.ComplexExtension
277+parseComplexExtension c = handleNamespaces c $ do
278+ base <- theAttribute "base" c >>= makeQName c
279+ attributes <- parseAttributes c
280+ model <- parseModelGroup c
281+ return Xsd.ComplexExtension
282+ { Xsd.complexExtensionBase = base
283+ , Xsd.complexExtensionModel = model
284+ , Xsd.complexExtensionAttributes = attributes
287+parseComplexRestriction :: Cursor -> P Xsd.ComplexRestriction
288+parseComplexRestriction c = handleNamespaces c $ do
289+ base <- theAttribute "base" c >>= makeQName c
290+ attributes <- parseAttributes c
291+ model <- parseModelGroup c
292+ return Xsd.ComplexRestriction
293+ { Xsd.complexRestrictionBase = base
294+ , Xsd.complexRestrictionModel = model
295+ , Xsd.complexRestrictionAttributes = attributes
298+parseModelGroup :: Cursor -> P (Maybe Xsd.ModelGroup)
299+parseModelGroup c = do
300+ sequenceAxis <- makeElemAxis "sequence"
301+ choiceAxis <- makeElemAxis "choice"
302+ allAxis <- makeElemAxis "all"
303+ case (c $/ sequenceAxis, c $/ choiceAxis, c $/ allAxis) of
304+ ([], [], []) -> return Nothing
305+ ([g], [], []) -> Just <$> parseSequence g
306+ ([], [g], []) -> Just <$> parseChoice g
307+ ([], [], [g]) -> Just <$> parseAll g
308+ _ -> parseError c "Multiple model groups"
310+parseSequence :: Cursor -> P Xsd.ModelGroup
311+parseSequence c = Xsd.Sequence <$> parseElements c
313+parseChoice :: Cursor -> P Xsd.ModelGroup
314+parseChoice c = Xsd.Choice <$> parseElements c
316+parseAll :: Cursor -> P Xsd.ModelGroup
317+parseAll c = Xsd.All <$> parseElements c
319+-- | Get all child elements
320+parseElements :: Cursor -> P [Xsd.RefOr Xsd.Element]
322+ elementAxis <- makeElemAxis "element"
323+ mapM parseElementOrRef (c $/ elementAxis)
325+parseAttributes :: Cursor -> P [Xsd.Attribute]
326+parseAttributes c = do
327+ attrAxis <- makeElemAxis "attribute"
328+ mapM parseAttribute (c $/ attrAxis)
330+parseAttribute :: Cursor -> P Xsd.Attribute
331+parseAttribute c = do
332+ use <- parseUseAttribute c
333+ case (anAttribute "name" c, anAttribute "ref" c) of
334+ (Just n, Nothing) -> do
335+ name <- makeTargetQName n
336+ tp <- case anAttribute "type" c of
338+ simpleTypeAxis <- makeElemAxis "simpleType"
339+ case c $/ simpleTypeAxis of
340+ [s] -> Xsd.Inline <$> parseSimpleType s
341+ [] -> return (Xsd.Ref anySimpleType)
342+ _ -> parseError c "Multiple simple types"
343+ Just t -> Xsd.Ref <$> makeQName c t
344+ return $ Xsd.InlineAttribute $ Xsd.AttributeInline
345+ { Xsd.attributeInlineName = name
346+ , Xsd.attributeInlineUse = use
347+ , Xsd.attributeInlineType = tp
349+ (Nothing, Just r) -> do
350+ ref <- makeQName c r
351+ return $ Xsd.RefAttribute $ Xsd.AttributeRef
352+ { Xsd.attributeRefRef = ref
353+ , Xsd.attributeRefUse = use
355+ _ -> parseError c "Should be either name or ref"
358+ Xsd.QName (Just (Xsd.Namespace Xsd.schemaNamespace)) "anySimpleType"
360+parseUseAttribute :: Cursor -> P Xsd.Use
361+parseUseAttribute c = case anAttribute "use" c of
362+ Nothing -> return Xsd.Optional
363+ Just "optional" -> return Xsd.Optional
364+ Just "required" -> return Xsd.Required
365+ Just "prohibited" -> return Xsd.Prohibited
366+ _ -> parseError c "Unknown attribute use"
368+parseAnnotations :: Cursor -> P [Xsd.Annotation]
369+parseAnnotations c = do
370+ annotationAxis <- makeElemAxis "annotation"
371+ documentationAxis <- makeElemAxis "documentation"
376+ &// documentationAxis
378+ return $ Xsd.Documentation <$> documentation
380+-- Section: parser entry points
382+-- | Specifies modes of operation
384+ { configIgnoreSchemaNamespace :: Bool
385+ -- ^ if not set, parser will check that all relevant xml nodes
386+ -- live is the correct XMLSchema namespace
390+-- | Default configuration
391+defaultConfig :: Config
392+defaultConfig = Config
393+ { configIgnoreSchemaNamespace = False
396+-- | Parse the whole document
398+-- Make sure the document is parsed with `psRetainNamespaces` enabled!
399+parse :: Config -> Document -> Either ParseError Xsd
404+ , envTargetNamespace = Nothing
407+ c = fromDocument doc
408+ runExcept $ flip runReaderT env $ handleNamespaces c $ do
409+ schemaAxis <- makeElemAxis "schema"
410+ case c $.// schemaAxis of
411+ [] -> parseError c "Not schema found"
412+ [s] -> parseSchema s
413+ _ -> parseError c "Multiple schemata found"
417+-- It uses the `defaultConfig`
418+parseFile :: FilePath -> IO (Either ParseError Xsd)
419+parseFile path = parse defaultConfig <$>
420+ Text.XML.readFile def{psRetainNamespaces=True} path
422+parseLazyByteString :: Lazy.ByteString -> Either ParseError Xsd
423+parseLazyByteString = parse defaultConfig
424+ . parseLBS_ def{psRetainNamespaces=True}
426+-- Section: parser environment
428+-- | Namespace prefix, e.g. \"xs\" or \"tns\"
429+newtype Prefix = Prefix Text
432+-- | Parser environment
434+ { envConfig :: Config
435+ , envTargetNamespace :: Maybe Xsd.Namespace
436+ -- ^ targetNamespace for the current schema
437+ , envPrefixes :: [(Maybe Prefix, Xsd.Namespace)]
438+ -- ^ all xmlns:prefix="namespace" things from the
444+type P a = ReaderT Env (Except ParseError) a
446+-- Section: error handling
448+-- | Describes what exactly went wrong
449+data ParseError = ParseError
450+ { errorLocation :: Text
451+ -- ^ e.g. schema.element[Person].attribute[firstName]
452+ , errorMessage :: Text
456+-- | Builds path to the current curson position
458+-- Goes through parents and collects their element names,
459+-- optionally adds value of "name" attribute
460+pprCursor :: Cursor -> Text
461+pprCursor = Text.intercalate "." . go []
463+ go ctx c = case node c of
467+ elemName = nameLocalName (elementName e)
468+ name = case laxAttribute "name" c of
469+ (n:_) -> elemName <> "[" <> n <> "]"
476+-- | Fail with the location and message
477+parseError :: Cursor -> Text -> P a
478+parseError c msg = lift $ throwE ParseError
479+ { errorLocation = pprCursor c
480+ , errorMessage = msg
483+-- Section: general utilities
485+-- | Get value of an optional attribute
486+anAttribute :: Text -> Cursor -> Maybe Text
487+anAttribute name c = case c $| laxAttribute name of
491+-- | Get value of the required attribute
492+theAttribute :: Text -> Cursor -> P Text
493+theAttribute name c = maybe err return (anAttribute name c)
495+ err = parseError c ("Element should have attibute " <> name)
497+-- | Select nodes with the specified name
499+-- Depending on the `configIgnoreSchemaNamespace`, it will or will not
500+-- check the namespace to be XMLSchema one
501+makeElemAxis :: Text -> P Axis
502+makeElemAxis name = do
503+ ignore <- asks (configIgnoreSchemaNamespace . envConfig)
505+ then laxElement name
506+ else element (mkSchemaName name)
508+-- | Parses reference to a type
510+-- Fails if it has a prefix which is not bound
511+makeQName :: Cursor -> Text -> P Xsd.QName
512+makeQName c t = case Text.splitOn ":" t of
514+ ns <- asks (lookup Nothing . envPrefixes)
516+ { Xsd.qnNamespace = ns
520+ known <- asks (lookup (Just (Prefix p)) . envPrefixes)
522+ Nothing -> parseError c "Unbound prefix"
525+ { Xsd.qnNamespace = known
528+ _ -> parseError c ("Can't parse name " <> t)
530+makeTargetQName :: Text -> P Xsd.QName
531+makeTargetQName name = do
532+ tns <- asks envTargetNamespace
534+ { Xsd.qnNamespace = tns
538+-- | Get name of the current node
540+-- Depending on `configIgnoreSchemaNamespace` value, it will or will not
541+-- check the namespace to be XMLSchema one
542+getElementName :: Cursor -> P Text
543+getElementName c = do
546+ let name = elementName e
547+ ignore <- asks (configIgnoreSchemaNamespace . envConfig)
549+ when (nameNamespace name /= Just Xsd.schemaNamespace) $ do
550+ parseError c "Expected XMLSchema element"
551+ return (nameLocalName name)
552+ _ -> parseError c "Expected node element here"
554+-- | Attaches xml schema namespace
555+mkSchemaName :: Text -> Name
556+mkSchemaName name = Name name (Just Xsd.schemaNamespace) Nothing
558+-- | Add additional xmlns bindings into local scope
559+handleNamespaces :: Cursor -> P a -> P a
560+handleNamespaces c m = do
561+ ns <- parsePrefixes c
562+ local (\env -> env {envPrefixes = ns ++ envPrefixes env}) m
564+-- | Parse namespace bindings
566+-- Cursor should point to schema node
567+parsePrefixes :: Cursor -> P [(Maybe Prefix, Xsd.Namespace)]
568+parsePrefixes c = case node c of
569+ NodeElement e -> return
570+ . concatMap toPrefix
574+ _ -> parseError c "Expected an element"
576+ toPrefix (n, v) = case Text.splitOn ":" (nameLocalName n) of
577+ ["xmlns"] -> [(Nothing, Xsd.Namespace v)]
578+ ["xmlns", prefix] -> [(Just (Prefix prefix), Xsd.Namespace v)]