/*
SRBookmarkStorage.m

Author: Makoto Kinoshita

Copyright 2004 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import "SRDefaultsKey.h"
#import "SRPasteboard.h"

#import "SRAppDelegate.h"

#import "SRBookmark.h"
#import "SRBookmarkStorage.h"

#import "WebKitEx.h"

// Notification name
NSString*   SRBookmarkAddedNotificationName = @"SRBookmarkAddeddNotificationName";
NSString*   SRBookmarkRemovedNotificationName = @"SRBookmarkRemovedNotificationName";
NSString*   SRBookmarkChangedNotificationName = @"SRBookmarkChangedNotificationName";

// Pasteboard data type
NSString*    SRBookmarkPboardType = @"SRBookmarkPboardType";

// Bookmark element name for Safari
static NSString*    _SafariBookmarkChildrenName = @"Children";
static NSString*    _SafariBookmarkTitleName = @"Title";
static NSString*    _SafariBookmarkTypeName = @"WebBookmarkType";
static NSString*    _SafariBookmarksTypeListName = @"WebBookmarkTypeList";
static NSString*    _SafariBookmarksTypeLeafName = @"WebBookmarkTypeLeaf";
static NSString*    _SafariBookmarkURLName = @"URLString";
static NSString*    _SafariBookmarkURIDictionaryName = @"URIDictionary";
static NSString*    _SafariBookmarkURITitleName = @"title";
static NSString*    _SafariBookmarkAutoTabName = @"WebBookmarkAutoTab";
static NSString*    _SafariBookmarkUUIDName = @"WebBookmarkUUID";

static NSString*    _SafariBookmarkVersion = @"WebBookmarkFileVersion";

static NSString*    _SafariBookmarksBarName = @"BookmarksBar";
static NSString*    _SafariBookmarksMenuName = @"BookmarksMenu";
//static NSString*    _SafariAddressBookMenuName = @"AddressBook";
//static NSString*    _SafariRendezbousMenuName = @"Rendezvous";
//static NSString*    _SafariHistoryMenuName = @"History";

static NSString*    _typeStr[] = {
    @"HTML", 
    @"Folder", 
    @"BookmarksBar", 
    @"BookmarksMenu", 
};

@interface SRBookmarkStorage (private)
- (void)_reconstructBookmarks;
- (void)_readFirefoxBookmarks;
@end

#pragma mark -

// BookmarkStorage class
@implementation SRBookmarkStorage

//--------------------------------------------------------------//
#pragma mark -- Accessing to instance --
//--------------------------------------------------------------//

+ (id)sharedInstance
{
    // Create shared instance
    static SRBookmarkStorage*   _storage = nil;
    static BOOL                 _isInitializing = NO;
    if (!_storage) {
        if (_isInitializing) {
            return nil;
        }
        
        _isInitializing = YES;
        _storage = [[SRBookmarkStorage alloc] init];
        _isInitializing = NO;
    }
    
    return _storage;
}

- (id)init
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _rootBookmark = [[SRBookmark 
            folderWithTitle:NSLocalizedStringFromTable(@"Bookmarks", @"Bookmark", nil) 
            originalBrowser:SRBrowserShiira] retain];
    _hasFirefoxBookmark = NO;
    _hasIEBookmark = NO;
    
    // Load all bookmarks
    _notifyFlag = NO;
    [self reloadBookmarksOfAllBrowsers];
    [self _reconstructBookmarks];
    _notifyFlag = YES;
    
    // When selected browser bookmark is not found, select Shiira
    int browser;
    browser = [defaults integerForKey:SRBookmarkBookmarksBar];
    if (![self hasBookmarkOfBrowser:browser]) {
        [defaults setInteger:SRBrowserShiira forKey:SRBookmarkBookmarksBar];
    }
    
    // Configure bookmark table
    NSArray*    isUsing;
    isUsing = [defaults objectForKey:SRBookmarkIsUsing];
    if (!isUsing || [isUsing count] < SRNumberOfBrowser) {
        NSMutableArray* array;
        array = [NSMutableArray arrayWithArray:isUsing];
        
        int i;
        for (i = [array count]; i < SRNumberOfBrowser; i++) {
            [array addObject:[NSNumber numberWithBool:NO]];
        }
        [defaults setObject:array forKey:SRBookmarkIsUsing];
    }
    
    // Register key value observation
    [defaults addObserver:self 
            forKeyPath:SRBookmarkIsUsing 
            options:NSKeyValueObservingOptionNew 
            context:NULL];
    
    return self;
}

- (void)dealloc
{
    [_shiiraBookmarks release];
    [_safariBookmarks release];
    [_firefoxBookmarks release];
    [_rootBookmark release];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Loading bookmarks --
//--------------------------------------------------------------//

- (void)_reconstructBookmarks
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Turn off notify flag
    BOOL    notifyFlag = _notifyFlag;
    _notifyFlag = NO;
    
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    
    if ([[_rootBookmark children] count] > 0) {
        // Before bookmarks removing, save it
        [[NSApp delegate] saveBookmarks];
        
        // Reconstruct Shiira bookmark
        [_shiiraBookmarks removeAllChildren];
        enumerator = [[_rootBookmark children] objectEnumerator];
        while (child = [enumerator nextObject]) {
            if ([child isShiiraBookmark]) {
                [_shiiraBookmarks addChild:child];
            }
        }
    }
    
    // Remove all bookmakrs
    [_rootBookmark removeAllChildren];
    
    // Get isUsing
    NSArray*    isUsing;
    isUsing = [defaults objectForKey:SRBookmarkIsUsing];
    
    // Add Shiira bookmarks
    enumerator = [[_shiiraBookmarks children] objectEnumerator];
    while (child = [enumerator nextObject]) {
        [_rootBookmark addChild:child];
    }
    
    // Add Safari bookmarks
#if 1
    if (_safariBookmarks && 
        [isUsing count] > SRBrowserSafari && 
        [[isUsing objectAtIndex:SRBrowserSafari] boolValue])
    {
        [_rootBookmark addChild:_safariBookmarks];
    }
#else
    BOOL    isIncludedAllSafariBookmarksMenu;
    BOOL    isIntegratedAllSafariBookmarksMenu;
    isIncludedAllSafariBookmarksMenu = [defaults boolForKey:SRBookmarkIncludeAllSafariBookmarksMenu];
    isIntegratedAllSafariBookmarksMenu = [defaults boolForKey:SRBookmarkIntegrateSafariBookmarksMenu];
    
    if (isIncludedAllSafariBookmarksMenu) {
        if (isIntegratedAllSafariBookmarksMenu) {
            // Add children
            enumerator = [[_safariBookmarks children] objectEnumerator];
            while (child = [enumerator nextObject]) {
                [_rootBookmark addChild:child];
            }
        }
        else {
            [_rootBookmark addChild:_safariBookmarks];
        }
    }
#endif
    
    // Add Firefox bookmarks
    if ([isUsing count] > SRBrowserFirefox && 
        [[isUsing objectAtIndex:SRBrowserFirefox] boolValue])
    {
        if (!_firefoxBookmarks) {
            [self _readFirefoxBookmarks];
        }
        else {
            [_rootBookmark addChild:_firefoxBookmarks];
        }
    }
    
    // Add IE bookmarks
    if (_ieBookmarks && 
        [isUsing count] > SRBrowserIE && 
        [[isUsing objectAtIndex:SRBrowserIE] boolValue])
    {
        [_rootBookmark addChild:_ieBookmarks];
    }
    
    // Restore back notify flag
    _notifyFlag = notifyFlag;
    if (_notifyFlag) {
        NSNotification* notification;
        notification = [NSNotification notificationWithName:SRBookmarkChangedNotificationName 
                object:[NSArray arrayWithObject:_rootBookmark]];
        [self notifyBookmarkUpdated:notification];
    }
}

- (SRBookmark*)_convertShiiraBookmarkDict:(NSDictionary*)bookmarkDict
{
    SRBookmark* bookmark = nil;
    
    // Get bookmark info
    NSString*   type;
    NSString*   title;
    NSString*   URLString;
    NSString*   iconURLString;
    NSString*   UUID;
    type = [bookmarkDict objectForKey:@"Type"];
    title = [bookmarkDict objectForKey:@"Title"];
    URLString = [bookmarkDict objectForKey:@"URLString"];
    iconURLString = [bookmarkDict objectForKey:@"IconURLString"];
    UUID = [bookmarkDict objectForKey:@"UUID"];
    
    // For HTML
    if ([type isEqualToString:@"HTML"]) {
        bookmark = [SRBookmark bookmarkWithTitle:title 
                URLString:URLString 
                originalBrowser:SRBrowserShiira];
        
        // Get icon URL string
        if (!iconURLString) {
            iconURLString = [[WebIconDatabase sharedIconDatabase] iconURLOfURL:URLString];
        }
        
        // Set icon URL string
        if (iconURLString) {
            [bookmark setIconURLString:iconURLString];
        }
    }
    // For folder
    else if ([type isEqualToString:@"Folder"]) {
        bookmark = [SRBookmark folderWithTitle:title 
                originalBrowser:SRBrowserShiira];
        
        BOOL        isAutoTab = NO;
        NSNumber*   number;
        if (number = [bookmarkDict objectForKey:@"AutoTab"]) {
            isAutoTab = [number boolValue];
        }
        [bookmark setAutoTab:isAutoTab];
    }
    // For bookmarks bar
    else if ([type isEqualToString:@"BookmarksBar"]) {
        bookmark = [SRBookmark bookmarksBar];
        _shiiraBookmarksBar = bookmark;
    }
    
    // Set UUID
    [bookmark setUUID:UUID];
    
    // Get children
    NSArray*    children;
    children = [bookmarkDict objectForKey:@"Children"];
    if (children) {
        // Enumerate children
        NSEnumerator*   enumerator;
        NSDictionary*   child;
        enumerator = [children objectEnumerator];
        while (child = [enumerator nextObject]) {
            // Create child bookmark
            SRBookmark* childBookmark;
            childBookmark = [self _convertShiiraBookmarkDict:child];
            
            if (childBookmark) {
                // Add child
                [bookmark addChild:childBookmark];
            }
        }
    }
    
    return bookmark;
}

- (void)_readShiiraBookmarks
{
    // Get the paths of ~/Library/Shiira/Bookmarks.plist
    NSArray*	libraryPaths;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    
    NSString*	shiiraBookmarkPath;
    shiiraBookmarkPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:@"Shiira/Bookmarks.plist"];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:shiiraBookmarkPath]) {
        NSLog(@"Could not find Shiira bookmark at %@", shiiraBookmarkPath);
        
        // Add Shiira default bookmark
        [_shiiraBookmarks release];
        _shiiraBookmarks = [[SRBookmark 
                folderWithTitle:NSLocalizedStringFromTable(@"Bookmarks", @"Bookmark", nil) 
                originalBrowser:SRBrowserShiira] retain];
        _shiiraBookmarksBar = [SRBookmark bookmarksBar];
        [_shiiraBookmarks addChild:_shiiraBookmarksBar];
        
        return;
    }
    
    // Read bookmark plist
    NSDictionary*   bookmarkDict;
    bookmarkDict = [NSDictionary dictionaryWithContentsOfFile:shiiraBookmarkPath];
    if (!bookmarkDict) {
        NSLog(@"Could not read Shiira bookmark file");
        
        // Add Shiira default bookmark
        [_shiiraBookmarks release];
        _shiiraBookmarks = [[SRBookmark 
                folderWithTitle:NSLocalizedStringFromTable(@"Bookmarks", @"Bookmark", nil) 
                originalBrowser:SRBrowserShiira] retain];
        _shiiraBookmarksBar = [SRBookmark bookmarksBar];
        [_shiiraBookmarks addChild:_shiiraBookmarksBar];
        
        return;
    }
    
    // Convert plist to Shiira bookmark
    [_shiiraBookmarks release];
    _shiiraBookmarks = [[self _convertShiiraBookmarkDict:bookmarkDict] retain];
    
    // Check existence bookmarks bar and bookmarks menu
    if (!_shiiraBookmarksBar) {
        // Create Shiira default bookmarks bar
        _shiiraBookmarksBar = [SRBookmark bookmarksBar];
        [_shiiraBookmarks insertChild:_shiiraBookmarksBar atIndex:0];
    }
    
#if 0
    // Check index of bookmarks bar and bookmarks menu
    NSMutableArray* rootBookmarks;
    rootBookmarks = [_bookmarks objectForKey:SRBookmarkChildrenName];
    if ([rootBookmarks indexOfObject:_bookmarksBar] != 0) {
        [_bookmarksBar retain];
        [rootBookmarks removeObject:_bookmarksBar];
        [rootBookmarks insertObject:_bookmarksBar atIndex:0];
        [_bookmarksBar release];
    }
    if ([rootBookmarks indexOfObject:_bookmarksMenu] != 1) {
        [_bookmarksMenu retain];
        [rootBookmarks removeObject:_bookmarksMenu];
        [rootBookmarks insertObject:_bookmarksMenu atIndex:1];
        [_bookmarksMenu release];
    }
#endif
}

- (SRBookmark*)_convertSafariBookmarkDict:(NSDictionary*)bookmarkDict
{
    SRBookmark* bookmark = nil;
    
    // Get bookmark info
    NSString*   type;
    type = [bookmarkDict objectForKey:_SafariBookmarkTypeName];
    
    // For list
    if ([type isEqualToString:_SafariBookmarksTypeListName] || 
        [type isEqualToString:_SafariBookmarksBarName] || 
        [type isEqualToString:_SafariBookmarksMenuName])
    {
		// Get title
        NSString*   title;
        NSString*   localizedTitle;
        title = [bookmarkDict objectForKey:_SafariBookmarkTitleName];
        localizedTitle = title;
        if (!localizedTitle) {
            if ([bookmarkDict objectForKey:_SafariBookmarkVersion]) {
                // This is root
                localizedTitle = NSLocalizedStringFromTable(
                        @"SafariBookmarks", @"Bookmark", @"Safari bookmarks");
            }
            else {
                localizedTitle = NSLocalizedString(@"Untitled", @"Untitled");
            }
        }
        else if([localizedTitle isEqualToString:_SafariBookmarksBarName]) {
            // Safari bookmarks bar
            localizedTitle = NSLocalizedStringFromTable(
                    @"SafariBookmarksBar", @"Bookmark", @"Safari bookmarks");
        }
        else if([localizedTitle isEqualToString:_SafariBookmarksMenuName]) {
            // Safari bookmarks menu
            localizedTitle = NSLocalizedStringFromTable(
                    @"SafariBookmarksMenu", @"Bookmark", @"Safari bookmarks");
        }
        
        // Get auto tab
        NSNumber*   isAutoTab;
        isAutoTab = [bookmarkDict objectForKey:_SafariBookmarkAutoTabName];
        
        // Get UUID
        NSString*   UUID;
        UUID = [bookmarkDict objectForKey:_SafariBookmarkUUIDName];
        
        // Create bookmark
        bookmark = [SRBookmark folderWithTitle:localizedTitle 
                originalBrowser:SRBrowserSafari];
        if (isAutoTab) {
            [bookmark setAutoTab:[isAutoTab boolValue]];
        }
        if (UUID) {
            [bookmark setUUID:UUID];
        }
        if ([title isEqualToString:_SafariBookmarksBarName]) {
            _safariBookmarksBar = bookmark;
        }
        if ([title isEqualToString:_SafariBookmarksMenuName]) {
            _safariBookmarksMenu = bookmark;
        }
        
        // Copy children
        NSArray*        children;
        NSEnumerator*   enumerator;
        NSDictionary*   child;
        children = [bookmarkDict objectForKey:_SafariBookmarkChildrenName];
        enumerator = [children objectEnumerator];
        while (child = [enumerator nextObject]) {
            // Convert child
            SRBookmark* childBookmark;
            childBookmark = [self _convertSafariBookmarkDict:child];
            if (childBookmark) {
                // Add child
                [bookmark addChild:childBookmark];
            }
        }
        
        // Make folder immutable
        [bookmark setMutable:NO];
    }
    
    // For leaf
    else if ([type isEqualToString:_SafariBookmarksTypeLeafName]) {
		// Get title, URLString and UUID
        NSString*   title;
        NSString*   URLString;
        NSString*   UUID;
        title = [[bookmarkDict objectForKey:_SafariBookmarkURIDictionaryName] 
                objectForKey:_SafariBookmarkURITitleName];
        URLString = [bookmarkDict objectForKey:_SafariBookmarkURLName];
        UUID = [bookmarkDict objectForKey:_SafariBookmarkUUIDName];
        
        if (title && URLString) {
            // Create bookmark
            bookmark = [SRBookmark bookmarkWithTitle:title 
                    URLString:URLString 
                    originalBrowser:SRBrowserSafari];
            [bookmark setMutable:NO];
            if (UUID) {
                [bookmark setUUID:UUID];
            }
        }
	}
	
    return bookmark;
}

- (void)_readSafariBookmarks
{
    // Get the paths of ~/Library/Safari/Bookmarks.plist
    NSArray*	libraryPaths;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    
    NSString*	safariBookmarkPath;
    safariBookmarkPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:@"Safari/Bookmarks.plist"];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:safariBookmarkPath]) {
        NSLog(@"Could not find Safari bookmark at %@", safariBookmarkPath);
        return;
    }
    
    // Read bookmark dictionary
    NSDictionary*   bookmarkDict;
    bookmarkDict = [NSDictionary dictionaryWithContentsOfFile:safariBookmarkPath];
    if (!bookmarkDict) {
        NSLog(@"Could not read Safari bookmark file");
        return;
    }
    
    // Convert Safari bookmark
    [_safariBookmarks release];
    _safariBookmarks = [[self _convertSafariBookmarkDict:bookmarkDict] retain];
}

- (NSString*)_removeTags:(NSArray*)tags fromHtml:(NSString*)html
{
    NSMutableString*    buffer;
    buffer = [NSMutableString string];
    
    NSScanner*  scanner;
    scanner = [NSScanner scannerWithString:html];
    [scanner setCharactersToBeSkipped:nil];
    while (![scanner isAtEnd]) {
        // Scan '<'
        NSString*   token;
        if ([scanner scanUpToString:@"<" intoString:&token]) {
            [buffer appendString:token];
        }
        
        // Scan '>'
        NSString*   tag;
        if ([scanner scanUpToString:@">" intoString:&tag]) {
            // Append tag if it is not contained in tags
            tag = [tag stringByAppendingString:@">"];
            if (![tags containsObject:tag]) {
                [buffer appendString:tag];
            }
            [scanner scanString:@">" intoString:nil];
        }
    }
    
    return buffer;
}

- (void)_readFirefoxBookmarks
{
    // Get the paths of ~/Library/Application Support/Firefox/profiles.ini
    NSArray*	libraryPaths;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    
    NSString*	profilesPath;
    profilesPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:@"Application Support/Firefox/profiles.ini"];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:profilesPath]) {
        //SR_WARNING(@"Could not find Firefox profiles at %@", profilesPath);
        _hasFirefoxBookmark = NO;
        return;
    }
    
    // Parse profiles.ini
    NSString*   profiles;
    profiles = [[NSString alloc] 
            initWithData:[NSData dataWithContentsOfFile:profilesPath] encoding:NSUTF8StringEncoding];
    if (!profiles) {
        SR_WARNING(@"Could not read Firefox profiles");
        _hasFirefoxBookmark = NO;
        return;
    }
    [profiles autorelease];
    
    NSScanner*  scanner;
    NSString*   profilePath = nil;
    scanner = [NSScanner scannerWithString:profiles];
    while (![scanner isAtEnd]) {
        NSString*   token;
        if ([scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] 
                intoString:&token])
        {
            if ([token hasPrefix:@"Path="]) {
                // Remove 'Path='
                profilePath = [token substringFromIndex:5];
                break;
            }
        }
        
        [scanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:nil];
    }
    if (!profilePath) {
        SR_WARNING(@"Could not read Firefox profile path");
        _hasFirefoxBookmark = NO;
        return;
    }
    
    // Get bookmarks path
    NSString*   bookmarksPath;
    bookmarksPath = [[profilesPath stringByDeletingLastPathComponent] 
            stringByAppendingPathComponent:profilePath];
    bookmarksPath = [bookmarksPath stringByAppendingPathComponent:@"bookmarks.html"];
    if (![fileMgr fileExistsAtPath:bookmarksPath]) {
        SR_WARNING(@"Could not find Firefox bookmarks at %@", bookmarksPath);
        _hasFirefoxBookmark = NO;
        return;
    }
    
    // We found firefox bookmark
    _hasFirefoxBookmark = YES;
    
    // If firefox bookmark is not used, return
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    NSArray*    isUsing;
    isUsing = [defaults objectForKey:SRBookmarkIsUsing];
    if (isUsing && 
        [isUsing count] > SRBrowserFirefox && 
        ![[isUsing objectAtIndex:SRBrowserFirefox] boolValue])
    {
        return;
    }
    
    // Remove unneccessary tags
    static NSArray* _tags = nil;
    if (!_tags) {
        _tags = [[NSArray arrayWithObjects:@"<p>", @"<P>", @"<dd>", @"<DD>", @"<hr>", @"<HR>", nil] retain];
    }
    
    NSString*   html;
    html = [[NSString alloc] 
            initWithData:[NSData dataWithContentsOfFile:bookmarksPath] encoding:NSUTF8StringEncoding];
    [html autorelease];
    html = [self _removeTags:_tags fromHtml:html];
    
    // Start HTML parsing
    if (_firefoxBoookmarkWebView) {
        [_firefoxBoookmarkWebView stopLoading:self];
        [_firefoxBoookmarkWebView release];
    }
    _firefoxBoookmarkWebView = [[WebView alloc] initWithFrame:NSZeroRect frameName:nil groupName:nil];
    [_firefoxBoookmarkWebView setFrameLoadDelegate:self];
    [[_firefoxBoookmarkWebView mainFrame] loadHTMLString:html baseURL:nil];
}

- (void)_readIEBookmarks
{
    // Get the paths of ~/Library/Preferences/Explorer/Favorites.html
    NSArray*	libraryPaths;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    
    NSString*	favoritesPath;
    favoritesPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:@"Preferences/Explorer/Favorites.html"];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:favoritesPath]) {
        SR_WARNING(@"Could not find IE favorites %@", favoritesPath);
        _hasIEBookmark = NO;
        return;
    }
    
    // Remove unneccessary tags
    static NSArray* _tags = nil;
    if (!_tags) {
        _tags = [[NSArray arrayWithObjects:@"<p>", @"<P>", @"<dd>", @"<DD>", @"<hr>", @"<HR>", nil] retain];
    }
    
    NSString*   html;
    html = [[NSString alloc] 
//            initWithData:[NSData dataWithContentsOfFile:favoritesPath] encoding:NSUTF8StringEncoding];
            initWithData:[NSData dataWithContentsOfFile:favoritesPath] encoding:NSShiftJISStringEncoding];
    [html autorelease];
    html = [self _removeTags:_tags fromHtml:html];
    
    // Start HTML parsing
    _hasIEBookmark = YES;
    if (_ieBoookmarkWebView) {
        [_ieBoookmarkWebView stopLoading:self];
        [_ieBoookmarkWebView release];
    }
    _ieBoookmarkWebView = [[WebView alloc] initWithFrame:NSZeroRect frameName:nil groupName:nil];
    [_ieBoookmarkWebView setFrameLoadDelegate:self];
    [[_ieBoookmarkWebView mainFrame] loadHTMLString:html baseURL:nil];
}

- (void)_addFirefoxBookmarksFromDOMHTMLDListElement:(DOMHTMLDListElement*)dlist intoBookmark:(SRBookmark*)folder
{
    // Get children
    DOMNodeList*    children;
    children = [dlist childNodes];
    if (!children) {
        return;
    }
    
    unsigned long   i;
    for (i = 0; i < [children length]; i++) {
        SRBookmark* bookmark = nil;
        
        // Get DT child
        DOMNode*    dt;
        dt = [children item:i];
        if (!dt || ![[dt nodeName] isEqualToString:@"DT"]) {
            continue;
        }
        
        // When DT has H3, it is a folder
        DOMHTMLElement* h3;
        h3 = (DOMHTMLElement*)[dt firstChildOfNodeName:@"H3"];
        if (h3) {
            // Get title
            NSString*   title;
            title = [h3 innerText];
            
            // Create bookmark
            bookmark = [SRBookmark folderWithTitle:title 
                    originalBrowser:SRBrowserFirefox];
            
            // Get dlist
            DOMHTMLDListElement*    dl;
            dl = (DOMHTMLDListElement*)[dt firstChildOfClass:[DOMHTMLDListElement class]];
            if (dl) {
                [self _addFirefoxBookmarksFromDOMHTMLDListElement:dl intoBookmark:bookmark];
            }
            
            // Make folder immutable
            [bookmark setMutable:NO];
            [folder addChild:bookmark];
            
            // Check PERSONAL_TOOLBAR_FOLDER attribute
            if ([[h3 getAttribute:@"PERSONAL_TOOLBAR_FOLDER"] isEqualToString:@"true"]) {
                _firefoxBookmarksBar = bookmark;
            }
            
            continue;
        }
        
        // Otherwise, when DT ahs A, it is a leaf
        DOMHTMLAnchorElement*   a;
        a = (DOMHTMLAnchorElement*)[dt firstChildOfClass:[DOMHTMLAnchorElement class]];
        if (a) {
            // Get title and URL string
            NSString*   title;
            NSString*   URLString;
            title = [a innerText];
            URLString = [a href];
            
            // Create bookmark
            if (title && URLString) {
                bookmark = [SRBookmark bookmarkWithTitle:title 
                        URLString:URLString 
                        originalBrowser:SRBrowserFirefox];
                [bookmark setMutable:NO];
                [folder addChild:bookmark];
            }
        }
    }
}

- (void)_createFirefoxBookmarksFromDOMDocument:(DOMHTMLDocument*)document
{
    // Create root bookmark for Firefox
    NSString*   localizedTitle;
    localizedTitle = NSLocalizedStringFromTable(
            @"FirefoxBookmarks", @"Bookmark", @"Firefox bookmarks");
    [_firefoxBookmarks release];
    _firefoxBookmarks = [[SRBookmark folderWithTitle:localizedTitle originalBrowser:SRBrowserFirefox] retain];
    
    // Get body
    DOMHTMLElement* body;
    body = [document body];
    
    // Get DL node
    DOMNode*    node;
    node = [body firstChildOfClass:[DOMHTMLDListElement class]];
    if (node) {
    _notifyFlag = NO;
        // Create Firefox bookmarks
        [self _addFirefoxBookmarksFromDOMHTMLDListElement:(DOMHTMLDListElement*)node 
                intoBookmark:_firefoxBookmarks];
    _notifyFlag = YES;
    }
    
    // Reconstruct bookmarks
    [self _reconstructBookmarks];
}

- (void)_createIEBookmarksFromDOMDocument:(DOMHTMLDocument*)document
{
    _notifyFlag = NO;
    
    // Create root bookmark for IE
    NSString*   localizedTitle;
    localizedTitle = NSLocalizedStringFromTable(
            @"IEBookmarks", @"Bookmark", @"Internet Explorer bookmarks");
    [_ieBookmarks release];
    _ieBookmarks = [[SRBookmark folderWithTitle:localizedTitle originalBrowser:SRBrowserIE] retain];
    
    // Get body
    DOMHTMLElement* body;
    body = [document body];
    
    // Get DL node
    DOMNode*    node;
    node = [body firstChildOfClass:[DOMHTMLDListElement class]];
    if (node) {
        // Create Firefox bookmarks
        [self _addFirefoxBookmarksFromDOMHTMLDListElement:(DOMHTMLDListElement*)node 
                intoBookmark:_ieBookmarks];
    }
    _notifyFlag = YES;
    
    // Reconstruct bookmarks
    [self _reconstructBookmarks];
}

//--------------------------------------------------------------//
#pragma mark -- Loading bookmark --
//--------------------------------------------------------------//

- (void)reloadBookmarksOfBrowser:(int)browser
{
    // For Shiira
    if (browser == SRBrowserShiira) {
        [self _readShiiraBookmarks];
        
        // Notify bookmark change
        NSNotification* notification;
        notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
                object:_rootBookmark];
        [self notifyBookmarkUpdated:notification];
        
        return;
    }
    
    // For Safari
    if (browser == SRBrowserSafari) {
        [self _readSafariBookmarks];
        
        // Notify bookmark change
        NSNotification* notification;
        notification = [NSNotification notificationWithName:SRBookmarkAddedNotificationName 
                object:_rootBookmark];
        [self notifyBookmarkUpdated:notification];
        
        return;
    }
    
    // For Firefix
    if (browser == SRBrowserFirefox) {
        [self _readFirefoxBookmarks];
        return;
    }
    
    // For Camino
    if (browser == SRBrowserCamino) {
        return;
    }
    
    // For Internet Explorer
    if (browser == SRBrowserIE) {
        //[self _readIEBookmarks];
        return;
    }
}

- (void)reloadBookmarksOfAllBrowsers
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    [self reloadBookmarksOfBrowser:SRBrowserShiira];
    [self reloadBookmarksOfBrowser:SRBrowserSafari];
    [self reloadBookmarksOfBrowser:SRBrowserFirefox];
    [self reloadBookmarksOfBrowser:SRBrowserCamino];
    //[self reloadBookmarksOfBrowser:SRBrowserIE];
}

- (NSDictionary*)_copyForSaveShiiraBookmark:(SRBookmark*)bookmark
{
    // Check browser type
    if ([bookmark originalBrowser] != SRBrowserShiira) {
        return nil;
    }
    
    // Get type
    int type;
    type = [bookmark type];
    
    // For folder
    if ([bookmark isFolderType]) {
        // Copy bookmark
        NSString*               title;
        BOOL                    isAutoTab;
        NSMutableDictionary*    copiedBookmark;
        NSMutableArray*         copiedChildren;
        NSString*               UUID;
        
        title = [bookmark title];
        isAutoTab = [bookmark isAutoTab];
        copiedBookmark = [NSMutableDictionary dictionary];
        copiedChildren = [NSMutableArray array];
        UUID = [bookmark UUID];
        
        [copiedBookmark setObject:_typeStr[type] forKey:@"Type"];
        [copiedBookmark setObject:title forKey:@"Title"];
        [copiedBookmark setObject:copiedChildren forKey:@"Children"];
        [copiedBookmark setObject:[NSNumber numberWithBool:isAutoTab] forKey:@"AutoTab"];
        [copiedBookmark setObject:UUID forKey:@"UUID"];
        
        // Copy children
        NSArray*        children;
        NSEnumerator*   enumerator;
        SRBookmark*     child;
        children = [bookmark children];
        enumerator = [children objectEnumerator];
        while (child = [enumerator nextObject]) {
            // Copy child
            NSDictionary*   copiedChild;
            copiedChild = [self _copyForSaveShiiraBookmark:child];
            if (copiedChild) {
                [copiedChildren addObject:copiedChild];
            }
        }
        
        return copiedBookmark;
    }
    
    // For HTML
    else {
        // Get title and URL string
        NSString*   title;
        NSString*   URLString;
        NSString*   iconURLString;
        NSString*   UUID;
        title = [bookmark title];
        URLString = [bookmark URLString];
        iconURLString = [bookmark iconURLString];
        UUID = [bookmark UUID];
        
        // Copy bookmark
        NSMutableDictionary*    copiedBookmark;
        copiedBookmark = [NSMutableDictionary dictionary];
        [copiedBookmark setObject:_typeStr[type] forKey:@"Type"];
        [copiedBookmark setObject:title forKey:@"Title"];
        [copiedBookmark setObject:URLString forKey:@"URLString"];
        if (iconURLString) {
            [copiedBookmark setObject:iconURLString forKey:@"IconURLString"];
        }
        [copiedBookmark setObject:UUID forKey:@"UUID"];
        
        return copiedBookmark;
    }
    
    return nil;
}

- (BOOL)saveShiiraBookmarksToURL:(NSURL*)URL error:(NSError**)error
{
    // Get the paths of ~/Library/Shiira/Bookmarks.plist
    NSArray*	libraryPaths;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    
    NSString*	shiiraBookmarkPath;
    shiiraBookmarkPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:@"Shiira/Bookmarks.plist"];
    
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:shiiraBookmarkPath]) {
        NSLog(@"Could not find Shiira bookmark at %@", shiiraBookmarkPath);
        return NO;
    }
    
    // Copy Shiira bookmarks
    NSDictionary*   copiedBookmarks;
    copiedBookmarks = [self _copyForSaveShiiraBookmark:_rootBookmark];
    if (!copiedBookmarks) {
        return NO;
    }
    
    // Save to file
    if (![copiedBookmarks writeToURL:URL atomically:YES]) {
        return NO;
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- Accessing to bookmark --
//--------------------------------------------------------------//

- (SRBookmark*)rootBookmark
{
    return _rootBookmark;
}

- (BOOL)hasBookmarkOfBrowser:(int)browser
{
    switch (browser) {
    case SRBrowserShiira: {
        // Always YES
        return YES;
    }
    case SRBrowserSafari: {
        // Always YES
        return YES;
    }
    case SRBrowserFirefox: {
        return _hasFirefoxBookmark;
    }
#if 0
    case SRBrowserCamino: {
    }
#endif
    case SRBrowserIE: {
        return _hasIEBookmark;
    }
#if 0
    case SRBrowserOmniWeb: {
    }
    case SRBrowserOpera: {
    }
    case SRBrowserICab: {
    }
#endif
    }
    
    return NO;
}

- (SRBookmark*)shiiraBookmarksBar
{
    return _shiiraBookmarksBar;
}

- (NSArray*)shiiraBookmarksMenu
{
    NSMutableArray* bookmarks;
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    bookmarks = [NSMutableArray array];
    enumerator = [[_rootBookmark children] objectEnumerator];
    while (child = [enumerator nextObject]) {
        if ([child originalBrowser] == SRBrowserShiira && child != _shiiraBookmarksBar) {
            [bookmarks addObject:child];
        }
    }
    
    return bookmarks;
}

- (SRBookmark*)safariBookmarksBar
{
    return _safariBookmarksBar;
}

- (SRBookmark*)safariBookmarksMenu
{
    return _safariBookmarksMenu;
}

- (SRBookmark*)firefoxBookmarks
{
    return _firefoxBookmarks;
}

- (SRBookmark*)firefoxBookmarksBar
{
    return _firefoxBookmarksBar;
}

- (SRBookmark*)ieBookmarksBar
{
    return _ieBookmarksBar;
}

- (SRBookmark*)ieBookmarksMenu
{
    return _ieBookmarksMenu;
}

- (void)_folderMenuOfBookmark:(SRBookmark*)bookmark 
        menu:(NSMenu*)menu 
        indentationLevel:(int)level
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed, isFaviconUsedBookmarkMenu;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    isFaviconUsedBookmarkMenu = [defaults boolForKey:SRIconUseFaviconBookmarkMenu];
    
    // Check type and mutability
    if (![bookmark isFolderType] || ![bookmark isMutable]) {
        return;
    }
    
    // Create menu item
    NSMenuItem* item;
    item = [[NSMenuItem alloc] initWithTitle:[bookmark title] 
            action:nil 
            keyEquivalent:@""];
    [item autorelease];
    [item setIndentationLevel:level];
    [item setRepresentedObject:bookmark];
    if (isFaviconUsed && isFaviconUsedBookmarkMenu) {
        [item setImage:[bookmark icon]];
    }
    
    // Add menu item
    [menu addItem:item];
    
    // Check children
    NSArray*        children;
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    children = [bookmark children];
    enumerator = [children objectEnumerator];
    while (child = [enumerator nextObject]) {
        [self _folderMenuOfBookmark:child 
                menu:menu 
                indentationLevel:level + 1];
    }
}

- (NSMenu*)bookmarkFolderMenu
{
    // Create menu
    NSMenu* menu;
    menu = [[NSMenu alloc] initWithTitle:@""];
    [menu autorelease];
    
    // Create folder menu
    [self _folderMenuOfBookmark:_rootBookmark 
            menu:menu 
            indentationLevel:0];
    
    return menu;
}

- (SRBookmark*)parentOfBookmark:(SRBookmark*)bookmark 
        fromRoot:(SRBookmark*)rootBookmark
{
    // Check type
    if (![rootBookmark isFolderType]) {
        return nil;
    }
    
    // Get children
    NSArray*    children;
    children = [rootBookmark children];
    
    // Find bookmark
    if ([children containsObject:bookmark]) {
        return rootBookmark;
    }
    
    // Find in children
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    enumerator = [children objectEnumerator];
    while (child = [enumerator nextObject]) {
        SRBookmark* parent;
        parent = [self parentOfBookmark:bookmark fromRoot:child];
        if (parent) {
            return parent;
        }
    }
    
    return nil;
}

- (SRBookmark*)parentOfBookmark:(SRBookmark*)bookmark
{
    return [self parentOfBookmark:bookmark fromRoot:_rootBookmark];
}

//--------------------------------------------------------------//
#pragma mark -- Find bookmark --
//--------------------------------------------------------------//

- (void)_findBookmarkWithString:(NSString*)findString 
        fromBookmark:(SRBookmark*)bookmark 
        intoResults:(NSMutableArray*)results
{
    // Check bookmark
    NSString*   title;
    NSString*   URLString;
    title = [bookmark title];
    URLString = [bookmark URLString];
    if (title && 
        [title rangeOfString:findString options:NSCaseInsensitiveSearch].location != NSNotFound)
    {
        [results addObject:bookmark];
    }
    else if (URLString && 
             [URLString rangeOfString:findString options:NSCaseInsensitiveSearch].location != NSNotFound)
    {
        [results addObject:bookmark];
    }
    
    if (![bookmark isFolderType]) {
        return;
    }
    
    // Find in children
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    enumerator = [[bookmark children] objectEnumerator];
    while (child = [enumerator nextObject]) {
        [self _findBookmarkWithString:findString 
                fromBookmark:child 
                intoResults:results];
    }
}

- (NSArray*)findBookmarkWithString:(NSString*)findString
{
    // Create array for results
    NSMutableArray* results;
    results = [NSMutableArray array];
    
    // Find string in root
    [self _findBookmarkWithString:findString 
            fromBookmark:_rootBookmark 
            intoResults:results];
    
    return results;
}

//--------------------------------------------------------------//
#pragma mark -- Notification --
//--------------------------------------------------------------//

- (BOOL)notifyFlag
{
    return _notifyFlag;
}

- (void)setNotifyFlag:(BOOL)flag
{
    _notifyFlag = flag;
}

- (void)notifyBookmarkUpdated:(NSNotification*)notification
{
    if (_notifyFlag) {
        [[NSNotificationCenter defaultCenter] 
                postNotification:notification];
    }
}

//--------------------------------------------------------------//
#pragma mark -- WebFrameLoad delegate --
//--------------------------------------------------------------//

- (void)webView:(WebView*)webView didFinishLoadForFrame:(WebFrame*)frame
{
    // For Firefox
    if (webView == _firefoxBoookmarkWebView) {
        // Get DOM document
        DOMDocument*    document;
        document = [frame DOMDocument];
        if (!document || ![document isKindOfClass:[DOMHTMLDocument class]]) {
            _hasFirefoxBookmark = NO;
            return;
        }
        
        _hasFirefoxBookmark = YES;
        [self _createFirefoxBookmarksFromDOMDocument:(DOMHTMLDocument*)document];
    }
    // For IE
    if (webView == _ieBoookmarkWebView) {
        // Get DOM document
        DOMDocument*    document;
        document = [frame DOMDocument];
        if (!document || ![document isKindOfClass:[DOMHTMLDocument class]]) {
            _hasIEBookmark = NO;
            return;
        }
        
        _hasIEBookmark = YES;
        [self _createIEBookmarksFromDOMDocument:(DOMHTMLDocument*)document];
    }
}

- (void)_webView:(WebView*)webView didError:(NSError*)error forFrame:(WebFrame*)frame
{
    // For Firefox
    if (webView == _firefoxBoookmarkWebView) {
        _hasFirefoxBookmark = NO;
        [_firefoxBoookmarkWebView release];
        _firefoxBoookmarkWebView = nil;
    }
    // For IE
    if (webView == _ieBoookmarkWebView) {
        _hasIEBookmark = NO;
        [_ieBoookmarkWebView release];
        _ieBoookmarkWebView = nil;
    }
}

- (void)webView:(WebView*)webView didFailProvisionalLoadWithError:(NSError*)error forFrame:(WebFrame*)frame
{
    [self _webView:webView didError:error forFrame:frame];
}

- (void)webView:(WebView*)webView didFailLoadWithError:(NSError*)error forFrame:(WebFrame*)frame
{
    [self _webView:webView didError:error forFrame:frame];
}

//--------------------------------------------------------------//
#pragma mark -- Key value observation --
//--------------------------------------------------------------//

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void *)context
{
    // Bookmark preferences
    if ([keyPath isEqualToString:SRBookmarkIsUsing]) {
        [self _reconstructBookmarks];
    }
}

@end

#pragma mark -

// Utility
NSMenu* SRCreateBookmarkMenu(
        SRBookmark* bookmark, 
        BOOL isExpandChild, 
        BOOL isFaviconAvailable)
{
    return SRCreateBookmarkMenuFromArray([bookmark children], isExpandChild, isFaviconAvailable);
}

NSMenu* SRCreateBookmarkMenuFromArray(
        NSArray* bookmarks, 
        BOOL isExpandChild, 
        BOOL isFaviconAvailable)
{
    // Create menu
    NSMenu* menu;
    menu = [[NSMenu alloc] initWithTitle:@""];
    [menu autorelease];
    
    NSEnumerator*   enumerator;
    SRBookmark*     child;
    enumerator = [bookmarks objectEnumerator];
    while (child = [enumerator nextObject]) {
        NSMenu* childMenu = nil;
        
        // Check child type
        if ([child isFolderType]) {
            if (!isExpandChild) {
                continue;
            }
            
            // Create child menu
            childMenu = SRCreateBookmarkMenu(child, isExpandChild, isFaviconAvailable);
        }
        
        // Get title and icon
        NSString*   title;
        NSImage*    icon;
        title = [child title];
        icon = [child icon];
        
        if (title && icon) {
            // Create NSMenuItem
            NSMenuItem* item;
            item = [[NSMenuItem alloc] initWithTitle:title 
                    action:@selector(openBookmarkAction:) 
                    keyEquivalent:@""];
            [item autorelease];
            if (isFaviconAvailable) {
                [item setImage:icon];
            }
            [item setRepresentedObject:child];
            if (childMenu) {
                [item setSubmenu:childMenu];
            }
            
            // Add item
            [menu addItem:item];
        }
    }
    
    return menu;
}

NSMenu* SRCreateHistoryMenu(
        NSArray* historyItems, 
        BOOL reverse)
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed, isFaviconUsedBookmarkMenu;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    isFaviconUsedBookmarkMenu = [defaults boolForKey:SRIconUseFaviconBookmarkMenu];
    
    // Create menu
    NSMenu* menu;
    menu = [[NSMenu alloc] initWithTitle:@""];
    [menu autorelease];
    
    NSEnumerator*   enumerator;
    WebHistoryItem* historyItem;
    if (reverse) {
        enumerator = [historyItems reverseObjectEnumerator];
    }
    else {
        enumerator = [historyItems objectEnumerator];
    }
    while (historyItem = [enumerator nextObject]) {
        // Get title and icon
        NSString*   title;
        NSImage*    icon;
        title = [historyItem title];
        icon = [historyItem icon];
        
        if (title && icon) {
            // Create NSMenuItem
            NSMenuItem* item;
            item = [[NSMenuItem alloc] initWithTitle:title 
                    action:@selector(openHistoryItemAction:) 
                    keyEquivalent:@""];
            [item autorelease];
            if (isFaviconUsed && isFaviconUsedBookmarkMenu) {
                [item setImage:icon];
            }
            [item setRepresentedObject:historyItem];
            
            // Add item
            [menu addItem:item];
        }
    }
    
    return menu;
}

NSString* SRStringRepresentaionOfBookmarks(
        NSArray* bookmarks)
{
    // Create string representation
    NSMutableString*    string;
    NSEnumerator*       enumerator;
    SRBookmark*         bookmark;
    string = [NSMutableString string];
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // For HTML
        if ([bookmark type] == SRBookmarkTypeHTML) {
            [string appendString:[bookmark URLString]];
        }
        // For folder
        else if ([bookmark isFolderType]) {
            [string appendString:[bookmark title]];
        }
        
        [string appendString:@"\n"];
    }
    if ([string hasSuffix:@"\n"]) {
        [string deleteCharactersInRange:NSMakeRange([string length] - 1, 1)];
    }
    
    return string;
}

id SRURLRepresentaionOfBookmarks(
        NSArray* bookmarks)
{
    NSMutableArray* array;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    array = [NSMutableArray array];
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // For HTML
        if ([bookmark type] == SRBookmarkTypeHTML) {
            [array addObject:[bookmark URLString]];
        }
        // For folder
        else if ([bookmark isFolderType]) {
        }
    }
    
    // Return XML data
    return [NSPropertyListSerialization dataFromPropertyList:array 
            format:NSPropertyListXMLFormat_v1_0 
            errorDescription:nil];
}

id SRWebURLsWithTitlesRepresentaionOfBookmarks(
        NSArray* bookmarks)
{
    NSMutableArray* URLs;
    NSMutableArray* titles;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    URLs = [NSMutableArray array];
    titles = [NSMutableArray array];
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // For HTML
        if ([bookmark type] == SRBookmarkTypeHTML) {
            // Get URL and title
            NSString*   URLString;
            NSString*   title;
            URLString = [bookmark URLString];
            title = [bookmark title];
            
            if (URLString && title) {
                [URLs addObject:URLString];
                [titles addObject:title];
            }
        }
        // For folder
        else if ([bookmark isFolderType]) {
        }
    }
    
    // Return by plist format
    return [NSPropertyListSerialization dataFromPropertyList:[NSArray arrayWithObjects:URLs, titles, nil] 
            format:NSPropertyListXMLFormat_v1_0 
            errorDescription:nil];
}

void SRSetURLRepresentationOfBookmarks(
        NSArray* bookmarks, 
        NSPasteboard* pboard)
{
    // Get first bookmark
    SRBookmark* bookmark;
    bookmark = [bookmarks objectAtIndex:0];
    
    // For HTML
    if ([bookmark type] == SRBookmarkTypeHTML) {
        NSURL*  url;
        url = [NSURL _web_URLWithUserTypedString:[bookmark URLString]];
        
        // Write it to pasteboard
        [url writeToPasteboard:pboard];
    }
}

NSString* SRCoreFlavor_url_RepresentaionOfBookmarks(
        NSArray* bookmarks)
{
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // For HTML
        if ([bookmark type] == SRBookmarkTypeHTML) {
            return [bookmark URLString];
        }
        // For folder
        else if ([bookmark isFolderType]) {
        }
    }
    
    return @"";
}

NSString* SRCoreFlavor_urln_RepresentaionOfBookmarks(
        NSArray* bookmarks)
{
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // For HTML
        if ([bookmark type] == SRBookmarkTypeHTML) {
            return [bookmark title];
        }
        // For folder
        else if ([bookmark isFolderType]) {
        }
    }
    
    return @"";
}

void SRWriteBookmarksToPasteboard(
        NSArray* bookmarks, 
        NSPasteboard* pboard)
{
    // Re-create bookmarks
    NSMutableArray* copiedBookmarks;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    copiedBookmarks = [NSMutableArray array];
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        SRBookmark* copiedBookmark;
        copiedBookmark = [SRBookmark bookmarkWithBookmark:bookmark deep:YES];
        
        [copiedBookmarks addObject:copiedBookmark];
    }
    
    // Declare paste board types
    NSArray*    types;
    types = [NSArray arrayWithObjects:
            NSStringPboardType, NSURLPboardType, /*NSFilesPromisePboardType,*/ SRBookmarkPboardType, 
            WebURLsWithTitlesPboardType, CorePasteboardFlavorType_url, CorePasteboardFlavorType_urln, nil];
    [pboard declareTypes:types owner:nil];
    
    // Create string data
    [pboard setString:SRStringRepresentaionOfBookmarks(copiedBookmarks) 
            forType:NSStringPboardType];
    
    // Create URL data
    SRSetURLRepresentationOfBookmarks(copiedBookmarks, pboard);
    
    // Create file promise data
    
    // Create bookmark data
    NSData*         bookmarkData;
    bookmarkData = [NSArchiver archivedDataWithRootObject:copiedBookmarks];
    [pboard setData:bookmarkData forType:SRBookmarkPboardType];
    
    // Create web URLs with titles data
    [pboard setData:SRWebURLsWithTitlesRepresentaionOfBookmarks(copiedBookmarks) 
            forType:WebURLsWithTitlesPboardType];
    
    // Create 'url ' data
    [pboard setString:SRCoreFlavor_url_RepresentaionOfBookmarks(copiedBookmarks) 
            forType:CorePasteboardFlavorType_url];
    
    // Create 'urln' data
    [pboard setString:SRCoreFlavor_urln_RepresentaionOfBookmarks(copiedBookmarks) 
            forType:CorePasteboardFlavorType_urln];
}

