note description: "XML file preference storage implementation." legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class PREFERENCES_STORAGE_XML inherit PREFERENCES_STORAGE_I redefine initialize_with_preferences, make_with_location, save_preferences end XML_CALLBACKS_FILTER_FACTORY export {NONE} all end REFACTORING_HELPER create make_empty, make_with_location feature {NONE} -- Initialization make_empty -- Create preferences storage in XML file. File will be created based on name of application. -- For operating systems which support it it will stored in the users home directory. For operating -- systems that do not support home directories it will be stored in the current working directory. local l_loc: detachable STRING l_exec: EXECUTION_ENVIRONMENT l_op: OPERATING_ENVIRONMENT fn: FILE_NAME do create l_exec l_op := l_exec.operating_environment if l_op.home_directory_supported then l_loc := l_exec.home_directory_name check l_loc /= Void end -- implied by `home_directory_supported' else l_loc := l_exec.current_working_directory end create fn.make_from_string (l_loc) fn.set_file_name ("stored_preferences.xml") make_with_location (fn.string) end make_with_location (a_location: STRING) -- Create preference storage in the XML file at location `a_location'. -- If file does not exist create new one. do Precursor {PREFERENCES_STORAGE_I} (a_location) create xml_structure.make_with_root_named ("EIFFEL_DOCUMENT", create {XML_NAMESPACE}.make_default) end feature {PREFERENCES} -- Initialization initialize_with_preferences (a_preferences: PREFERENCES) do Precursor (a_preferences) extract_preferences_from_file end feature {PREFERENCES} -- Resource Management exists: BOOLEAN -- Does storage exists ? local f: RAW_FILE do create f.make (location) Result := f.exists end has_preference (a_name: STRING): BOOLEAN -- Does the underlying store contain a preference with `a_name'? do Result := session_values.has (a_name) end get_preference_value (a_name: STRING): detachable STRING -- Retrieve the preference string value from the underlying store. do Result := session_values.item (a_name) end save_preference (a_preference: PREFERENCE) -- Save `a_preference' to the file on disk. local l_preferences: like preferences do -- TODO: neilc. How to save only a single preference to the file? l_preferences := preferences check attached l_preferences end -- implied by precondition `initialized' save_preferences (l_preferences.preferences.linear_representation, True) end save_preferences (a_preferences: ARRAYED_LIST [PREFERENCE]; a_save_modified_values_only: BOOLEAN) -- Save all preferences in `a_preferences' to storage device. -- If `a_save_modified_values_only' then only preferences whose value is different -- from the default one are saved, otherwise all preferences are saved. local l_preference: PREFERENCE l_file: PLAIN_TEXT_FILE pref_string1, pref_string2, pref_string3: STRING do pref_string1 := "%N%T" create l_file.make (location) safe_open_write (l_file) if l_file.is_open_write then l_file.put_string ("") from a_preferences.start until a_preferences.after loop l_preference := a_preferences.item if not a_save_modified_values_only or else not l_preference.is_default_value then l_file.put_string (pref_string1) l_file.put_string (escape_xml (l_preference.name)) l_file.put_string (pref_string2) l_file.put_string (escape_xml (l_preference.string_value)) l_file.put_string (pref_string3) else -- nothing to do to remove them from file. end a_preferences.forth end l_file.put_string ("%N") l_file.close else debug ("refactor_fixme") fixme ("Add code to let callers that `preferences' could not be saved") end end end remove_preference (a_preference: PREFERENCE) -- Remove `preference' from storage device. do end feature {NONE} -- Implementation xml_structure: detachable XML_DOCUMENT -- XML structure built from parsing of `file'. extract_preferences_from_file -- Extract from the `file' the saved preference values. require location_not_void: location /= Void local parser: XML_LITE_STOPPABLE_PARSER l_file: PLAIN_TEXT_FILE l_tree: XML_CALLBACKS_TREE l_attrib: detachable XML_ATTRIBUTE pref_name, pref_value: detachable STRING l_root_element: XML_ELEMENT t_preference, t_name, t_value: STRING l_retried: BOOLEAN do create parser.make create l_tree.make_null parser.set_callbacks (l_tree) create l_file.make (location) if l_file.exists and then l_file.is_readable then safe_open_read (l_file) if l_file.is_open_read then parser.parse_from_file (l_file) l_file.close if not parser.error_occurred and then (attached l_tree.document as l_xml_structure) -- should be implied by `not has_error' then xml_structure := l_xml_structure from t_preference := "PREFERENCE" t_name := "NAME" t_value := "VALUE" l_root_element := l_xml_structure.root_element l_root_element.start until l_root_element.after loop if attached {XML_ELEMENT} l_root_element.item_for_iteration as node then if node.name ~ t_preference then -- Found preference l_attrib := node.attribute_by_name (t_name) if l_attrib /= Void then pref_name := l_attrib.value l_attrib := node.attribute_by_name (t_value) if l_attrib /= Void then pref_value := l_attrib.value end if pref_value = Void then check xml_contain_value: False end create pref_value.make_empty end session_values.put (pref_value, pref_name) end end end l_root_element.forth end else debug ("refactor_fixme") fixme ("Add code to let callers know that XML file was invalid") end end else debug ("refactor_fixme") fixme ("Add code to let callers that we could not open preference file") end end else debug ("refactor_fixme") fixme ("Add code to let callers that preference file can not be read") end end end escape_xml (a_string: STRING): STRING -- Escape xml entities in `a_string'. require a_string_not_void: a_string /= Void do create Result.make_from_string (a_string) Result.replace_substring_all (Amp_string, amp_entity) Result.replace_substring_all (Lt_string, Lt_entity) Result.replace_substring_all (Gt_string, Gt_entity) Result.replace_substring_all (Quot_string, quot_entity) ensure result_not_void: Result /= Void end Lt_string: STRING once create Result.make (1) Result.append_character (lt_char) end Gt_string: STRING once create Result.make (1) Result.append_character (gt_char) end Amp_string: STRING once create Result.make (1) Result.append_character (amp_char) end Quot_string: STRING once create Result.make (1) Result.append_character (quot_char) end safe_open_read (a_file: FILE) -- Safely open `a_file' with read mode. require a_file_not_void: a_file /= Void a_file_closed: a_file.is_closed local retried: BOOLEAN do if not retried then a_file.open_read elseif not a_file.is_closed then a_file.close end rescue if not retried then retried := True retry end end safe_open_write (a_file: FILE) -- Safely open `a_file' with write mode. require a_file_not_void: a_file /= Void a_file_closed: a_file.is_closed local retried: BOOLEAN do if not retried then a_file.open_write elseif not a_file.is_closed then a_file.close end rescue if not retried then retried := True retry end end feature {NONE} -- XML markup and entities constants Lt_char: CHARACTER = '<' Gt_char: CHARACTER = '>' Amp_char: CHARACTER = '&' Quot_char: CHARACTER = '%"' Lt_entity: STRING = "<" Gt_entity: STRING = ">" Amp_entity: STRING = "&" Quot_entity: STRING = """ invariant has_session_values: session_values /= Void has_xml_structure: xml_structure /= Void note copyright: "Copyright (c) 1984-2010, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end