class EL_XML_ELEMENT_LIST_EDITIONS

(source code)

description

Xml element list editions

note
	description: "Xml element list editions"

	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-09-07 12:17:51 GMT (Thursday 7th September 2023)"
	revision: "12"

class
	EL_XML_ELEMENT_LIST_EDITIONS [STORABLE_TYPE -> EL_STORABLE_XML_ELEMENT create make_default end]

create
	make

feature {NONE} -- Initialization

	make (a_target_list: like target_list; a_file_path: FILE_PATH)
			--
		do
			create crc.make
			storage_file_path := a_file_path.with_new_extension ("editions.xml")

			if storage_file_path.exists then
				set_editions_text
			else
				create editions_text.make_empty
			end

			creation_actions := creation_action_table
			target_list := a_target_list
		end

feature -- Access

	file_size_kb: REAL
		do
			Result := (storage_file.count / 1024).truncated_to_real
		end

	replace_or_extend_count: INTEGER

feature -- Element change

	put_replacement (element: STORABLE_TYPE; a_index: INTEGER)
			--
		do
			store_edition (create {like Type_replacement_edition}.make (element, a_index))
		end

	put_extension (element: STORABLE_TYPE)
			--
		do
			store_edition (create {like Type_extension_edition}.make (element))
		end

	put_removal (a_index: INTEGER)
			--
		do
			store_edition (create {like Type_removal_edition}.make (a_index))
		end

	put_blank
		do
			storage_file.put_character (' ')
		end

	set_storage_file_path (a_storage_file_path: like storage_file_path)
		do
			storage_file_path := a_storage_file_path
			storage_file.rename_file (storage_file_path)
		end

feature -- Basic operations

	apply
			-- Apply list-editions to target list
		local
			xdoc: EL_XML_DOC_CONTEXT
			edition_node_list: EL_XPATH_NODE_CONTEXT_LIST
			edition_node: EL_XPATH_NODE_CONTEXT
			template: EL_STRING_8_TEMPLATE
		do
			if not editions_text.is_empty then
				create template.make (XML_template)
				template.set_variable ("EDITIONS_LIST", editions_text)

				create xdoc.make_from_string (template.substituted)

				edition_node_list := xdoc.context_list ("/list-editions/*")
				from edition_node_list.start until edition_node_list.after loop
					edition_node := edition_node_list.context

					if creation_actions.has_key (edition_node.name) then
						if edition_node.name ~ Tag_remove then
							apply_edition (creation_actions.found_item (edition_node))

						elseif attached edition_node.find_node ("*") as found_node  then
							apply_edition (creation_actions.found_item (found_node))
						end
					end
					edition_node_list.forth
				end
				create storage_file.make_open_append (storage_file_path)
			else
				create storage_file.make_open_write (storage_file_path)
			end
			create editions_text.make_empty
		end

	reset
			--
		do
			storage_file.close; reopen
		end

	close_and_delete
			--
		do
			storage_file.close; storage_file.delete
		end

	close
			--
		do
			storage_file.close
		end

	reopen
			--
		do
			storage_file.open_write
		end

feature -- Status query

	is_empty: BOOLEAN
			--
		do
			Result := storage_file.is_empty
		end

