@@ -111,6 +111,8 @@ module Data.THGen.XML
111111 , (?)
112112 , (*)
113113 , (+)
114+ , (!%)
115+ , (?%)
114116 , (&)
115117 , (=:=)
116118 -- Re-exports
@@ -123,13 +125,16 @@ import Control.Lens hiding (repeated, enum, (&))
123125import Control.Lens.Internal.FieldTH (makeFieldOpticsForDec)
124126import qualified Data.Char as C
125127import Data.List.NonEmpty (NonEmpty)
128+import Data.Maybe (maybeToList, mapMaybe)
126129import Data.String
127130import Data.THGen.Enum
128131import qualified Data.Text as T
129132import qualified Language.Haskell.TH as TH
130133import Prelude hiding ((+), (*))
131134import Text.XML.DOM.Parser
135+import Text.XML.ParentAttributes
132136import qualified Text.XML.Writer as XW
137+import qualified Text.XML as X
133138
134139data XmlFieldPlural
135140 = XmlFieldPluralMandatory -- Occurs exactly 1 time (Identity)
@@ -137,13 +142,25 @@ data XmlFieldPlural
137142 | XmlFieldPluralRepeated -- Occurs 0 or more times (List)
138143 | XmlFieldPluralMultiplied -- Occurs 1 or more times (NonEmpty)
139144
145+data XmlAttributePlural
146+ = XmlAttributePluralMandatory -- Occurs exactly 1 time (Identity)
147+ | XmlAttributePluralOptional -- Occurs 0 or 1 times (Maybe)
148+
140149data PrefixName = PrefixName String String
141150
142151data IsoXmlDescPreField = IsoXmlDescPreField String TH.TypeQ
143152
153+data IsoXmlDescPreAttribute = IsoXmlDescPreAttribute String TH.TypeQ
154+
144155data IsoXmlDescField = IsoXmlDescField XmlFieldPlural String TH.TypeQ
145156
146−data IsoXmlDescRecord = IsoXmlDescRecord [IsoXmlDescField]
157+data IsoXmlDescAttribute = IsoXmlDescAttribute XmlAttributePlural String TH.TypeQ
158+
159+data IsoXmlDescRecordPart
160+ = IsoXmlDescRecordField IsoXmlDescField
161+ | IsoXmlDescRecordAttribute IsoXmlDescAttribute
162+
163+newtype IsoXmlDescRecord = IsoXmlDescRecord [IsoXmlDescRecordPart]
147164
148165makePrisms ''IsoXmlDescRecord
149166
@@ -165,25 +182,34 @@ appendField
165182 -> IsoXmlDescPreField
166183 -> IsoXmlDescRecord
167184appendField plural xrec (IsoXmlDescPreField name ty) =
168− let xfield = IsoXmlDescField plural name ty
185+ let xfield = IsoXmlDescRecordField $ IsoXmlDescField plural name ty
169186 in over _IsoXmlDescRecord (xfield:) xrec
170187
171−(!) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
172−(!) = appendField XmlFieldPluralMandatory
188+appendAttribute
189+ :: XmlAttributePlural
190+ -> IsoXmlDescRecord
191+ -> IsoXmlDescPreAttribute
192+ -> IsoXmlDescRecord
193+appendAttribute plural xrec (IsoXmlDescPreAttribute name ty) =
194+ let xattribute = IsoXmlDescRecordAttribute $ IsoXmlDescAttribute plural name ty
195+ in over _IsoXmlDescRecord (xattribute:) xrec
173196
174−(?) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
197+(!), (?), (*), (+) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
198+(!) = appendField XmlFieldPluralMandatory
175199(?) = appendField XmlFieldPluralOptional
176−
177−(*) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
178200(*) = appendField XmlFieldPluralRepeated
179−
180−(+) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
181201(+) = appendField XmlFieldPluralMultiplied
182202
203+(!%), (?%) :: IsoXmlDescRecord -> IsoXmlDescPreAttribute -> IsoXmlDescRecord
204+(!%) = appendAttribute XmlAttributePluralMandatory
205+(?%) = appendAttribute XmlAttributePluralOptional
206+
183207infixl 2 !
184208infixl 2 ?
185209infixl 2 *
186210infixl 2 +
211+infixl 2 !%
212+infixl 2 ?%
187213
188214appendEnumCon :: IsoXmlDescEnum -> IsoXmlDescEnumCon -> IsoXmlDescEnum
189215appendEnumCon xenum xenumcon =
@@ -201,8 +227,8 @@ infix 0 =:=
201227
202228instance Description PrefixName IsoXmlDescRecord where
203229 prefixName =:= descRecord =
204− let descFields = descRecord ^. _IsoXmlDescRecord
205− in isoXmlGenerateRecord prefixName (reverse descFields)
230+ let descRecordParts = descRecord ^. _IsoXmlDescRecord
231+ in isoXmlGenerateRecord prefixName (reverse descRecordParts)
206232
207233record :: IsoXmlDescRecord
208234record = IsoXmlDescRecord []
@@ -223,6 +249,14 @@ instance IsString IsoXmlDescPreField where
223249 where
224250 ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper name)
225251
252+instance IsString (TH.TypeQ -> IsoXmlDescPreAttribute) where
253+ fromString = IsoXmlDescPreAttribute
254+
255+instance IsString IsoXmlDescPreAttribute where
256+ fromString name = IsoXmlDescPreAttribute name ty
257+ where
258+ ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper name)
259+
226260instance s ~ String => IsString (s -> PrefixName) where
227261 fromString = PrefixName
228262
@@ -251,20 +285,30 @@ isoXmlGenerateEnum (ExhaustivenessName strName' exh) enumCons = do
251285 name = TH.mkName strName
252286 enumDecls <- enumGenerate enumDesc
253287 toXmlInst <- do
254− let toXmlExpr = [e|XW.toXML . T.pack . show|]
255288 TH.instanceD
256289 (return [])
257290 [t|XW.ToXML $(TH.conT name)|]
258− [funSimple 'XW.toXML toXmlExpr]
291+ [funSimple 'XW.toXML [e|XW.toXML . T.pack . show|]]
292+ toXmlAttributeInst <- do
293+ TH.instanceD
294+ (return [])
295+ [t|ToXmlAttribute $(TH.conT name)|]
296+ [funSimple 'toXmlAttribute [e|T.pack . show|]]
259297 fromDomInst <- do
260298 TH.instanceD
261299 (return [])
262300 [t|FromDom $(TH.conT name)|]
263− [ funSimple 'fromDom [e|parseContent readContent|] ]
264− return (enumDecls ++ [toXmlInst, fromDomInst])
301+ [funSimple 'fromDom [e|parseContent readContent|]]
302+ fromAttributeInst <- do
303+ TH.instanceD
304+ (return [])
305+ [t|FromAttribute $(TH.conT name)|]
306+ [funSimple 'fromAttribute [e|readContent|]]
307+ return $ enumDecls ++ [toXmlInst, toXmlAttributeInst,
308+ fromDomInst, fromAttributeInst]
265309
266−isoXmlGenerateRecord :: PrefixName -> [IsoXmlDescField] -> TH.DecsQ
267−isoXmlGenerateRecord (PrefixName strName' strPrefix') descFields = do
310+isoXmlGenerateRecord :: PrefixName -> [IsoXmlDescRecordPart] -> TH.DecsQ
311+isoXmlGenerateRecord (PrefixName strName' strPrefix') descRecordParts = do
268312 let
269313 strName = "Xml" ++ strName'
270314 strPrefix = "x" ++ strPrefix'
@@ -272,35 +316,62 @@ isoXmlGenerateRecord (PrefixName strName' strPrefix') descFields = do
272316 fieldName str = "_" ++ strPrefix ++ over _head C.toUpper str
273317 dataDecl <- do
274318 let
275− constructors = do
276− IsoXmlDescField fieldPlural fieldStrName fieldType <- descFields
277− let
278− fName = TH.mkName (fieldName fieldStrName)
279− fType = case fieldPlural of
280− XmlFieldPluralMandatory -> fieldType
281− XmlFieldPluralOptional -> [t| Maybe $fieldType |]
282− XmlFieldPluralRepeated -> [t| [$fieldType] |]
283− XmlFieldPluralMultiplied -> [t| NonEmpty $fieldType |]
284− return $ TH.varStrictType fName (TH.strictType TH.isStrict fType)
319+ fields = do
320+ descRecordPart <- descRecordParts
321+ return $ case descRecordPart of
322+ IsoXmlDescRecordField descField ->
323+ let
324+ IsoXmlDescField fieldPlural fieldStrName fieldType = descField
325+ fName = TH.mkName (fieldName fieldStrName)
326+ fType = case fieldPlural of
327+ XmlFieldPluralMandatory -> fieldType
328+ XmlFieldPluralOptional -> [t| Maybe $fieldType |]
329+ XmlFieldPluralRepeated -> [t| [$fieldType] |]
330+ XmlFieldPluralMultiplied -> [t| NonEmpty $fieldType |]
331+ in
332+ TH.varStrictType fName (TH.strictType TH.isStrict fType)
333+ IsoXmlDescRecordAttribute descAttribute ->
334+ let
335+ IsoXmlDescAttribute
336+ attributePlural attributeStrName attributeType = descAttribute
337+ fName = TH.mkName (fieldName attributeStrName)
338+ fType = case attributePlural of
339+ XmlAttributePluralMandatory -> attributeType
340+ XmlAttributePluralOptional -> [t| Maybe $attributeType |]
341+ in
342+ TH.varStrictType fName (TH.strictType TH.isStrict fType)
285343 TH.dataD
286344 (return [])
287345 name
288346 []
289− [TH.recC name constructors]
347+ [TH.recC name fields]
290348 [''Eq, ''Show]
291349 lensDecls <- makeFieldOpticsForDec lensRules dataDecl
292350 fromDomInst <- do
293351 let
294− exprHeader = [e|pure $(TH.conE name)|]
295− exprFields = do
296− IsoXmlDescField fieldPlural fieldStrName _ <- descFields
297− let
298− exprFieldStrName = TH.litE (TH.stringL fieldStrName)
299− fieldParse = case fieldPlural of
300− XmlFieldPluralMandatory -> [e|inElem|]
301− _ -> [e|inElemTrav|]
302− return [e|$fieldParse $exprFieldStrName fromDom|]
303− fromDomExpr = foldl (\e fe -> [e| $e <*> $fe |]) exprHeader exprFields
352+ exprHeader = [e|pure $(TH.conE name)|]
353+ exprRecordParts = do
354+ descRecordPart <- descRecordParts
355+ return $ case descRecordPart of
356+ IsoXmlDescRecordField descField ->
357+ let
358+ IsoXmlDescField fieldPlural fieldStrName _ = descField
359+ exprFieldStrName = TH.litE (TH.stringL fieldStrName)
360+ fieldParse = case fieldPlural of
361+ XmlFieldPluralMandatory -> [e|inElem|]
362+ _ -> [e|inElemTrav|]
363+ in
364+ [e|$fieldParse $exprFieldStrName fromDom|]
365+ IsoXmlDescRecordAttribute descAttribute ->
366+ let
367+ IsoXmlDescAttribute attributePlural attributeStrName _ = descAttribute
368+ exprAttributeStrName = TH.litE (TH.stringL attributeStrName)
369+ attributeParse = case attributePlural of
370+ XmlAttributePluralMandatory -> [e|parseAttribute|]
371+ XmlAttributePluralOptional -> [e|parseAttributeMaybe|]
372+ in
373+ [e|$attributeParse $exprAttributeStrName fromAttribute|]
374+ fromDomExpr = foldl (\e fe -> [e| $e <*> $fe |]) exprHeader exprRecordParts
304375 TH.instanceD
305376 (return [])
306377 [t|FromDom $(TH.conT name)|]
@@ -309,24 +380,59 @@ isoXmlGenerateRecord (PrefixName strName' strPrefix') descFields = do
309380 toXmlInst <- do
310381 objName <- TH.newName strPrefix
311382 let
312− exprFooter = [e|return ()|]
313383 exprFields = do
314− IsoXmlDescField fieldPlural fieldStrName _ <- descFields
384+ descRecordPart <- descRecordParts
385+ IsoXmlDescField fieldPlural fieldStrName _ <-
386+ maybeToList $ case descRecordPart of
387+ IsoXmlDescRecordField descField -> Just descField
388+ _ -> Nothing
315389 let
316− fieldRender = [e|XW.toXML|]
317390 fName = TH.mkName (fieldName fieldStrName)
318391 exprFieldStrName = TH.litE (TH.stringL fieldStrName)
319392 exprForField = case fieldPlural of
320393 XmlFieldPluralMandatory -> [e|id|]
321394 _ -> [e|traverse|]
322395 exprFieldValue = [e|$(TH.varE fName) $(TH.varE objName)|]
323− exprFieldRender = [e|XW.element $exprFieldStrName . $fieldRender|]
396+ exprFieldRender = [e|mkElement $exprFieldStrName|]
324397 return [e|$exprForField $exprFieldRender $exprFieldValue|]
325398 toXmlExpr
326399 = TH.lamE [if null exprFields then TH.wildP else TH.varP objName]
327− $ foldr (\fe e -> [e|$fe *> $e|]) exprFooter exprFields
400+ $ foldr (\fe e -> [e|$fe *> $e|]) [e|return ()|] exprFields
328401 TH.instanceD
329402 (return [])
330403 [t|XW.ToXML $(TH.conT name)|]
331404 [funSimple 'XW.toXML toXmlExpr]
332− return $ [dataDecl] ++ lensDecls ++ [fromDomInst, toXmlInst]
405+
406+ toXmlParentAttributesInst <- do
407+ objName <- TH.newName strPrefix
408+ let
409+ exprAttributes = do
410+ descRecordPart <- descRecordParts
411+ IsoXmlDescAttribute attributePlural attributeStrName _ <-
412+ maybeToList $ case descRecordPart of
413+ IsoXmlDescRecordAttribute descAttribute -> Just descAttribute
414+ _ -> Nothing
415+ let
416+ fName = TH.mkName (fieldName attributeStrName)
417+ exprAttrStrName = TH.litE (TH.stringL attributeStrName)
418+ exprAttrValue = [e|$(TH.varE fName) $(TH.varE objName)|]
419+ exprAttrWrap = case attributePlural of
420+ XmlAttributePluralMandatory -> [e|Just . toXmlAttribute|]
421+ XmlAttributePluralOptional -> [e|fmap toXmlAttribute|]
422+ return [e|($exprAttrStrName, $exprAttrWrap $exprAttrValue)|]
423+ toXmlParentAttributesExpr
424+ = TH.lamE [if null exprAttributes then TH.wildP else TH.varP objName]
425+ $ [e|mapMaybe distribPair $(TH.listE exprAttributes)|]
426+ TH.instanceD
427+ (return [])
428+ [t|ToXmlParentAttributes $(TH.conT name)|]
429+ [funSimple 'toXmlParentAttributes toXmlParentAttributesExpr]
430+
431+ return $ [dataDecl] ++ lensDecls ++
432+ [fromDomInst, toXmlInst, toXmlParentAttributesInst]
433+
434+mkElement :: (XW.ToXML a, ToXmlParentAttributes a) => X.Name -> a -> XW.XML
435+mkElement name a = XW.elementA name (toXmlParentAttributes a) (XW.toXML a)
436+
437+distribPair :: Functor f => (a, f b) -> f (a, b)
438+distribPair (a, fb) = (a,) <$> fb