class EL_TRANSFORMABLE_ZSTRING

(source code)

description

Implementation routines to transform instance of ZSTRING

note
	description: "Implementation routines to transform instance of ${ZSTRING}"

	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: "2024-11-05 13:20:02 GMT (Tuesday 5th November 2024)"
	revision: "76"

deferred class
	EL_TRANSFORMABLE_ZSTRING

inherit
	EL_ZSTRING_IMPLEMENTATION

	EL_APPENDABLE_ZSTRING

	EL_PREPENDABLE_ZSTRING

	EL_CHARACTER_32_CONSTANTS

feature {EL_READABLE_ZSTRING} -- Basic operations

	crop (left_delimiter, right_delimiter: CHARACTER_32)
		do
			current_readable.copy (current_readable.cropped (left_delimiter, right_delimiter))
		end

	enclose (left, right: CHARACTER_32)
		do
			current_readable.copy (current_readable.enclosed (left, right))
		end

	fill_blank
		-- Fill with `count' blank characters.
		do
			fill_character (' ')
		end

	fill_character (uc: CHARACTER_32)
			-- Fill with `capacity' characters all equal to `uc'.
		local
			c: CHARACTER
		do
			c := encoded_character (uc)
			internal_fill_character (c)
			if c = Substitute then
				make_unencoded_filled (uc, count)
			end
		end

	hide (characters: READABLE_STRING_GENERAL)
		-- hide all occurrences of `characters' with control characters `0x1' to `0x8' and `0x11' to `0x18'
		-- so they are not affected by an escaping routine
		do
			hide_or_reveal (characters, False)
		end

	mirror
		-- Reverse the order of characters.
		-- "Hello" -> "olleH".
		local
			c_i: CHARACTER; i, l_count, block_index, last_upper: INTEGER
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			l_count := count
			if l_count > 1 then
				if attached unencoded_area as area_32 and then area_32.count > 0
					and then attached empty_unencoded_buffer as buffer
					and then attached area as l_area
				then
					last_upper := buffer.last_upper
					from i := l_count - 1 until i < 0 loop
						c_i := l_area.item (i)
						if c_i = Substitute then
							last_upper := buffer.extend (iter.item ($block_index, area_32, i + 1), last_upper, l_count - i)
						end
						i := i - 1
					end
					String_8.mirror (Current)
					buffer.set_last_upper (last_upper)
					set_unencoded_from_buffer (buffer)
				else
					String_8.mirror (Current)
				end
			end
		ensure
			same_count: count = old count
			valid_string: is_valid
			reversed: across 1 |..| count as n all item (n.item) = (old twin) [count - n.item + 1] end
		end

	multiply (n: INTEGER)
			-- Duplicate a string within itself
			-- ("hello").multiply(3) => "hellohellohello"
		require
			meaningful_multiplier: n >= 1
		do
			current_readable.copy (current_readable.multiplied (n))
		end

	quote (type: INTEGER)
		require
			type_is_single_double_or_appropriate: 1 <= type and type <= 3
		do
			current_readable.copy (current_readable.quoted (type))
		end

	to_canonically_spaced
		-- adjust string so that `is_canonically_spaced' becomes true
		local
			c_i: CHARACTER; uc_i: CHARACTER_32; i, j, l_count, block_index, space_count, last_upper: INTEGER
			is_space: BOOLEAN; c: EL_CHARACTER_32_ROUTINES
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			if not is_canonically_spaced
				and then attached unencoded_area as area_32 and then attached area as l_area
				and then attached empty_unencoded_buffer as buffer
			then
				last_upper := buffer.last_upper
				l_count := count
				from i := 0; j := 0 until i = l_count loop
					c_i := l_area [i]
					if c_i = Substitute then
						uc_i := iter.item ($block_index, area_32, i + 1)
						 -- `c.is_space' is workaround for finalization bug
						is_space := c.is_space (uc_i)
					else
						is_space := c_i.is_space
					end
					if is_space then
						space_count := space_count + 1
					else
						space_count := 0
					end
					inspect space_count
						when 0 then
							l_area [j] := c_i
							if c_i = Substitute then
								last_upper := buffer.extend (uc_i, last_upper, j + 1)
							end
							j := j + 1

						when 1 then
							l_area [j] := ' '
							j := j + 1
					else
					end
					i := i + 1
				end
				set_count (j)
				buffer.set_last_upper (last_upper)
				set_unencoded_from_buffer (buffer)
				if l_count > 50 and then ((i - j) * 100.0 / l_count).rounded > 15 then
					trim -- reallocate to new size
				end
			end
		ensure
			canonically_spaced: is_canonically_spaced
		end

	to_lower
		-- Convert to lower case.
		do
			to_lower_area (area, 0, count - 1)
			reset_hash
		ensure
			length_and_content: elks_checking implies Current ~ (old as_lower)
		end

	to_proper
		do
			to_proper_area (area, 0, count - 1)
			reset_hash
		end

	to_upper
		-- Convert to upper case.
		do
			to_upper_area (area, 0, count - 1)
			reset_hash
		ensure
			length_and_content: elks_checking implies Current ~ (old as_upper)
		end

	reveal (characters: READABLE_STRING_GENERAL)
		-- reveal set of `characters' previously hidden by call to `hide'
		do
			hide_or_reveal (characters, True)
		end

	translate (old_characters, new_characters: READABLE_STRING_GENERAL)
		-- replace all characters in `old_characters' with corresponding character in `new_characters'.
		do
			translate_with_deletion (old_characters, new_characters, False)
		end

	translate_or_delete (old_characters, new_characters: READABLE_STRING_GENERAL)
		-- replace all characters in `old_characters' with corresponding character in `new_characters'
		-- and removing any characters corresponding to null value '%U'
		do
			translate_with_deletion (old_characters, new_characters, True)
		end

	unescape (unescaper: EL_ZSTRING_UNESCAPER)
		do
			make_from_zcode_area (unescaper.unescaped_array (current_readable))
		end