void SRWriteWebHistoryItemsToPasteboard(
        NSArray* historyItems, 
        NSPasteboard* pboard)
{
    // Create bookmarks from history items
    NSMutableArray* bookmarks;
    NSEnumerator*   enumerator;
    WebHistoryItem* historyItem;
    bookmarks = [NSMutableArray array];
    enumerator = [historyItems objectEnumerator];
    while (historyItem = [enumerator nextObject]) {
        SRBookmark* bookmark;
        bookmark = [SRBookmark bookmarkWithTitle:[historyItem title] 
                URLString:[historyItem URLString] 
                originalBrowser:SRBrowserShiira];
        
        [bookmarks addObject:bookmark];
    }
    
    // Declare paste board types
    NSArray*    types;
    types = [NSArray arrayWithObjects:
            NSStringPboardType, NSURLPboardType, /*NSFilesPromisePboardType,*/ SRBookmarkPboardType, 
            WebURLsWithTitlesPboardType, CorePasteboardFlavorType_url, CorePasteboardFlavorType_urln, nil];
    [pboard declareTypes:types owner:nil];
    
    // Create string data
    [pboard setString:SRStringRepresentaionOfBookmarks(bookmarks) 
            forType:NSStringPboardType];
    
    // Create URL data
    SRSetURLRepresentationOfBookmarks(bookmarks, pboard);
    
    // Create file promise data
    
    // Create bookmark data
    NSData*         bookmarkData;
    bookmarkData = [NSArchiver archivedDataWithRootObject:bookmarks];
    [pboard setData:bookmarkData forType:SRBookmarkPboardType];
    
    // Create web URLs with titles data
    [pboard setData:SRWebURLsWithTitlesRepresentaionOfBookmarks(bookmarks) 
            forType:WebURLsWithTitlesPboardType];
    
    // Create 'url ' data
    [pboard setString:SRCoreFlavor_url_RepresentaionOfBookmarks(bookmarks) 
            forType:CorePasteboardFlavorType_url];
    
    // Create 'urln' data
    [pboard setString:SRCoreFlavor_urln_RepresentaionOfBookmarks(bookmarks) 
            forType:CorePasteboardFlavorType_urln];
}

NSArray* SRReadBookmarksFromPasteboard(
        NSPasteboard* pboard)
{
    NSData* data;
    id      unarchivedObject;
    data = [pboard dataForType:SRBookmarkPboardType];
    unarchivedObject = [NSUnarchiver unarchiveObjectWithData:data];
    if (!unarchivedObject || ![unarchivedObject isKindOfClass:[NSArray class]]) {
        return nil;
    }
    
    // Re-create bookmarks
    NSMutableArray* copiedBookmarks;
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    copiedBookmarks = [NSMutableArray array];
    enumerator = [unarchivedObject objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        SRBookmark* copiedBookmark;
        copiedBookmark = [SRBookmark bookmarkWithBookmark:bookmark deep:YES];
        
        [copiedBookmarks addObject:copiedBookmark];
    }
    
    return copiedBookmarks;
}
