-- |Datatypes for HTML parameterized over an annotation type and a script type.
module WebBits.Html.Syntax (
  -- * HTML Data Structures
    HtmlId,AttributeValue,Attribute(..),Html(..)
  -- * The Script class
  , Script(..)
  -- * Miscellaneous Functions
  , attributeValue,attributeUpdate,attributeSet,isAttributeExpr
  ) where

import Text.ParserCombinators.Parsec(CharParser,SourcePos)
import Data.Generics(Data)
import Data.Typeable(Typeable)

--------------------------------------------------------------------------------
-- Types

type HtmlId = String
type AttributeValue = String

data Attribute a s
  = Attribute HtmlId AttributeValue a
  | AttributeExpr a HtmlId s String
  deriving (Show,Eq,Typeable,Data)

data Html a sc
  = Element HtmlId [Attribute a sc] [Html a sc] a
  | Text String a
  | Comment String a
  | HtmlSeq [Html a sc] -- ^must be a non-empty list
  | ProcessingInstruction String a
  | InlineScript sc a String
  | Script sc a
  deriving (Show,Eq,Typeable,Data)
  
--------------------------------------------------------------------------------
-- The Script class

-- |A type 't' of the 'Script' class can be parsed using 'Parsec'.  't' is of
-- kind '* -> *', as the parsed AST should be annotated with souce locations
-- (see 'Text.ParserCombinators.Parsec.SourcePos').
--
-- The big deal here is that we can embed a parser for some scripting language,
-- (e.g. Javascript) into this HTML parser with ease, while preserving source
-- locations.  The Html datatype is parameterized over a script parser (an
-- instance of Script).
class Script t where
  parseScriptBlock:: [Attribute SourcePos t] -> CharParser a t
  -- An inline script parser, which may be Nothing if the scripting language
  -- does not support inline scripts.
  parseInlineScript:: Maybe (CharParser a t)
  -- A parser for script-expressions defined inline as attribute values.
  parseAttributeScript:: Maybe (CharParser a t)
  
--------------------------------------------------------------------------------
-- HTML navigation

isAttributeExpr (AttributeExpr _ _ _ _) = True
isAttributeExpr _                       = False

-- |Returns the value of the attribute in the list, or 'Nothing' if it doesn't
-- exist of the value is an inline-expression.
attributeValue:: HtmlId -> [Attribute a s] -> Maybe String
attributeValue name [] = Nothing
attributeValue name ((AttributeExpr pos name' expr init):rest) =
  if name == name' then Nothing
                   else attributeValue name rest
attributeValue name ((Attribute name' value _):rest) =
  if name == name' then Just value
                   else attributeValue name rest

attributeSet:: HtmlId -> String -> [Attribute a s]  -> [Attribute a s]
attributeSet n v attrs = attributeUpdate n (\_ -> v) attrs

attributeUpdate:: HtmlId -> (String -> String) -> [Attribute a s] 
               -> [Attribute a s]
attributeUpdate n f [] = 
  [Attribute n (f "") (error "attributeUpdate--no value")] -- TODO: undefined?!
attributeUpdate n _ ((AttributeExpr _ _ _ _):_) =
  error $ "attributeUpdate: " ++ n ++ " is an expression-attribute."
attributeUpdate n f ((Attribute n' v p):attrs) =
  if n' == n then (Attribute n (f v) p):attrs
             else (Attribute n' v p):(attributeUpdate n f attrs)