feature {EL_READABLE_ZSTRING} -- Replacement

	expand_tabs (space_count: INTEGER)
		-- replace tabs with spaces
		do
			replace_substring_all (tab, space * space_count)
		end

	replace_character (uc_old, uc_new: CHARACTER_32)
		local
			c_old, c_new: CHARACTER; new_unencoded: CHARACTER_32; i, l_count, block_index: INTEGER
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			l_count := count
			c_old := encoded_character (uc_old); c_new := encoded_character (uc_new)
			if c_new = Substitute then
				new_unencoded := uc_new
			end
			if attached area as l_area and then attached unencoded_area as area_32 then
				if c_old = Substitute then
					if area_32.count > 0 then
						from i := 0 until i = l_count loop
							inspect l_area [i]
								when Substitute then
									if uc_old = iter.item ($block_index, area_32, i + 1) then
										l_area [i] := c_new
									end
							else
							end
							i := i + 1
						end
						replace_unencoded_character (uc_old, new_unencoded, False)
					end
				else
					from i := 0 until i = l_count loop
						if l_area [i] = c_old then
							l_area [i] := c_new
							inspect c_new
								when Substitute then
									put_unencoded (new_unencoded, i + 1)
							else
							end
						end
						i := i + 1
					end
				end
			end
			reset_hash
		ensure
			valid_unencoded: is_valid
			replaced: uc_old /= uc_new implies occurrences (uc_new) = old (occurrences (uc_old) + occurrences (uc_new))
		end

	replace_delimited_substring (left, right, new: EL_READABLE_ZSTRING; include_delimiter: BOOLEAN; start_index: INTEGER)
			-- Searching from start_index, replaces text delimited by left and right with 'new'
			-- Text replaced includeds delimiter if 'include_delimiter' is true
		local
			pos_left, pos_right, start_pos, end_pos: INTEGER
		do
			pos_left := substring_index (left, start_index)
			if pos_left > 0 then
				start_pos := pos_left + left.count
				pos_right := substring_index (right, start_pos)
				if pos_right > 0 then
					end_pos := pos_right - 1
					if include_delimiter then
						start_pos := start_pos - left.count
						end_pos := end_pos + right.count
					end
					replace_substring (new, start_pos, end_pos)
				end
			end
		end

	replace_delimited_substring_general (
		left, right, new: READABLE_STRING_GENERAL; include_delimiter: BOOLEAN; start_index: INTEGER
	)
		do
			replace_delimited_substring (
				adapted_argument (left, 1), adapted_argument (right, 2),
				adapted_argument (new, 3), include_delimiter, start_index
			)
		end

	replace_set_members (set: EL_SET [CHARACTER_32]; uc_new: CHARACTER_32)
		-- Replace all encoded characters that are member of `set' with the `uc_new' character
		local
			i, l_count, block_index: INTEGER; c_i, c_new: CHARACTER_8; uc_i: CHARACTER_32
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			l_count := count; c_new := encoded_character (uc_new)
			if attached unicode_table as l_unicode_table and then attached area as l_area
				and then attached unencoded_area as area_32
			then
				from i := 0 until i = l_count loop
					c_i := l_area [i]
					inspect character_8_band (c_i)
						when Substitute then
							uc_i:= iter.item ($block_index, area_32, i + 1)

						when Ascii_range then
							uc_i := c_i
					else
						uc_i := l_unicode_table [c_i.code]
					end
					if set.has (uc_i) then
						l_area [i] := c_new
						inspect c_new
							when Substitute then
								put (uc_new, i + 1)
						else
						end
					end
					i := i + 1
				end
			end
			reset_hash
		ensure
			valid_unencoded: is_valid
		end

	replace_set_members_8 (set: EL_SET [CHARACTER_8]; new: CHARACTER_8)
		-- Replace all encoded characters that are member of `set' with the `new' character
		-- useful only if `set' consists of ASCII characters or characters which match the current
		-- `Codec' character set
		require
			substitute_reserved: not set.has (Substitute) and new /= Substitute
		local
			i, l_count: INTEGER; c_i: CHARACTER_8
		do
			l_count := count
			if attached area as l_area then
				from i := 0 until i = l_count loop
					c_i := l_area [i]
					inspect c_i
						when Substitute then
							do_nothing -- unencoded character
					else
						if set.has (c_i) then
							l_area [i] := new
						end
					end
					i := i + 1
				end
			end
			reset_hash
		ensure
			valid_unencoded: is_valid
		end

	replace_substring (s: EL_READABLE_ZSTRING; start_index, end_index: INTEGER)
		local
			buffer: like Unencoded_buffer; l_count, old_count: INTEGER
		do
			old_count := count
			String_8.replace_substring (Current, s, start_index, end_index)
			inspect respective_encoding (s)
				when Both_have_mixed_encoding then
					l_count := start_index - 1
					buffer := empty_unencoded_buffer
					if has_unencoded_between (start_index, end_index) then
						if l_count.to_boolean then
							buffer.append_substring (Current, 1, start_index - 1, 0)
						end
						if s.count.to_boolean then
							buffer.append (s, l_count)
						end
						if end_index < old_count then
							buffer.append_substring (Current, end_index + 1, old_count, l_count + s.count)
						end
						set_unencoded_from_buffer (buffer)
					else
						shift_unencoded_from (start_index, s.count - (end_index - start_index + 1))
						buffer.append (s, l_count)
						insert_unencoded (buffer)
