%{
note

	description: 

		"XML parsers using a native Eiffel parser"

	implements: "XML 1.0 (Second Edition) - W3C Recommendation 6 October 2000"

	library: "Gobo Eiffel XML Library"
	copyright: "Copyright (c) 2002-2019, Eric Bezault and others"
	license: "MIT License"
	date: "$Date: 2020-01-08 10:24:11 +0000 (Wed, 08 Jan 2020) $"
	revision: "$Revision: 103831 $"

class XM_EIFFEL_PARSER

inherit

	XM_EIFFEL_PARSER_SKELETON

create

	make

%}

-- TODO:
-- character classes validation: external
-- pi: should have space after PI_TARGET?
-- getting error messages from scanner
-- check no external entity or DTD for standalone docs
-- xml declaration in external entities
-- DTD external id lacks base

%type <XM_EIFFEL_PARSER_NAME> namespace_name s_tag_name s_tag e_tag attribute
%type <DS_HASH_SET [XM_EIFFEL_PARSER_NAME]> s_tag_trail
%type <STRING> namespace_name_second nm_token doctype_name doctype_system_token
%type <STRING> char_data tagname_first tagname_atom name_token pi_target_token 
%type <STRING> doctype_pe_reference entityvalue_pe_reference
%type <STRING> entity_value entity_value_trail entity_value_trail_item
%type <STRING> att_value att_value_trail att_value_trail_item
%type <STRING> value_reference entity_value_reference
%type <STRING> comment_content comment_content_trail comment_content_item 
%type <STRING> pi_content pi_content_first pi_content_trail pi_content_item
%type <STRING> content_text
%type <XM_DTD_EXTERNAL_ID> external_id public_id doctype_decl_external_name
%type <STRING> ndata_decl doctype_name_space
%type <XM_DTD_ELEMENT_CONTENT> content_spec children cp choice choice_trail seq seq_trail mixed mixed_trail
%type <STRING> repetition 
%type <DS_BILINKED_LIST [XM_DTD_ATTRIBUTE_CONTENT]> attlist_decl_trail
%type <XM_DTD_ATTRIBUTE_CONTENT> att_def att_type att_tokenized_type enumerated_type default_decl
%type <DS_BILINKED_LIST [STRING]> enumeration enumeration_trail
%type <STRING> version_info encoding_decl space_eq
%type <BOOLEAN> sd_decl
%type <XM_EIFFEL_DECLARATION> xml_decl xml_decl_opt xml_decl_misc

