summaryrefslogtreecommitdiffstats
path: root/chrome/browser/bookmarks/bookmark_html_writer.cc
diff options
context:
space:
mode:
authorsky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-07 04:31:35 +0000
committersky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-07 04:31:35 +0000
commitb504899244b4264994d4daae2bee660706dba652 (patch)
treeeda190cb35dfff7f700a4c636730ff2487cb35d8 /chrome/browser/bookmarks/bookmark_html_writer.cc
parente562de106eeab2667eeb6922ddf2d771a0efa55d (diff)
downloadchromium_src-b504899244b4264994d4daae2bee660706dba652.zip
chromium_src-b504899244b4264994d4daae2bee660706dba652.tar.gz
chromium_src-b504899244b4264994d4daae2bee660706dba652.tar.bz2
Adds import/export of bookmarks to bookmarks.html file.
BUG=1649 TEST=bring up bookmark manager and try out import/export from the tools menu. Note that import ALWAYS creates a new folder under the 'Other bookmarks folder' with the name of Imported (x). This is by design. Review URL: http://codereview.chromium.org/9471 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4968 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/bookmarks/bookmark_html_writer.cc')
-rw-r--r--chrome/browser/bookmarks/bookmark_html_writer.cc332
1 files changed, 332 insertions, 0 deletions
diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/bookmarks/bookmark_html_writer.cc
new file mode 100644
index 0000000..b6c26ef
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_html_writer.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/bookmarks/bookmark_html_writer.h"
+
+#include "base/message_loop.h"
+#include "base/scoped_handle.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/values.h"
+#include "chrome/browser/bookmarks/bookmark_codec.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/history/history_types.h"
+#include "net/base/escape.h"
+
+namespace bookmark_html_writer {
+
+namespace {
+
+// File header.
+const char kHeader[] =
+ "<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
+ "<!-- This is an automatically generated file.\r\n"
+ " It will be read and overwritten.\r\n"
+ " DO NOT EDIT! -->\r\n"
+ "<META HTTP-EQUIV=\"Content-Type\""
+ " CONTENT=\"text/html; charset=UTF-8\">\r\n"
+ "<TITLE>Bookmarks</TITLE>\r\n"
+ "<H1>Bookmarks</H1>\r\n"
+ "<DL><p>\r\n";
+
+// Newline separator.
+const char kNewline[] = "\r\n";
+
+// The following are used for bookmarks.
+
+// Start of a bookmark.
+const char kBookmarkStart[] = "<DT><A HREF=\"";
+// After kBookmarkStart.
+const char kAddDate[] = "\" ADD_DATE=\"";
+// After kAddDate.
+const char kBookmarkAttributeEnd[] = "\">";
+// End of a bookmark.
+const char kBookmarkEnd[] = "</A>";
+
+// The following are used when writing folders.
+
+// Start of a folder.
+const char kFolderStart[] = "<DT><H3 ADD_DATE=\"";
+// After kFolderStart.
+const char kLastModified[] = "\" LAST_MODIFIED=\"";
+// After kLastModified when writing the bookmark bar.
+const char kBookmarkBar[] = "\" PERSONAL_TOOLBAR_FOLDER=\"true\">";
+// After kLastModified when writing a user created folder.
+const char kFolderAttributeEnd[] = "\">";
+// End of the folder.
+const char kFolderEnd[] = "</H3>";
+// Start of the children of a folder.
+const char kFolderChildren[] = "<DL><p>";
+// End of the children for a folder.
+const char kFolderChildrenEnd[] = "</DL><p>";
+
+// Number of characters to indent by.
+const size_t kIndentSize = 4;
+
+// Class responsible for the actual writing.
+class Writer : public Task {
+ public:
+ Writer(Value* bookmarks, const std::wstring& path)
+ : bookmarks_(bookmarks),
+ path_(path) {
+ }
+
+ virtual void Run() {
+ if (!OpenFile())
+ return;
+
+ Value* roots;
+ if (!Write(kHeader) ||
+ bookmarks_->GetType() != Value::TYPE_DICTIONARY ||
+ !static_cast<DictionaryValue*>(bookmarks_.get())->Get(
+ BookmarkCodec::kRootsKey, &roots) ||
+ roots->GetType() != Value::TYPE_DICTIONARY) {
+ NOTREACHED();
+ return;
+ }
+
+ DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
+ Value* root_folder_value;
+ Value* other_folder_value;
+ if (!roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
+ &root_folder_value) ||
+ root_folder_value->GetType() != Value::TYPE_DICTIONARY ||
+ !roots_d_value->Get(BookmarkCodec::kOtherBookmarFolderNameKey,
+ &other_folder_value) ||
+ other_folder_value->GetType() != Value::TYPE_DICTIONARY) {
+ NOTREACHED();
+ return; // Invalid type for root folder and/or other folder.
+ }
+
+ IncrementIndent();
+
+ if (!WriteNode(*static_cast<DictionaryValue*>(root_folder_value),
+ history::StarredEntry::BOOKMARK_BAR) ||
+ !WriteNode(*static_cast<DictionaryValue*>(other_folder_value),
+ history::StarredEntry::OTHER)) {
+ return;
+ }
+
+ DecrementIndent();
+
+ Write(kFolderChildrenEnd);
+ Write(kNewline);
+ }
+
+ private:
+ // Types of text being written out. The type dictates how the text is
+ // escaped.
+ enum TextType {
+ // The text is the value of an html attribute, eg foo in
+ // <a href="foo">.
+ ATTRIBUTE_VALUE,
+
+ // Actual content, eg foo in <h1>foo</h2>.
+ CONTENT
+ };
+
+ // Opens the file, returning true on success.
+ bool OpenFile() {
+ handle_.Set(
+ CreateFile(path_.c_str(), GENERIC_WRITE, 0, NULL,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
+ if (!handle_.IsValid())
+ return false;
+ return true;
+ }
+
+ // Increments the indent.
+ void IncrementIndent() {
+ indent_.resize(indent_.size() + kIndentSize, ' ');
+ }
+
+ // Decrements the indent.
+ void DecrementIndent() {
+ DCHECK(!indent_.empty());
+ indent_.resize(indent_.size() - kIndentSize, ' ');
+ }
+
+ // Writes raw text out returning true on success. This does not escape
+ // the text in anyway.
+ bool Write(const std::string& text) {
+ DWORD wrote;
+ bool result =
+ (WriteFile(handle_, text.c_str(), text.length(), &wrote, NULL) &&
+ wrote == text.length());
+ DCHECK(result);
+ return result;
+ }
+
+ // Writes out the text string (as UTF8). The text is escaped based on
+ // type.
+ bool Write(const std::wstring& text, TextType type) {
+ std::string utf8_string;
+
+ switch (type) {
+ case ATTRIBUTE_VALUE:
+ // Convert " to \"
+ if (text.find(L"\"") != std::wstring::npos) {
+ std::wstring replaced_string = text;
+ ReplaceSubstringsAfterOffset(&replaced_string, 0, L"\"", L"\\\"");
+ utf8_string = WideToUTF8(replaced_string);
+ } else {
+ utf8_string = WideToUTF8(text);
+ }
+ break;
+
+ case CONTENT:
+ utf8_string = WideToUTF8(EscapeForHTML(text));
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ return Write(utf8_string);
+ }
+
+ // Indents the current line.
+ bool WriteIndent() {
+ return Write(indent_);
+ }
+
+ // Converts a time string written to the JSON codec into a time_t string
+ // (used by bookmarks.html) and writes it.
+ bool WriteTime(const std::wstring& time_string) {
+ base::Time time =
+ base::Time::FromInternalValue(StringToInt64(time_string));
+ return Write(Int64ToString(time.ToTimeT()));
+ }
+
+ // Writes the node and all its children, returning true on success.
+ bool WriteNode(const DictionaryValue& value,
+ history::StarredEntry::Type folder_type) {
+ std::wstring title, date_added_string, type_string;
+ if (!value.GetString(BookmarkCodec::kNameKey, &title) ||
+ !value.GetString(BookmarkCodec::kDateAddedKey, &date_added_string) ||
+ !value.GetString(BookmarkCodec::kTypeKey, &type_string) ||
+ (type_string != BookmarkCodec::kTypeURL &&
+ type_string != BookmarkCodec::kTypeFolder)) {
+ NOTREACHED();
+ return false;
+ }
+
+ if (type_string == BookmarkCodec::kTypeURL) {
+ std::wstring url_string;
+ if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
+ NOTREACHED();
+ return false;
+ }
+ if (!WriteIndent() ||
+ !Write(kBookmarkStart) ||
+ !Write(url_string, ATTRIBUTE_VALUE) ||
+ !Write(kAddDate) ||
+ !WriteTime(date_added_string) ||
+ !Write(kBookmarkAttributeEnd) ||
+ !Write(title, CONTENT) ||
+ !Write(kBookmarkEnd) ||
+ !Write(kNewline)) {
+ return false;
+ }
+ return true;
+ }
+
+ // Folder.
+ std::wstring last_modified_date;
+ Value* child_values;
+ if (!value.GetString(BookmarkCodec::kDateModifiedKey,
+ &last_modified_date) ||
+ !value.Get(BookmarkCodec::kChildrenKey, &child_values) ||
+ child_values->GetType() != Value::TYPE_LIST) {
+ NOTREACHED();
+ return false;
+ }
+ if (folder_type != history::StarredEntry::OTHER) {
+ // The other folder name is not written out. This gives the effect of
+ // making the contents of the 'other folder' be a sibling to the bookmark
+ // bar folder.
+ if (!WriteIndent() ||
+ !Write(kFolderStart) ||
+ !WriteTime(date_added_string) ||
+ !Write(kLastModified) ||
+ !WriteTime(last_modified_date)) {
+ return false;
+ }
+ if (folder_type == history::StarredEntry::BOOKMARK_BAR) {
+ if (!Write(kBookmarkBar))
+ return false;
+ title = L"Bookmark Bar";
+ } else if (!Write(kFolderAttributeEnd)) {
+ return false;
+ }
+ if (!Write(title, CONTENT) ||
+ !Write(kFolderEnd) ||
+ !Write(kNewline) ||
+ !WriteIndent() ||
+ !Write(kFolderChildren) ||
+ !Write(kNewline)) {
+ return false;
+ }
+ IncrementIndent();
+ }
+
+ // Write the children.
+ ListValue* children = static_cast<ListValue*>(child_values);
+ for (size_t i = 0; i < children->GetSize(); ++i) {
+ Value* child_value;
+ if (!children->Get(i, &child_value) ||
+ child_value->GetType() != Value::TYPE_DICTIONARY) {
+ NOTREACHED();
+ return false;
+ }
+ if (!WriteNode(*static_cast<DictionaryValue*>(child_value),
+ history::StarredEntry::USER_GROUP)) {
+ return false;
+ }
+ }
+ if (folder_type != history::StarredEntry::OTHER) {
+ // Close out the folder.
+ DecrementIndent();
+ if (!WriteIndent() ||
+ !Write(kFolderChildrenEnd) ||
+ !Write(kNewline)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // The BookmarkModel as a Value. This value was generated from the
+ // BookmarkCodec.
+ scoped_ptr<Value> bookmarks_;
+
+ // Path we're writing to.
+ std::wstring path_;
+
+ // File we're writing to.
+ ScopedHandle handle_;
+
+ // How much we indent when writing a bookmark/folder. This is modified
+ // via IncrementIndent and DecrementIndent.
+ std::string indent_;
+};
+
+} // namespace
+
+void WriteBookmarks(MessageLoop* thread,
+ BookmarkModel* model,
+ const std::wstring& path) {
+ // BookmarkModel isn't thread safe (nor would we want to lock it down
+ // for the duration of the write), as such we make a copy of the
+ // BookmarkModel using BookmarkCodec then write from that.
+ BookmarkCodec codec;
+ scoped_ptr<Writer> writer(new Writer(codec.Encode(model), path));
+ if (thread)
+ thread->PostTask(FROM_HERE, writer.release());
+ else
+ writer->Run();
+}
+
+} // namespace bookmark_html_writer