diff --git a/src/pugixml.cpp b/src/pugixml.cpp
index 0b76d1f15b3580fcaad9df8173ae4f39dc0be62b..24b4a6ad78618b00583e8e22810561ce3048401e 100644
--- a/src/pugixml.cpp
+++ b/src/pugixml.cpp
@@ -1887,6 +1887,8 @@ namespace
 			}
 
 			THROW_ERROR(status_bad_doctype, s);
+
+			return s;
 		}
 
 		char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel)
@@ -2975,6 +2977,40 @@ namespace
 		}
 	}
 
+	xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding)
+	{
+		if (!file) return make_parse_result(status_file_not_found);
+
+		fseek(file, 0, SEEK_END);
+		long length = ftell(file);
+		fseek(file, 0, SEEK_SET);
+
+		if (length < 0)
+		{
+			fclose(file);
+			return make_parse_result(status_io_error);
+		}
+		
+		char* s = static_cast<char*>(global_allocate(length > 0 ? length : 1));
+
+		if (!s)
+		{
+			fclose(file);
+			return make_parse_result(status_out_of_memory);
+		}
+
+		size_t read = fread(s, 1, (size_t)length, file);
+		fclose(file);
+
+		if (read != (size_t)length)
+		{
+			global_deallocate(s);
+			return make_parse_result(status_io_error);
+		}
+		
+		return doc.load_buffer_inplace_own(s, length, options, encoding);
+	}
+
 #ifndef PUGIXML_NO_STL
 	template <typename T> xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream<T>& stream, unsigned int options, xml_encoding encoding)
 	{
@@ -3007,6 +3043,67 @@ namespace
 		return doc.load_buffer_inplace_own(buffer.release(), actual_length * sizeof(T), options, encoding);
 	}
 #endif
+
+#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__MINGW32__)
+	FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+	{
+		return _wfopen(path, mode);
+	}
+#else
+	char* convert_path_heap(const wchar_t* str)
+	{
+		assert(str);
+
+		STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4);
+
+		size_t length = wcslen(str);
+
+		// first pass: get length in utf8 characters
+		size_t size = sizeof(wchar_t) == 2 ?
+			utf_decoder<utf8_counter>::decode_utf16_block(reinterpret_cast<const uint16_t*>(str), length, 0) :
+			utf_decoder<utf8_counter>::decode_utf32_block(reinterpret_cast<const uint32_t*>(str), length, 0);
+
+		// allocate resulting string
+		char* result = static_cast<char*>(global_allocate(size + 1));
+		if (!result) return 0;
+
+		// second pass: convert to utf8
+		if (size > 0)
+		{
+			uint8_t* begin = reinterpret_cast<uint8_t*>(result);
+			uint8_t* end = sizeof(wchar_t) == 2 ?
+				utf_decoder<utf8_writer>::decode_utf16_block(reinterpret_cast<const uint16_t*>(str), length, begin) :
+				utf_decoder<utf8_writer>::decode_utf32_block(reinterpret_cast<const uint32_t*>(str), length, begin);
+	  	
+			assert(begin + size == end);
+			(void)!end;
+		}
+
+		// zero-terminate
+		result[size] = 0;
+
+	  	return result;
+	}
+
+	FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+	{
+		// there is no standard function to open wide paths, so our best bet is to try utf8 path
+		char* path_utf8 = convert_path_heap(path);
+		if (!path_utf8) return 0;
+
+		// convert mode to ASCII (we mirror _wfopen interface)
+		char mode_ascii[4] = {0};
+		for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast<char>(mode[i]);
+
+		// try to open the utf8 path
+		FILE* result = fopen(path_utf8, mode_ascii);
+
+		// free dummy buffer
+		global_deallocate(path_utf8);
+
+		return result;
+	}
+#endif
 }
 
 namespace pugi
@@ -4255,36 +4352,17 @@ namespace pugi
 		reset();
 
 		FILE* file = fopen(path, "rb");
-		if (!file) return make_parse_result(status_file_not_found);
-
-		fseek(file, 0, SEEK_END);
-		long length = ftell(file);
-		fseek(file, 0, SEEK_SET);
 
-		if (length < 0)
-		{
-			fclose(file);
-			return make_parse_result(status_io_error);
-		}
-		
-		char* s = static_cast<char*>(global_allocate(length > 0 ? length : 1));
+		return load_file_impl(*this, file, options, encoding);
+	}
 
-		if (!s)
-		{
-			fclose(file);
-			return make_parse_result(status_out_of_memory);
-		}
+	xml_parse_result xml_document::load_file(const wchar_t* path, unsigned int options, xml_encoding encoding)
+	{
+		reset();
 
-		size_t read = fread(s, 1, (size_t)length, file);
-		fclose(file);
+		FILE* file = open_file_wide(path, L"rb");
 
-		if (read != (size_t)length)
-		{
-			global_deallocate(s);
-			return make_parse_result(status_io_error);
-		}
-		
-		return load_buffer_inplace_own(s, length, options, encoding);
+		return load_file_impl(*this, file, options, encoding);
 	}
 
 	xml_parse_result xml_document::load_buffer_impl(void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own)
@@ -4377,6 +4455,19 @@ namespace pugi
 		return true;
 	}
 
+	bool xml_document::save_file(const wchar_t* path, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+	{
+		FILE* file = open_file_wide(path, L"wb");
+		if (!file) return false;
+
+		xml_writer_file writer(file);
+		save(writer, indent, flags, encoding);
+
+		fclose(file);
+
+		return true;
+	}
+
 #ifndef PUGIXML_NO_STL
 	std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str)
 	{
diff --git a/src/pugixml.hpp b/src/pugixml.hpp
index b6f1710fbc640a6641796a3157024cf268a66f05..12b8f491ae567056a9051572c29eefe155a38ba9 100644
--- a/src/pugixml.hpp
+++ b/src/pugixml.hpp
@@ -1672,6 +1672,16 @@ namespace pugi
 		 */
 		xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
 
+		/**
+		 * Load document from file
+		 *
+		 * \param path - file path
+		 * \param options - parsing options
+		 * \param encoding - source data encoding
+		 * \return parsing result
+		 */
+		xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
 		/**
 		 * Load document from buffer
 		 *
@@ -1751,6 +1761,17 @@ namespace pugi
 		 * \return success flag
 		 */
 		bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+
+		/**
+		 * Save XML to file
+		 *
+		 * \param path - file path
+		 * \param indent - indentation string
+		 * \param flags - formatting flags
+		 * \param encoding - encoding used for writing
+		 * \return success flag
+		 */
+		bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
 	};
 
 #ifndef PUGIXML_NO_XPATH