%token <STRING> NAME NAME_UTF8
%token <STRING> NMTOKEN NMTOKEN_UTF8
%token <STRING> EQ
%token <STRING> SPACE
%token <STRING> APOS QUOT
%token <STRING> CHARDATA CHARDATA_UTF8
%token <STRING> COMMENT_START COMMENT_END COMMENT_DASHDASH
%token <STRING> PI_START PI_TARGET PI_TARGET_UTF8 PI_END PI_RESERVED
%token <STRING> XMLDECLARATION_START
%token <STRING> XMLDECLARATION_END
%token <STRING> XMLDECLARATION_VERSION
%token <STRING> XMLDECLARATION_VERSION_10
%token <STRING> XMLDECLARATION_STANDALONE
%token <STRING> XMLDECLARATION_STANDALONE_YES XMLDECLARATION_STANDALONE_NO
%token <STRING> XMLDECLARATION_ENCODING XMLDECLARATION_ENCODING_VALUE
%token <STRING> CDATA_START
%token <STRING> CDATA_END
%token <STRING> DOCTYPE_START
%token <STRING> DOCTYPE_END
%token <STRING> DOCTYPE_DECLARATION_START DOCTYPE_DECLARATION_END
%token <STRING> DOCTYPE_ELEMENT_EMPTY DOCTYPE_ELEMENT_ANY
%token <STRING> DOCTYPE_ELEMENT DOCTYPE_ATTLIST DOCTYPE_ENTITY DOCTYPE_NOTATION
%token <STRING> DOCTYPE_GROUP_START DOCTYPE_GROUP_END
%token <STRING> DOCTYPE_GROUP_OR DOCTYPE_GROUP_SEQ
%token <STRING> DOCTYPE_GROUP_ZEROONE DOCTYPE_GROUP_ANY DOCTYPE_GROUP_ONEMORE
%token <STRING> DOCTYPE_PCDATA
%token <STRING> DOCTYPE_PUBLIC
%token <STRING> DOCTYPE_SYSTEM DOCTYPE_SYSTEM_UTF8
%token <STRING> DOCTYPE_REQUIRED DOCTYPE_IMPLIED DOCTYPE_FIXED
%token <STRING> DOCTYPE_ATT_CDATA DOCTYPE_ATT_ID 
%token <STRING> DOCTYPE_ATT_IDREF DOCTYPE_ATT_IDREFS
%token <STRING> DOCTYPE_ATT_ENTITY DOCTYPE_ATT_ENTITIES 
%token <STRING> DOCTYPE_ATT_NMTOKEN DOCTYPE_ATT_NMTOKENS
%token <STRING> DOCTYPE_ATT_NOTATION
%token <STRING> DOCTYPE_PERCENT
%token <STRING> DOCTYPE_PEREFERENCE DOCTYPE_PEREFERENCE_UTF8
%token <STRING> ENTITYVALUE_PEREFERENCE ENTITYVALUE_PEREFERENCE_UTF8
%token <STRING> DOCTYPE_IGNORE DOCTYPE_INCLUDE
%token <STRING> DOCTYPE_NDATA
%token <STRING> DOCTYPE_CONDITIONAL_START
%token <STRING> DOCTYPE_CONDITIONAL_END
%token <STRING> DOCTYPE_CONDITIONAL_IGNORE
%token <STRING> VALUE_START
%token <STRING> VALUE_END
%token <STRING> TAG_START
%token <STRING> TAG_START_END
%token <STRING> TAG_END_EMPTY
%token <STRING> TAG_END
%token <STRING> TAG_NAME_FIRST TAG_NAME_FIRST_UTF8
%token <STRING> TAG_NAME_ATOM TAG_NAME_ATOM_UTF8
%token <STRING> TAG_NAME_COLON
%token <STRING> CONTENT_ENTITY CONTENT_ENTITY_UTF8 CONTENT_CONDITIONAL_END
%token <STRING> ATTRIBUTE_ENTITY ATTRIBUTE_ENTITY_UTF8 ATTRIBUTE_LT
%token <STRING> ENTITY_INVALID INPUT_INVALID

%%
-- This grammar is heavily based on the standard's EBNF productions.
-- Production names are the same as far as sensible and the productions 
-- are in the same order as in the standard text, whose section headings 
-- are included.

-- 2.1 Well formed XML documents

document: prolog element misc_maybe
	;

-- Convenience productions (out of the standard)

-- Namespace handling: to be fully compatible with XML 1.0 which allows 
-- any number of colons in names while still be ready to support namespaces,
-- namespace names (outside of DTD declarations) are handled as a list 
-- of colon-separated atoms.

namespace_name: tagname_first
		{
			$$ := new_namespace_name
			namespace_force_last ($$, $1)
		}
	| TAG_NAME_COLON
		{
			$$ := new_namespace_name
			namespace_force_last ($$, shared_empty_string)
			namespace_force_last ($$, shared_empty_string)
		}
	| TAG_NAME_COLON namespace_name_second
		{ 
			$$ := new_namespace_name
			namespace_force_last ($$, shared_empty_string)
			namespace_force_last ($$, $2)
		}
	| namespace_name TAG_NAME_COLON namespace_name_second
		{ 
			$$ := $1
			namespace_force_last ($$, $3)
		}
	| namespace_name TAG_NAME_COLON
		{
			$$ := $1
			namespace_force_last ($$, shared_empty_string)
		}
	;

namespace_name_second: tagname_atom { $$ := $1 }
	| tagname_first { $$ := $1 }
	;

	
-- Common for split UTF8/ascii tokens


char_data: CHARDATA { $$ := onstring_ascii ($1) }
	| CHARDATA_UTF8 { $$ := onstring_utf8 ($1) }
	;
	
tagname_first: TAG_NAME_FIRST { $$ := onstring_ascii ($1) }
	| TAG_NAME_FIRST_UTF8 { $$ := onstring_utf8 ($1) }
	;

tagname_atom: TAG_NAME_ATOM { $$ := onstring_ascii ($1) }
	| TAG_NAME_ATOM_UTF8 { $$ := onstring_utf8 ($1) }
	;

nm_token: doctype_name { $$ := $1 }
	| NMTOKEN { $$ := onstring_ascii ($1) }
	| NMTOKEN_UTF8 { $$ := onstring_utf8 ($1) }
	;

name_token: NAME { $$ := onstring_ascii ($1) }
	| NAME_UTF8 { $$ := onstring_utf8 ($1) }
	;