--						buffer.set_in_use (False)
					end

				when Only_current then
					remove_unencoded_substring (start_index, end_index)
					shift_unencoded_from (start_index, s.count)

				when Only_other then
					set_unencoded_area (s.unencoded_area.twin)
					shift_unencoded (start_index - 1)
			else
			end
		ensure
			new_count: count = old count - (end_index - start_index + 1) + old s.count
			replaced: elks_checking implies
				(current_readable ~ (old (substring (1, start_index - 1) + s + substring (end_index + 1, count))))
			valid_unencoded: is_valid
		end

	replace_substring_all (old_substring, new_substring: READABLE_STRING_GENERAL)
		local
			old_, new: ZSTRING
		do
			if old_substring.count > 0 then
				old_ := adapted_argument (old_substring, 1)
				new := adapted_argument (new_substring, 2)
				if old_ /~ new then
					if respective_encoding (new) = Neither then
						String_8.replace_substring_all (Current, old_, new)
					else
						replace_area_substrings (old_, new)
					end
				end
			end
		end

	replace_substring_general (s: READABLE_STRING_GENERAL; start_index, end_index: INTEGER)
		do
			replace_substring (adapted_argument (s, 1), start_index, end_index)
		end

feature {EL_READABLE_ZSTRING} -- Removal

	keep_head (n: INTEGER)
		-- Remove all characters except for the first `n';
		-- do nothing if `n' >= `count'.
		local
			l_count: INTEGER
		do
			if has_mixed_encoding then
				l_count := count
				if has_substitutes_between (area, n + 1, l_count) then
					remove_unencoded_substring (n + 1, l_count)
				end
			end
			internal_keep_head (n)
		ensure then
			valid_unencoded: is_valid
		end

	keep_tail (n: INTEGER)
			-- Remove all characters except for the last `n';
			-- do nothing if `n' >= `count'.
		local
			leading_count: INTEGER
		do
			leading_count := count - n
			internal_keep_tail (n)
			if has_mixed_encoding then
				if count = 0 then
					make_unencoded
				else
					if has_unencoded_between (1, leading_count) then
						remove_unencoded_substring (1, leading_count)

					elseif has_unencoded_between (leading_count + 1, leading_count + n) then
						shift_unencoded (leading_count.opposite)
					end
				end
			end
		ensure then
			valid_unencoded: is_valid
		end

	left_adjust
		-- Remove leading whitespace.
		local
			space_count, old_count: INTEGER; l_area: like area
		do
			l_area := area; old_count := count
			space_count := internal_leading_white_space (l_area, old_count)

			if space_count > 0 then
				if has_unencoded_between (1, space_count) then
					remove_head (space_count)
				else
					l_area.overlapping_move (space_count, 0, old_count - space_count)
					if has_mixed_encoding then
						shift_unencoded (space_count.opposite)
					end
					set_count (old_count - space_count)
				end
			end
		end

	prune_all_leading (uc: CHARACTER_32)
			-- Remove all leading occurrences of `c'.
		do
			remove_head (leading_occurrences (uc))
		end

	prune_all_trailing (uc: CHARACTER_32)
			-- Remove all trailing occurrences of `c'.
		do
			remove_tail (trailing_occurrences (uc))
		end

	remove_head (n: INTEGER)
			-- Remove first `n' characters;
			-- if `n' > `count', remove all.
		require
			n_non_negative: n >= 0
		do
			if n > count then
				set_count (0)
				reset_hash
			else
				keep_tail (count - n)
			end
		ensure
			removed: elks_checking implies current_readable ~ (old substring (n.min (count) + 1, count))
		end

	remove_quotes
		require
			long_enough: count >= 2
		do
			remove_head (1); remove_tail (1)
		end

	remove_tail (n: INTEGER)
			-- Remove last `n' characters;
			-- if `n' > `count', remove all.
		require
			n_non_negative: n >= 0
		local
			l_count: INTEGER
		do
			l_count := count
			if n > l_count then
				set_count (0)
				reset_hash
			else
				keep_head (l_count - n)
			end
		ensure
			removed: elks_checking implies current_readable ~ (old substring (1, count - n.min (count)))
		end

	right_adjust
		-- Remove trailing whitespace.
		local
			trailing_count: INTEGER
		do
			trailing_count := trailing_white_space
			if trailing_count.to_boolean then
				keep_head (count - trailing_count)
			end
		end

