class EL_XML_TEXT_GENERATOR

(source code)

description

Generate XML document from node scan source

note
	description: "Generate XML document from node scan source"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2023-12-11 17:04:41 GMT (Monday 11th December 2023)"
	revision: "32"

class
	EL_XML_TEXT_GENERATOR

inherit
	EL_DOCUMENT_NODE_SCANNER
		export
			{NONE} all
		redefine
			make_default, on_meta_data
		end

	EL_MODULE_LIO

	EL_CHARACTER_8_CONSTANTS; EL_STRING_8_CONSTANTS; XML_STRING_8_CONSTANTS

	EL_SHARED_STRING_8_BUFFER_SCOPES; EL_SHARED_FORMAT_FACTORY

create
	make

feature {NONE} -- Initialization

	make_default
			--
		do
			create output_stack.make (10)
			pool := Default_pool
			Precursor
		end

feature -- Basic operations

	convert_lines (lines: ITERABLE [READABLE_STRING_GENERAL]; a_output: like output)
			--
		require
			valid_output: a_output.is_open_write and a_output.is_writable
		do
			output := a_output
			do_scan (agent	scan_from_lines (lines))
		ensure
			stack_empty: output_stack.is_empty
		end

	convert_stream (a_input, a_output: IO_MEDIUM)
			--
		require
			valid_input: a_input.is_open_read and a_input.is_readable
			valid_output: a_output.is_open_write and a_output.is_writable
		do
			output := a_output
			do_scan (agent	scan_from_stream (a_input))
		ensure
			stack_empty: output_stack.is_empty
		end

	convert_text (text: STRING; a_output: like output)
			--
		require
			valid_output: a_output.is_open_write and a_output.is_writable
		do
			output := a_output
			do_scan (agent	scan (text))
		ensure
			stack_empty: output_stack.is_empty
		end

feature {NONE} -- Parsing events

	on_comment
			--
		do
			put_last_tag (True)
			output.put_string (tab * output_stack.count)
			output.put_string (Bracket.left_comment)
			if attached last_node as node then
				if node.has (New_line_character) then
					output.put_new_line
					put_content_lines (node, True)
					output.put_string (tab * output_stack.count)
				else
					put_single_line (node)
				end
			end
			output.put_string (Bracket.right_comment)
			output.put_new_line
			last_state := State_comment
		end

	on_content (node: EL_DOCUMENT_NODE_STRING)
			--
		do
			put_last_tag (False)
			put_content (node)
		end

	on_end_document
			--
		do
		end

	on_end_tag
			--
		local
			last_tag_output, tag_output: EL_STRING_8_LIST
		do
			last_tag_output := output_stack.item
			if last_tag_output.last = Bracket.slash_right then
				tag_output := last_tag_output
			else
				create tag_output.make (4)
				if last_state /= State_content then
					tag_output.extend (last_tag_output [1]) 		-- 1. Tabs
				end
				tag_output.extend (Bracket.left_slash)				-- 2.
				tag_output.extend (last_tag_output [3]) 			-- 3. Element name
				tag_output.extend (Bracket.right)					-- 4.
			end
			put_output (tag_output, True)
			output_stack.remove
			last_state := State_end_tag
		end

	on_meta_data (version: REAL; a_encoding: EL_ENCODING_BASE)
		--
		local
			xml: XML_ROUTINES
		do
			Precursor (version, a_encoding)
			if a_encoding.encoded_as_utf (8) then
				output.put_string ({UTF_CONVERTER}.Utf_8_bom_to_string_8)
			end
			output.put_string (xml.header (version, a_encoding.name))
			output.put_new_line
		end

	on_processing_instruction
			--
		do
		end

	on_start_document
			--
		do
		end

	on_start_tag
			--
		local
			tag_output: EL_STRING_8_LIST; i: INTEGER
		do
			put_last_tag (True)
			create tag_output.make (attribute_list.count + 5)

			tag_output.extend (tab * output_stack.count)
			tag_output.extend (Bracket.left)

			tag_output.extend (pool.borrowed_item)
			tag_output.last.append (last_node_name)

			if attached attribute_list.area as area and then area.count > 0 then
				from until i = area.count loop
					tag_output.extend (new_reusable_name_value_pair (area [i]))
					i := i + 1
				end
			end
			tag_output.extend (Bracket.slash_right)

			output_stack.put (tag_output)
			last_state := State_tag
		end

feature {NONE} -- Implementation

	do_scan (action: PROCEDURE)
		do
			across String_8_pool_scope as scope loop
				pool := scope
				action.apply
			end
		end

	put_content (node: EL_DOCUMENT_NODE_STRING)
			--
		do
			if node.has (New_line_character) then
				output.put_new_line
				put_content_lines (node, False)
				last_state := State_multi_line_content
			else
				put_single_line (node)
				last_state := State_content
			end
		end

	put_content_lines (node: EL_DOCUMENT_NODE_STRING; tabbed: BOOLEAN)
		local
			text: STRING
		do
			across String_8_scope as scope loop
				text := scope.item
				node.append_adjusted_to (text)

				Line_splitter.set_target (Xml_escaper.escaped (text, False))
				across Line_splitter as list loop
					if tabbed then
						output.put_string (tab * (output_stack.count + 1))
					end
					output.put_string (list.item)
					output.put_new_line
				end
			end
		end

	put_single_line (node: EL_DOCUMENT_NODE_STRING)
		do
			across String_8_scope as scope loop
				if attached scope.item as text then
					Xml_escaper.escape_into (node, text)
					output.put_string (text)
				end
			end
		end

	put_last_tag (append_new_line: BOOLEAN)
			--
		local
			last_tag_output: EL_STRING_8_LIST
		do
			if not output_stack.is_empty then
				last_tag_output := output_stack.item
				if last_tag_output.last = Bracket.slash_right then
					last_tag_output.finish
					last_tag_output.replace (Bracket.right)
					put_output (last_tag_output, append_new_line)
				end
			end
		end

	put_output (tag_output: EL_STRING_8_LIST; append_new_line: BOOLEAN)
			--
		local
			i: INTEGER
		do
			from i := 1 until i > tag_output.count loop
				output.put_string (tag_output.i_th (i))
				i := i + 1
			end
			if append_new_line then
				output.put_new_line
			end
		end

	new_reusable_name_value_pair (node: EL_DOCUMENT_NODE_STRING): STRING_8
		do
			Result := pool.borrowed_item
			Result.append_character (' ')
			Result.append (node.raw_name)
			Result.append (Value_equals_separator)
			XML_escaper.escape_into (node, Result)
			Result.append_character ('"')
		end

feature {NONE} -- Internal attributes

	last_queue_item_is_new_line: BOOLEAN

	last_queue_item_is_text: BOOLEAN

	last_state: INTEGER

	output: IO_MEDIUM

	output_stack: ARRAYED_STACK [EL_STRING_8_LIST]

	pool: like Default_pool

feature {NONE} -- States

	State_comment: INTEGER = 4

	State_content: INTEGER = 3

	State_end_tag: INTEGER = 2

	State_multi_line_content: INTEGER = 5

	State_tag: INTEGER = 1

feature {NONE} -- Constants

	Default_pool: EL_STRING_POOL_SCOPE_CURSOR [STRING]
		once
			create Result.make_default
		end

	Line_splitter: EL_SPLIT_ON_CHARACTER [STRING]
		once
			create Result.make (Empty_string_8, New_line_character)
		end

	New_line_character: CHARACTER_8 = '%N'

	Value_equals_separator: STRING = " = %""

end