pi_target_token: PI_TARGET { $$ := onstring_ascii ($1) }
	| PI_TARGET_UTF8 { $$ := onstring_utf8 ($1) }
	;

doctype_pe_reference: DOCTYPE_PEREFERENCE { $$ := onstring_ascii ($1) }
	| DOCTYPE_PEREFERENCE_UTF8 { $$ := onstring_utf8 ($1) }
	;

entityvalue_pe_reference: ENTITYVALUE_PEREFERENCE { $$ := onstring_ascii ($1) }
	| ENTITYVALUE_PEREFERENCE_UTF8 { $$ := onstring_utf8 ($1) }
	;

doctype_system_token: DOCTYPE_SYSTEM { $$ := onstring_ascii ($1) }
	| DOCTYPE_SYSTEM_UTF8 { $$ := onstring_utf8 ($1) }
	;

-- Within the DTD name = keywords + ordinary words
-- This is not handled within the scanner because the position 
-- of a word determines whether it is a keyword or not.

doctype_name: name_token { $$ := $1 }
	| DOCTYPE_ATT_CDATA { $$ := $1 }
	| DOCTYPE_ATT_ID { $$ := $1 }
	| DOCTYPE_ATT_IDREF { $$ := $1 }
	| DOCTYPE_ATT_IDREFS { $$ := $1 }
	| DOCTYPE_ATT_ENTITY { $$ := $1 }
	| DOCTYPE_ATT_ENTITIES { $$ := $1 }
	| DOCTYPE_ATT_NMTOKEN { $$ := $1 }
	| DOCTYPE_ATT_NMTOKENS { $$ := $1 }
	| DOCTYPE_ATT_NOTATION { $$ := $1 }
	| DOCTYPE_ELEMENT_EMPTY { $$ := $1 }
	| DOCTYPE_ELEMENT_ANY { $$ := $1 }
	| DOCTYPE_IGNORE { $$ := $1 }
	| DOCTYPE_INCLUDE { $$ := $1 }
	| DOCTYPE_NDATA { $$ := $1 }
	;

maybe_space: -- Empty
	| req_space
	;
	
req_space: space_item
	| req_space space_item
	;

space_item: SPACE
	| doctype_pe_reference 
		{ 
				-- Really applies only in DTD, but token cannot appear in content
				-- and test above would catch, if unintentionally.
			if not in_external_dtd then 
				force_error (Error_doctype_peref_only_in_dtd) 
			end 
		}
	;

-- 2.3 Common syntactic constructs

entity_value: VALUE_START VALUE_END
		{ $$ := shared_empty_string }
	| VALUE_START entity_value_trail_item VALUE_END
		{ $$ := $2 }
	| VALUE_START entity_value_trail VALUE_END
		{ $$ := $2 }
	;

entity_value_trail: entity_value_trail_item entity_value_trail_item
		{ $$ := STRING_.concat ($1, $2) }
	| entity_value_trail entity_value_trail_item
		{ $$ := STRING_.appended_string ($1, $2) }
	;

entity_value_trail_item: char_data
		{ $$ := $1 }
	| entity_value_reference
		{ $$ := $1 }
	;
	
entity_value_reference: entityvalue_pe_reference
		{ $$ := entity_referenced_in_entity_value ($1) }
-- character_reference now char_data
-- bypassed entity handled by scanner
	;

att_value: VALUE_START VALUE_END
		{ $$ := shared_empty_string }
	| VALUE_START att_value_trail_item VALUE_END
		{ $$ := $2 }
	| VALUE_START att_value_trail VALUE_END
		{ $$ := $2 }
	;

att_value_trail: att_value_trail_item att_value_trail_item
		{ $$ := STRING_.concat ($1, $2) }
	| att_value_trail att_value_trail_item
		{ $$ := STRING_.appended_string ($1, $2) }
	;

att_value_trail_item: char_data { $$ := $1 }
	| value_reference { $$ := $1 }
	| ATTRIBUTE_LT 
		{
			force_error (Error_lt_not_allowed_attribute_value)
			$$ := ""
		}
	;