feature {NONE} -- Implementation

	set_editions_text
			-- Set editions text ommitting any possibley corrupted editions
		local
			editions_file: PLAIN_TEXT_FILE; editions_checksum_node: EL_XML_DOC_CONTEXT
			line, edition: STRING corruption_found: BOOLEAN
		do
			create editions_file.make_open_read (storage_file_path)
			create edition.make_empty
			create editions_text.make (editions_file.count)
			from until corruption_found or editions_file.after loop
				editions_file.read_line
				line := editions_file.last_string
				if line.starts_with (Editions_checksum_beginning) then
					edition.remove_tail (1) -- remove carriage return
					crc.add_string (edition)
					create editions_checksum_node.make_from_string (line)
					if crc.checksum /= editions_checksum_node ["value"] then
						corruption_found := True
					else
						editions_text.append (edition)
						edition.wipe_out
					end
				else
					if line.substring_index (Tag_replace, 1) = 2 or else line.substring_index (Tag_extend, 1) = 2 then
						replace_or_extend_count := replace_or_extend_count + 1
					end
					edition.append (line); edition.append_character ('%N')
				end
			end
			editions_file.close
		end

	is_opening_tag (tag_name, text: STRING): BOOLEAN
		do
			if text.starts_with (once "<") and then text.substring_index (tag_name, 2) = 2 then
				Result := True
			end
		end

	is_closing_tag (tag_name, text: STRING): BOOLEAN
		do
			if text.starts_with (once "</") and then text.substring_index (tag_name, 3) = 3 then
				Result := True
			end
		end

	apply_edition (edition: like Type_edition)
			--
		do
			edition.apply (target_list)
		end

	store_edition (edition: like Type_edition)
			--
		local
			xml: STRING

		do
			xml := edition.to_xml
			xml.prune_all ('%U') -- Needed because of bug in manifest strings
			storage_file.put_string (xml); storage_file.put_new_line
			crc.add_string (xml)
			storage_file.put_string (Editions_checksum_template #$ [crc.checksum])
			storage_file.put_new_line
			storage_file.flush
		end

	extension_edition (edition_node: EL_XPATH_NODE_CONTEXT): like Type_extension_edition
			--
		do
			create Result.make (create {STORABLE_TYPE}.make_default)
			prepare_element (Result.element)
			Result.element.set_from_xpath_node (edition_node)
		end

	replacement_edition (edition_node: EL_XPATH_NODE_CONTEXT): like Type_replacement_edition
			--
		do
			create Result.make (create {STORABLE_TYPE}.make_default, edition_node ["index"])
			prepare_element (Result.element)
			Result.element.set_from_xpath_node (edition_node)
		end

	removal_edition (edition_node: EL_XPATH_NODE_CONTEXT): like Type_removal_edition
			--
		do
			create Result.make (edition_node ["index"])
		end

	prepare_element (element: STORABLE_TYPE)
			-- prepare element for setting by xpath node
		do
		end

	creation_action_table: EL_HASH_TABLE [FUNCTION [EL_XPATH_NODE_CONTEXT, like Type_edition], STRING]
			--
		do
			create Result.make (<<
				[Tag_extend, agent extension_edition],
				[Tag_remove, agent removal_edition],
				[Tag_replace, agent replacement_edition]
			>>)
			Result.compare_objects
		end

feature {NONE} -- Implementation: attributes

	storage_file_path: FILE_PATH

	storage_file: PLAIN_TEXT_FILE

	creation_actions: like creation_action_table

	target_list: LIST [STORABLE_TYPE]

	editions_text: STRING

	crc: EL_CYCLIC_REDUNDANCY_CHECK_32

feature {NONE} -- Type definitions

	Type_edition: EL_XML_ELEMENT_EDITION [STORABLE_TYPE]

	Type_extension_edition: EL_EXTENSION_EDITION [STORABLE_TYPE]

	Type_replacement_edition: EL_REPLACEMENT_EDITION [STORABLE_TYPE]

	Type_removal_edition: EL_REMOVAL_EDITION [STORABLE_TYPE]

feature {NONE} -- Constants

	XML_template: STRING =
		--

	"[
		<?xml version="1.0" encoding="iso-8859-1"?>
		<list-editions>
		$EDITIONS_LIST
		</list-editions>
	]"

	Editions_checksum_template: ZSTRING
		once
			Result := "<editions-checksum value=%"%S%"/>"
		end

	Editions_checksum_beginning: STRING
		once
			Result := Editions_checksum_template.substring (1, Editions_checksum_template.index_of ('=', 1))
		end

	Tag_remove: STRING
		once
			Result := (create {like Type_removal_edition}).tag_name
		end

	Tag_replace: STRING
		once
			Result := (create {like Type_replacement_edition}).tag_name
		end

	Tag_extend: STRING
		once
			Result := (create {like Type_extension_edition}).tag_name
		end

end