feature -- Contract Support

	deleted_count (old_set, new_set: READABLE_STRING_GENERAL): INTEGER
		local
			i: INTEGER
		do
			across to_string_32 as uc loop
				i := old_set.index_of (uc.item, 1)
				if i > 0 and then new_set [i] = '%U' then
					Result := Result + 1
				end
			end
		end

feature {NONE} -- Implementation

	area_index_of (a_area: like area; c: CHARACTER_8; upper_index: INTEGER): INTEGER
		local
			i: INTEGER
		do
			Result := -1
			from until Result >= 0 or else i > upper_index loop
				if a_area [i] = c then
					Result := i
				else
					i := i + 1
				end
			end
		end

	fully_encoded_area (general: READABLE_STRING_GENERAL; index: INTEGER): detachable like area
		do
			if general.is_string_8 and then attached compatible_string_8 (general) as str_8 then
				if str_8.is_immutable and then attached cursor_8 (str_8) as c then
					create Result.make_empty (str_8.count)
					Result.copy_data (c.area, c.area_first_index, 0, str_8.count)

				elseif attached {STRING_8} str_8 as s then
					Result := s.area
				end
			elseif same_type (general)
				and then attached {EL_ZSTRING} general as zstr and then not zstr.has_mixed_encoding
			then
				Result := zstr.area

			elseif attached adapted_argument (general, index) as zstr and then not zstr.has_mixed_encoding then
				Result := zstr.area
			end
		ensure
			no_unicode_substitutes:
				attached Result as l_area implies area_index_of (l_area, substitute, general.count - 1) < 0
		end

	hide_or_reveal (characters: READABLE_STRING_GENERAL; reveal_hidden: BOOLEAN)
		-- hide all occurrences of `characters' with control characters `0x1' to `0x8' and `0x11' to `0x18'
		-- so they are not affected by a call to an escaping routine
		require
			max_of_16: characters.count <= 16
		local
			l_count, offset, code, i: INTEGER; control_characters: STRING
		do
			l_count := characters.count.min (16)
			create control_characters.make_filled (' ', l_count)
			if attached control_characters.area as l_area then
				from code := 1 until i = l_count loop
					inspect code
						when 9 then
							offset := 0x10
							code := 1
					else
					end
					l_area [i] := (code + offset).to_character_8
					code := code + 1
					i := i + 1
				end
			end
			if reveal_hidden then
				translate (control_characters, characters)
			else
				translate (characters, control_characters)
			end
		end

	replace_area_substrings (a_old, new: ZSTRING)
		local
			i, l_count, count_delta, old_count, sum_count_delta, new_current_count: INTEGER
			previous_upper_plus_1, lower, upper, new_lower, new_upper: INTEGER
			l_area, replaced_area, new_area: like area; index_area: SPECIAL [INTEGER]
			old_index_list: ARRAYED_LIST [INTEGER]
		do
			old_count := a_old.count; count_delta := new.count - old_count

			old_index_list := internal_substring_index_list (a_old)
			if old_index_list.count > 0 then
				new_current_count := count + count_delta * old_index_list.count

				if has_mixed_encoding or new.has_mixed_encoding then
					set_replaced_unencoded (old_index_list, count_delta, a_old.count, new_current_count, new)
				end

				l_area := area; new_area := new.area; index_area := old_index_list.area
				create replaced_area.make_empty (new_current_count + 1)
				previous_upper_plus_1 := 1
				from until i = index_area.count loop
					lower := index_area [i]; upper := lower + old_count - 1
					new_lower := lower + sum_count_delta; new_upper := lower + old_count + count_delta - 1
					sum_count_delta := sum_count_delta + count_delta

					l_count := lower - previous_upper_plus_1
					if l_count > 0 then
						replaced_area.copy_data (l_area, previous_upper_plus_1 - 1, new_lower - l_count - 1, l_count)
					end
					replaced_area.copy_data (new_area, 0, new_lower - 1, new.count)
					previous_upper_plus_1 := upper + 1
					i := i + 1
				end
				l_count := count - previous_upper_plus_1 + 1
				if l_count > 0 then
					replaced_area.copy_data (l_area, previous_upper_plus_1 - 1, new_upper, l_count)
				end
				replaced_area.extend ('%U')
				check
					filled: replaced_area.count = new_current_count + 1
				end
				area := replaced_area
				set_count (new_current_count)
			end
		end

	replace_substring_all_zstring (old_substring, new_substring: EL_READABLE_ZSTRING)
		obsolete
			"Kept as reliable testing reference"
		local
			old_count, l_count, new_substring_count, old_substring_count: INTEGER
			previous_index, end_index, size_difference: INTEGER; internal_replace_done: BOOLEAN
			replaced_8, current_8, new_substring_8: EL_STRING_8
			substring_index_list: detachable LIST [INTEGER]
		do
			if not old_substring.is_equal (new_substring) then
				old_count := count; new_substring_count := new_substring.count; old_substring_count := old_substring.count
				size_difference := new_substring_count - old_substring_count
				previous_index := 1

				inspect respective_encoding (old_substring)
					when Both_have_mixed_encoding then
						substring_index_list := internal_substring_index_list (old_substring)

					when Only_current then
						if new_substring.has_mixed_encoding then
							if attached internal_substring_index_list (old_substring) as positions
								and then positions.count > 0
							then
								String_8.replace_substring_all (Current, old_substring, new_substring)
								internal_replace_done := True
								substring_index_list := positions
							end

						elseif attached internal_substring_index_list (old_substring) as positions
							and then positions.count > 0
						then
							String_8.replace_substring_all (Current, old_substring, new_substring)
							if attached empty_unencoded_buffer as buffer then
								from positions.start until positions.after loop
									end_index := positions.item - 1
									if end_index >= previous_index then
										buffer.append_substring (Current, previous_index, end_index, l_count)
										l_count := l_count + end_index - previous_index + 1
									end
									l_count := l_count + new_substring_count
									previous_index := positions.item + old_substring_count
									positions.forth
								end
								end_index := old_count
								if previous_index <= end_index then
									buffer.append_substring (Current, previous_index, end_index, l_count)
								end
								set_unencoded_from_buffer (buffer)
							end
						end

					when Only_other then
						-- since old_substring cannot match anything
						do_nothing

					when Neither then
						if new_substring.has_mixed_encoding then
							if attached internal_substring_index_list (old_substring) as positions
								and then positions.count > 0
							then
								String_8.replace_substring_all (Current, old_substring, new_substring)
								if attached empty_unencoded_buffer as buffer then
									from positions.start until positions.after loop
										end_index := positions.item - 1
										if end_index >= previous_index then
											l_count := l_count + end_index - previous_index + 1
										end
										buffer.append (new_substring, l_count)
										l_count := l_count + new_substring_count
										previous_index := positions.item + old_substring_count
										positions.forth
									end
									set_unencoded_from_buffer (buffer)
								end
							end
						else
							-- Can use STRING_8 implemenation
							String_8.replace_substring_all (Current, old_substring, new_substring)
						end
				else
					substring_index_list := internal_substring_index_list (old_substring)
				end
				if attached substring_index_list as positions and then positions.count > 0
					and then attached empty_unencoded_buffer as buffer
				then
					if not internal_replace_done then
						current_8 := String_8.injected (Current, 1)
						new_substring_8 := String_8.injected (new_substring, 2)
						create area.make_filled ('%U', old_count + size_difference * positions.count + 1)
						set_count (0); replaced_8 := current_string_8
					end
					from positions.start until positions.after loop
						end_index := positions.item - 1
						if end_index >= previous_index then
							if has_mixed_encoding then
								buffer.append_substring (Current, previous_index, end_index, l_count)
							end
							if not internal_replace_done then
								replaced_8.append_substring (current_8, previous_index, end_index)
							end
							l_count := l_count + end_index - previous_index + 1
						end
						if new_substring.has_mixed_encoding then
							buffer.append (new_substring, l_count)
						end
						if not internal_replace_done then
							replaced_8.append (new_substring_8)
						end
						l_count := l_count + new_substring_count
						previous_index := positions.item + old_substring_count
						positions.forth
					end
					end_index := old_count
					if previous_index <= end_index then
						if has_mixed_encoding then
							buffer.append_substring (Current, previous_index, end_index, l_count)
						end
						if not internal_replace_done then
							replaced_8.append_substring (current_8, previous_index, end_index)
						end
					end
					if not internal_replace_done then
						set_from_string_8 (replaced_8)
					end
					set_unencoded_from_buffer (buffer)
				end
			end
		end

	set_replaced_unencoded (a_index_list: ARRAYED_LIST [INTEGER]; count_delta, old_count, new_count: INTEGER; new: ZSTRING)
		local
			i, l_count, sum_count_delta, block_index, block_index_new,
			previous_upper_plus_1, lower, upper, new_lower, new_upper: INTEGER
			l_area: like area; index_area: SPECIAL [INTEGER]; area_32: SPECIAL [CHARACTER_32]
			buffer: like Unencoded_buffer; current_has_substitutes, new_has_substitutes: BOOLEAN
		do
			buffer := empty_unencoded_buffer; l_area := area; area_32 := unencoded_area
			index_area := a_index_list.area;
			current_has_substitutes := has_mixed_encoding
			new_has_substitutes := new.has_mixed_encoding
			previous_upper_plus_1 := 1
			from until i = index_area.count loop
				lower := index_area [i]; upper := lower + old_count - 1
				new_lower := lower + sum_count_delta; new_upper := lower + old_count + count_delta - 1
				sum_count_delta := sum_count_delta + count_delta

				l_count := lower - previous_upper_plus_1
				if current_has_substitutes and then l_count > 0 then
					buffer.append_substituted (
						l_area, area_32, $block_index, previous_upper_plus_1 - 1, new_lower - l_count - 1, l_count
					)
				end
				if new_has_substitutes then
					block_index_new := 0
					buffer.append_substituted (
						new.area, new.unencoded_area, $block_index_new, 0, new_lower - 1, new.count
					)
				end
				previous_upper_plus_1 := upper + 1
				i := i + 1
			end
			l_count := count - previous_upper_plus_1 + 1
			if current_has_substitutes and then l_count > 0 then
				buffer.append_substituted (
					l_area, area_32, $block_index, previous_upper_plus_1 - 1, new_upper, l_count
				)
			end
			set_unencoded_from_buffer (buffer)
		end

	translate_with_deletion (old_characters, new_characters: READABLE_STRING_GENERAL; delete_null: BOOLEAN)
		-- replace all characters in `old_characters' with corresponding character in `new_characters'.
		-- If `delete_null' is true, remove any characters corresponding to null value '%U'
		require
			each_old_has_new: old_characters.count = new_characters.count
		local
			i, j, index, min_count, upper_index, block_index, last_upper: INTEGER
			old_c, new_c: CHARACTER; old_z_code, new_z_code: NATURAL
			iter: EL_COMPACT_SUBSTRINGS_32_ITERATION
		do
			upper_index := count - 1; min_count := old_characters.count.min (new_characters.count)

			if attached area as l_area and then attached unencoded_area as area_32 then
				if area_32.count = 0
					and then attached fully_encoded_area (old_characters, 1) as old_area
					and then attached fully_encoded_area (new_characters, 2) as new_area
				then
					from until i > upper_index loop
						old_c := l_area [i]
						index := area_index_of (old_area, old_c, min_count - 1) -- negative 1 if not found
						if index >= 0 then
							new_c := new_area [index]
						else
							new_c := old_c
						end
						if delete_null implies new_c.code > 0 then
							l_area.put (new_c, j)
							j := j + 1
						end
						i := i + 1
					end
					set_count (j); l_area [j] := '%U'

				elseif attached empty_unencoded_buffer as l_new_unencoded
					and then attached shared_z_code_pattern_general (old_characters, 1) as old_expanded
					and then attached shared_z_code_pattern_general (new_characters, 2) as new_expanded
				then
					last_upper := l_new_unencoded.last_upper
					from until i > upper_index loop
						old_z_code := iter.i_th_z_code ($block_index, l_area, area_32, i)
						index := old_expanded.index_of (old_z_code.to_character_32, 1)
						if index > 0 then
							new_z_code := new_expanded.code (index)
						else
							new_z_code := old_z_code
						end
						if delete_null implies new_z_code > 0 then
							if new_z_code > 0xFF then
								last_upper := l_new_unencoded.extend_z_code (new_z_code, last_upper, j + 1)
								l_area.put (Substitute, j)
							else
								l_area.put (new_z_code.to_character_8, j)
							end
							j := j + 1
						end
						i := i + 1
					end
					set_count (j); l_area [j] := '%U'
					l_new_unencoded.set_last_upper (last_upper)
					set_unencoded_from_buffer (l_new_unencoded)
				end
			end
		ensure
			valid_unencoded: is_valid
			unchanged_count: not delete_null implies count = old count
			changed_count: delete_null implies count = old (count - deleted_count (old_characters, new_characters))
		end

end