value_reference: ATTRIBUTE_ENTITY { $$ := shared_empty_string } -- really handled by `read_token'
	| ATTRIBUTE_ENTITY_UTF8 { $$ := shared_empty_string }
	;

-- 2.5 Comments

comment: COMMENT_START comment_content COMMENT_END
		{ on_comment ($2) }
	| COMMENT_START COMMENT_END
		{ on_comment (shared_empty_string) }
	;

dtd_comment: COMMENT_START comment_content COMMENT_END
		{ on_dtd_comment ($2) }
	| COMMENT_START COMMENT_END
		{ on_dtd_comment (shared_empty_string) }
	;

comment_content: comment_content_item
		{ $$ := $1 }
	| comment_content_trail
		{ $$ := $1 }
	;

comment_content_trail: comment_content_item comment_content_item
		{ $$ := STRING_.concat ($1, $2) }
	| comment_content_trail comment_content_item
		{ $$ := STRING_.appended_string ($1, $2) }
	;

comment_content_item: char_data { $$ := $1 }
	| COMMENT_DASHDASH
		{
			force_error (Error_no_dash_dash_in_comment)
			$$ := ""
		}
	;

-- 2.6 Processing instructions

pi: PI_START pi_target_token req_space pi_content PI_END
		{ on_processing_instruction ($2, $4) }
	| PI_START pi_target_token maybe_space PI_END
		{ on_processing_instruction ($2, shared_empty_string) }
	| PI_RESERVED { force_error (Error_pi_xml_reserved) }
	;

dtd_pi: PI_START pi_target_token req_space pi_content PI_END
		{ on_dtd_processing_instruction ($2, $4) }
	| PI_START pi_target_token maybe_space PI_END
		{ on_dtd_processing_instruction ($2, shared_empty_string) }
	| PI_RESERVED { force_error (Error_pi_xml_reserved) }
	;

pi_content: pi_content_first pi_content_item
		{ $$ := STRING_.concat ($1, $2) }
	| pi_content_first pi_content_trail
		{ $$ := STRING_.concat ($1, $2) }
	| pi_content_first
		{ $$ := $1 }
	;
	
pi_content_trail: pi_content_item pi_content_item
		{ $$ := STRING_.concat ($1, $2) }
	| pi_content_trail pi_content_item
		{ $$ := STRING_.appended_string ($1, $2) }
	;

pi_content_item: char_data { $$ := $1 }
	| pi_target_token { $$ := $1 }
	| SPACE { $$ := $1 }
	;

pi_content_first: char_data { $$ := $1 }
	| pi_target_token { $$ := $1 }
	;

-- 2.7 CDATA section

cd_sect: CDATA_START CDATA_END
	| CDATA_START cdata_body CDATA_END
	;

cdata_body: cdata_body_item
	| cdata_body cdata_body_item
	;

cdata_body_item: char_data
		{ on_content ($1) }
	;

-- 2.8 Prolog and DTD

prolog: xml_decl_misc doctype_decl_misc
		{
			apply_encoding ($1.encoding)
			$1.process (Current) -- event
		}
	;

xml_decl_misc: misc_maybe
		{ create $$.make }
	| xml_decl
		{ $$ := $1 }
	| xml_decl misc_trail
		{ $$ := $1 }
	;

doctype_decl_misc: -- Empty
	| doctype_decl
	| doctype_decl misc_trail
	;

misc_maybe: -- Empty
	| misc_trail
	;

misc_trail: misc
	| misc_trail misc
	;

xml_decl: XMLDECLARATION_START version_info xml_decl_opt XMLDECLARATION_END
		{ 
			$3.set_version ($2)
			$$ := $3
		}
	| XMLDECLARATION_START error
		{
			force_error (Error_xml_declaration)
			create $$.make
		}
	;

xml_decl_opt: maybe_space
		{ create $$.make }
	| req_space sd_decl maybe_space
		{ 
			create $$.make 
			$$.set_stand_alone ($2)
		}
	| req_space encoding_decl maybe_space
		{ 
			create $$.make 
			$$.set_encoding ($2)
			apply_encoding ($2)
		}
	| req_space encoding_decl req_space sd_decl maybe_space
		{ 
			create $$.make;
			$$.set_encoding ($2)
			$$.set_stand_alone ($4) 
			apply_encoding ($2)
		}
	;

space_eq: EQ
		{ $$ := $1 }
	| SPACE EQ
		{ $$ := $2 }
	| EQ SPACE
		{ $$ := $1 }
	| SPACE EQ SPACE
		{ $$ := $2 }
	;
	
version_info: XMLDECLARATION_VERSION space_eq APOS XMLDECLARATION_VERSION_10 APOS
		{ $$ := $4 }
	| XMLDECLARATION_VERSION space_eq QUOT XMLDECLARATION_VERSION_10 QUOT
		{ $$ := $4 }
	;

misc: comment
	| pi
	| SPACE
	;

doctype_decl: doctype_decl_internal
	| doctype_decl_external doctype_decl_dtd
	;

doctype_decl_internal_name: doctype_name maybe_space 
		{ on_doctype ($1, Void, True) }
	;
	
doctype_decl_internal: DOCTYPE_START req_space doctype_decl_internal_name doctype_decl_declaration DOCTYPE_END
		{ on_dtd_end }
	;

doctype_decl_external_name: doctype_name req_space external_id maybe_space
		{ 
			on_doctype ($1, $3, False) 
			$$ := $3
		}
	;
	
doctype_decl_external: DOCTYPE_START req_space doctype_decl_external_name doctype_decl_declaration DOCTYPE_END
		{
			debug ("xml_parser")
				std.error.put_string ("[dtd: in]")
			end
			in_external_dtd := True
			when_external_dtd ($3) 
			on_dtd_end
		}
	;

-- External DTD

doctype_decl_dtd: DOCTYPE_DECLARATION_START text_decl doctype_decl_dtd_content DOCTYPE_DECLARATION_END
		{
			if not in_external_dtd then
				force_error (Error_misplaced_dtd_declaration)
			end
			debug ("xml_parser")
				std.error.put_string ("[dtd: out]")
			end
			in_external_dtd := False
		}
	;

doctype_decl_dtd_content: -- Empty
	| doctype_decl_dtd_items
	;

doctype_decl_dtd_items: doctype_decl_dtd_item
	| doctype_decl_dtd_items doctype_decl_dtd_item
	;

doctype_decl_dtd_item: markup_decl
	| conditional_sect
	;

-- Internal DTD

doctype_decl_declaration: -- Empty
	| DOCTYPE_DECLARATION_START DOCTYPE_DECLARATION_END maybe_space
	| DOCTYPE_DECLARATION_START doctype_decl_declaration_content DOCTYPE_DECLARATION_END maybe_space
	;

doctype_decl_declaration_content: markup_decl
	| doctype_decl_declaration_content markup_decl
	;

-- DTD body

markup_decl: element_decl
	| attlist_decl
	| entity_decl
	| notation_decl
	| dtd_pi
	| dtd_comment
	| SPACE -- not in XML1.0:29?
	| doctype_pe_reference
	;

-- 2.9 Stand-alone document declaration

sd_decl: XMLDECLARATION_STANDALONE space_eq APOS XMLDECLARATION_STANDALONE_YES APOS { $$ := True }
	| XMLDECLARATION_STANDALONE space_eq QUOT XMLDECLARATION_STANDALONE_YES QUOT { $$ := True }
	| XMLDECLARATION_STANDALONE space_eq APOS XMLDECLARATION_STANDALONE_NO APOS { $$ := False }
	| XMLDECLARATION_STANDALONE space_eq QUOT XMLDECLARATION_STANDALONE_NO QUOT { $$ := False }
	;

-- 3. Element

element: empty_elem_tag
	| s_tag e_tag 
		{
			if not $1.is_equal ($2) then
				force_error (Error_end_tag_mismatch)
			end
		}
	| s_tag content e_tag
		{
			if not $1.is_equal ($3) then
				force_error (Error_end_tag_mismatch)
			end
		}
	| s_tag content error { force_error (Error_element_end_tag) }
	| s_tag error { force_error (Error_element_content) }
	;


-- 3.1 Element tags

s_tag: TAG_START s_tag_name TAG_END
		{
			$$ := $2
			on_start_tag_finish
		}
	| TAG_START s_tag_name req_space s_tag_trail TAG_END
		{
			$$ := $2
			on_start_tag_finish
		}
	| TAG_START error
		{
			force_error (Error_start_tag)
			create $$.make_no_namespaces
		}
	;

empty_elem_tag: TAG_START s_tag_name TAG_END_EMPTY
		{
			on_start_tag_finish -- makes empty attributes
			on_end_tag (Void, $2.ns_prefix, $2.local_part)
		}
	| TAG_START s_tag_name req_space s_tag_trail TAG_END_EMPTY
		{
			on_start_tag_finish
			on_end_tag (Void, $2.ns_prefix, $2.local_part)
		}
	;

s_tag_name: namespace_name
		{
			$$ := $1
			on_start_tag (Void, $1.ns_prefix, $1.local_part)
		}
	;

s_tag_trail: attribute
		{
			$$ := new_name_set
			$$.force_new ($1)
		}
	| s_tag_trail req_space attribute
		{
			$$ := $1
			if ($$).has ($3) then
				force_error (Error_attribute_duplicate)
			else
				$$.force_new ($3)
			end
		}
	;

attribute: namespace_name EQ att_value
		{
			$$ := $1
			on_attribute (Void, $1.ns_prefix, $1.local_part, $3)
		}
	| namespace_name error 
		{
			force_error (Error_attribute)
			create $$.make_no_namespaces
		}
	;

e_tag: TAG_START_END namespace_name TAG_END
		{
			$$ := $2
			on_end_tag (Void, $2.ns_prefix, $2.local_part)
		}
	| TAG_START_END error
		{
			force_error (Error_end_tag)
			create $$.make_no_namespaces
		}
	;

content: content_item
	| content content_item
	;

content_item: content_text { on_content ($1) }
	| cd_sect
	| element
	| pi
	| comment
	| CONTENT_CONDITIONAL_END { force_error (Error_no_cdata_end_in_content) }
	| entity_in_content
	;

entity_in_content: CONTENT_ENTITY -- no action, switched in read_token
	| CONTENT_ENTITY_UTF8 -- no action, switched in read_token
	| XMLDECLARATION_END { force_error (Error_entity_xml_declaration) }
	;

content_text: char_data -- includes character_reference
		{ $$ := $1 }
	| SPACE 
		{ $$ := $1 }
	;

-- 3.2 Element type declaration

element_decl: DOCTYPE_ELEMENT req_space doctype_name req_space content_spec DOCTYPE_END
		{ on_element_declaration ($3,$5) }
	| DOCTYPE_ELEMENT error { force_error (Error_doctype_element) }
	;

content_spec: DOCTYPE_ELEMENT_EMPTY maybe_space
		{ create $$.make_empty }
	| DOCTYPE_ELEMENT_ANY maybe_space
		{ create $$.make_any }
	| mixed maybe_space
		{ $$ := $1 }
	| children
		{ $$ := $1 }
	;

children: choice maybe_space
		{ $$ := $1 }
	| choice repetition
		{ $$ := $1; set_element_repetition ($$, $2) }
	| seq maybe_space
		{ $$ := $1 }
	| seq repetition
		{ $$ := $1; set_element_repetition ($$, $2) }
	;

cp: doctype_name_space
		{ $$ := element_name ($1) }
	| doctype_name repetition
		{ $$ := element_name ($1); set_element_repetition ($$, $2) }
	| choice maybe_space
		{ $$ := $1 }
	| choice repetition
		{ $$ := $1; set_element_repetition ($$, $2) }
	| seq maybe_space
		{ $$ := $1 }
	| seq repetition
		{ $$ := $1; set_element_repetition ($$, $2) }
	;

repetition: DOCTYPE_GROUP_ANY maybe_space
		{ $$ := Zero_or_more_repetition }
	| DOCTYPE_GROUP_ONEMORE maybe_space
		{ $$ := One_or_more_repetition }
	| DOCTYPE_GROUP_ZEROONE maybe_space
		{ $$ := Zero_or_one_repetition }
	;

choice: group_start cp group_or choice_trail group_end
		{
			$$ := $4
			check attached $$.items as l_items then
				l_items.force_first ($2)
			end
		}
	;-- ensure choice.is_choice (that's why $4 is used)

choice_trail: cp
		{
			create $$.make_choice
			check attached $$.items as l_items then
				l_items.force_last ($1)
			end
		}
	| choice_trail group_or cp
		{
			$$ := $1
			check attached $$.items as l_items then
				l_items.force_last ($3)
			end
		}
	; -- ensure choice_trail.is_choice


seq: group_start seq_trail group_end
		{ $$ := $2 }
	;

seq_trail: cp
		{
			create $$.make_sequence
			check attached $$.items as l_items then
				l_items.force_last ($1)
			end
		}
	| seq_trail group_seq cp
		{
			$$ := $1
			check attached $$.items as l_items then
				l_items.force_last ($3)
			end
		}
	; -- ensure seq_trail.is_sequence

mixed: group_start pc_data group_end
		{ create $$.make_mixed }
	| group_start pc_data group_end DOCTYPE_GROUP_ANY
		{ create $$.make_mixed; $$.set_zero_or_more } 
	| group_start pc_data group_or mixed_trail group_end DOCTYPE_GROUP_ANY
		{ $$ := $4; $$.set_zero_or_more }
	; -- ensure mixed.is_content_mixed

mixed_trail: doctype_name_space
		{ 
			create $$.make_mixed
			check attached $$.items as l_items then
				l_items.force_last (element_name ($1))
			end
		}
	| mixed_trail group_or doctype_name_space
		{ 
			$$ := $1
			check attached $$.items as l_items then
				l_items.force_last (element_name ($3))
			end
		}
	; -- ensure mixed_trail.is_content_mixed

-- Primitives with space

doctype_name_space: DOCTYPE_NAME maybe_space
		{ $$ := $1 }
	;

group_start: DOCTYPE_GROUP_START maybe_space
	;

group_or: DOCTYPE_GROUP_OR maybe_space
	;

group_seq: DOCTYPE_GROUP_SEQ maybe_space
	;

group_end: DOCTYPE_GROUP_END
	;

pc_data: DOCTYPE_PCDATA maybe_space
	;

-- 3.3 Attribute list declaration

attlist_decl: DOCTYPE_ATTLIST req_space doctype_name maybe_space DOCTYPE_END
		-- empty declaration, what to do?
	| DOCTYPE_ATTLIST req_space doctype_name attlist_decl_trail maybe_space DOCTYPE_END
		{  on_attribute_declarations ($3, $4) }
	| DOCTYPE_ATTLIST error 
		{ force_error (Error_doctype_attribute) }
	;

attlist_decl_trail: att_def
		{ $$ := new_dtd_attribute_content_list; $$.force_last ($1) }
	|  attlist_decl_trail att_def
		{ $$ := $1; $$.force_last ($2) }
	;

att_def: req_space doctype_name req_space att_type req_space default_decl
		{ $$ := $4; $$.set_name ($2); $$.copy_default ($6) }
	| req_space doctype_name error
		{
			force_error (Error_doctype_attribute_item)
			create $$.make
		}
	;

att_type: DOCTYPE_ATT_CDATA -- string_type
		{ $$ := new_dtd_attribute_content; $$.set_data }
	| att_tokenized_type
		{ $$ := $1 }
	| enumerated_type
		{ $$ := $1 }
	;

att_tokenized_type: DOCTYPE_ATT_ID
		{ $$ := new_dtd_attribute_content; $$.set_id }
	| DOCTYPE_ATT_IDREF
		{ $$ := new_dtd_attribute_content; $$.set_id_ref }
	| DOCTYPE_ATT_IDREFS
		{ $$ := new_dtd_attribute_content; $$.set_id_ref; $$.set_list_type }
	| DOCTYPE_ATT_ENTITY
		{ $$ := new_dtd_attribute_content; $$.set_entity }
	| DOCTYPE_ATT_ENTITIES
		{ $$ := new_dtd_attribute_content; $$.set_entity; $$.set_list_type }
	| DOCTYPE_ATT_NMTOKEN
		{ $$ := new_dtd_attribute_content; $$.set_token }
	| DOCTYPE_ATT_NMTOKENS
		{ $$ := new_dtd_attribute_content; $$.set_token; $$.set_list_type }
	;

enumerated_type: notation_type
		{ $$ := new_dtd_attribute_content; $$.set_notation }
	| enumeration
		{ $$ := new_dtd_attribute_content; $$.set_enumeration_list ($1) }
	;

notation_type: DOCTYPE_ATT_NOTATION req_space group_start notation_type_trail DOCTYPE_GROUP_END
	;

notation_type_trail: doctype_name_space
	| notation_type_trail group_or doctype_name_space
	;

enumeration: group_start enumeration_trail group_end
		{ $$ := $2 }
	;

enumeration_trail: nm_token maybe_space
		{ $$ := new_string_bilinked_list; $$.force_last ($1) }
	| enumeration_trail group_or nm_token maybe_space
		{ $$ := $1; $$.force_last ($3) }
	;

default_decl: DOCTYPE_REQUIRED
		{ $$ := new_dtd_attribute_content; $$.set_value_required }
	| DOCTYPE_IMPLIED
		{ $$ := new_dtd_attribute_content; $$.set_value_implied }
	| DOCTYPE_FIXED req_space att_value
		{ $$ := new_dtd_attribute_content; $$.set_value_fixed ($3) }
	| att_value
		{ $$ := new_dtd_attribute_content; $$.set_default_value ($1) }
	;

-- 3.4 Conditional section

conditional_sect: include_sect
	| ignore_sect
	| DOCTYPE_CONDITIONAL_START error { force_error (Error_doctype_conditional_section) }
	;

include_sect: include_header DOCTYPE_CONDITIONAL_END
	| include_header doctype_decl_dtd_items DOCTYPE_CONDITIONAL_END
	;

include_header: DOCTYPE_CONDITIONAL_START maybe_space DOCTYPE_INCLUDE maybe_space DOCTYPE_DECLARATION_START
	;

ignore_sect: ignore_header ignore_sect_content DOCTYPE_CONDITIONAL_END
	;

ignore_header: DOCTYPE_CONDITIONAL_START maybe_space DOCTYPE_IGNORE maybe_space DOCTYPE_DECLARATION_START
		{ scanner.push_start_condition_dtd_ignore }
	;

ignore_sect_content: -- Empty
	| ignore_sect_items
	;

ignore_sect_items: ignore_sect_item
	| ignore_sect_items ignore_sect_item
	;

ignore_sect_item: char_data
	| DOCTYPE_CONDITIONAL_START ignore_sect_content DOCTYPE_CONDITIONAL_END
	;

-- 4.1 Reference

-- reference: ENTITY is used directly depending on context

-- 4.2 Entity declarations

entity_decl: ge_decl 
	| pe_decl
	| DOCTYPE_ENTITY error { force_error (Error_doctype_entity) }
	;

ge_decl: DOCTYPE_ENTITY req_space doctype_name req_space entity_value maybe_space DOCTYPE_END
		{
				-- Internal entity.
			when_entity_declared ($3, new_literal_entity ($3, $5))
			on_entity_declaration ($3, False, $5, Void, Void)
		}
	| DOCTYPE_ENTITY req_space doctype_name req_space external_id maybe_space DOCTYPE_END
		{
				-- External entity.
			when_entity_declared ($3, new_external_entity ($5))
			on_entity_declaration ($3, False, Void, $5, Void)
		}
	| DOCTYPE_ENTITY req_space doctype_name req_space external_id ndata_decl maybe_space DOCTYPE_END
		{
				-- Unparsed NDATA entity.
			on_entity_declaration ($3, False, Void, $5, $6)
		}
	;

pe_decl: DOCTYPE_ENTITY req_space DOCTYPE_PERCENT req_space doctype_name req_space entity_value maybe_space DOCTYPE_END
		{
				-- Internal PE entity.
			when_pe_entity_declared ($5, new_literal_entity ($5, $7))
			on_entity_declaration ($5, True, $7, Void, Void) 
		}
	| DOCTYPE_ENTITY req_space DOCTYPE_PERCENT req_space doctype_name req_space external_id maybe_space DOCTYPE_END
		{
				-- External PE entity.
			when_pe_entity_declared ($5, new_external_entity ($7)) 
			on_entity_declaration ($5, True, Void, $7, Void)
		}
	;

external_id: doctype_system_token
		{ $$ := new_dtd_external_id; $$.set_system ($1) }
	| DOCTYPE_PUBLIC doctype_system_token
		{ $$ := new_dtd_external_id; $$.set_public ($1); $$.set_system ($2) }
	;

ndata_decl: req_space DOCTYPE_NDATA req_space doctype_name -- $2 is 'NDATA'
	{ $$ := $4 }
	;

-- 4.3 Parsed entities

text_decl: -- Empty
	| XMLDECLARATION_START encoding_decl maybe_space XMLDECLARATION_END
	| XMLDECLARATION_START version_info req_space encoding_decl maybe_space XMLDECLARATION_END
	| XMLDECLARATION_START error { force_error (Error_xml_declaration) }
	;

encoding_decl: XMLDECLARATION_ENCODING space_eq APOS XMLDECLARATION_ENCODING_VALUE APOS
		{ $$ := $4 }
	| XMLDECLARATION_ENCODING space_eq QUOT XMLDECLARATION_ENCODING_VALUE QUOT
		{ $$ := $4 }
	;

-- 4.7 Notation declaration

notation_decl: DOCTYPE_NOTATION req_space doctype_name req_space external_id maybe_space DOCTYPE_END
		{ on_notation_declaration ($3, $5) }
	| DOCTYPE_NOTATION req_space doctype_name req_space public_id maybe_space DOCTYPE_END
		{ on_notation_declaration ($3, $5) }
	| DOCTYPE_NOTATION error
		{ force_error (Error_doctype_notation) }
	;

public_id: DOCTYPE_PUBLIC
		{ $$ := new_dtd_external_id; $$.set_system ($1) }
	;

%%

end
