@@ -625,13 +625,6 @@ BookmarkItem CBookmarkDatabase::bookmarkFromQuery(const QSqlQuery &query)
625625 return item;
626626}
627627
628- bool CBookmarkDatabase::importFromHtml (const QString &filename)
629- {
630- // 实现HTML导入(Chrome/Firefox格式)
631- // 这里简化为从历史记录导入
632- return true ;
633- }
634-
635628bool CBookmarkDatabase::exportToHtml (const QString &filename)
636629{
637630 QFile file (filename);
@@ -665,11 +658,376 @@ bool CBookmarkDatabase::exportToHtml(const QString &filename)
665658 << " ADD_DATE=\" " << timestamp << " \" "
666659 << " LAST_VISIT=\" " << timestamp << " \" "
667660 << " LAST_MODIFIED=\" " << timestamp << " \" >"
668- << item.title << " </A>" << " \n " ;
661+ << item.title << " </A></DT> " << " \n " ;
669662 }
670663
671- out << " </DL><p >" << " \n " ;
664+ out << " </p></DL >" << " \n " ;
672665
673666 file.close ();
674667 return true ;
675668}
669+
670+ bool CBookmarkDatabase::importFromHtml (const QString &filename)
671+ {
672+ QFile file (filename);
673+ if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) {
674+ qWarning () << " Failed to open file for import:" << filename;
675+ return false ;
676+ }
677+
678+ QTextStream in (&file);
679+ #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
680+ in.setEncoding (QStringConverter::Utf8);
681+ #else
682+ in.setCodec (" UTF-8" );
683+ #endif
684+
685+ QString content = in.readAll ();
686+ file.close ();
687+
688+ // 开始事务
689+ m_database.transaction ();
690+
691+ try {
692+ int importedCount = parseHtmlBookmarks (content);
693+
694+ if (!m_database.commit ()) {
695+ throw QString (" Failed to commit transaction: %1" ).arg (m_database.lastError ().text ());
696+ }
697+
698+ emit bookmarksChanged ();
699+
700+ qDebug (log) << " Successfully imported" << importedCount << " bookmarks" ;
701+ return true ;
702+
703+ } catch (const QString &error) {
704+ m_database.rollback ();
705+ qWarning (log) << " Import failed:" << error;
706+ return false ;
707+ }
708+ }
709+
710+ int CBookmarkDatabase::parseHtmlBookmarks (const QString &htmlContent)
711+ {
712+ int importedCount = 0 ;
713+
714+ // 解析 HTML 书签文件
715+ QDomDocument doc;
716+ if (!doc.setContent (htmlContent)) {
717+ throw QString (" Invalid HTML format" );
718+ }
719+
720+ // 查找根节点
721+ QDomElement root = doc.documentElement ();
722+ if (root.isNull ()) {
723+ throw QString (" No root element found" );
724+ }
725+
726+ // 查找 DL 标签(书签列表)
727+ QDomElement dlElement = findFirstElement (root, " DL" );
728+ if (dlElement.isNull ()) {
729+ throw QString (" No bookmarks list found" );
730+ }
731+
732+ // 递归解析书签
733+ QMap<QString, int > folderMap; // 文件夹路径 -> 文件夹ID
734+ folderMap[" /" ] = 0 ; // 根目录
735+
736+ importedCount = parseBookmarkList (dlElement, " /" , folderMap);
737+
738+ return importedCount;
739+ }
740+
741+ int CBookmarkDatabase::parseBookmarkList (const QDomElement &dlElement,
742+ const QString ¤tPath,
743+ QMap<QString, int > &folderMap)
744+ {
745+ int importedCount = 0 ;
746+ QDomNode child = dlElement.firstChild ();
747+
748+ while (!child.isNull ()) {
749+ QDomElement element = child.toElement ();
750+
751+ if (!element.isNull ()) {
752+ QString tagName = element.tagName ().toUpper ();
753+
754+ if (tagName == " DT" ) {
755+ // 处理 DT 节点
756+ importedCount += parseDtElement (element, currentPath, folderMap);
757+ } else if (tagName == " DL" ) {
758+ // 嵌套的 DL 标签,继续解析
759+ importedCount += parseBookmarkList (element, currentPath, folderMap);
760+ }
761+ }
762+
763+ child = child.nextSibling ();
764+ }
765+
766+ return importedCount;
767+ }
768+
769+ int CBookmarkDatabase::parseDtElement (const QDomElement &dtElement,
770+ const QString ¤tPath,
771+ QMap<QString, int > &folderMap)
772+ {
773+ int importedCount = 0 ;
774+
775+ // 查找子节点
776+ QDomNode child = dtElement.firstChild ();
777+ while (!child.isNull ()) {
778+ QDomElement element = child.toElement ();
779+
780+ if (!element.isNull ()) {
781+ QString tagName = element.tagName ().toUpper ();
782+
783+ if (tagName == " A" ) {
784+ // 书签链接
785+ importedCount += importBookmark (element, currentPath);
786+ } else if (tagName == " H3" ) {
787+ // 文件夹
788+ importedCount += importFolder (element, currentPath, folderMap);
789+ } else if (tagName == " DL" ) {
790+ // 嵌套的书签列表
791+ importedCount += parseBookmarkList (element, currentPath, folderMap);
792+ }
793+ }
794+
795+ child = child.nextSibling ();
796+ }
797+
798+ return importedCount;
799+ }
800+
801+ int CBookmarkDatabase::importBookmark (const QDomElement &aElement,
802+ const QString &folderPath)
803+ {
804+ // 获取链接属性
805+ QString url = aElement.attribute (" HREF" );
806+ QString title = aElement.text ();
807+
808+ if (url.isEmpty () || title.isEmpty ()) {
809+ return 0 ;
810+ }
811+
812+ // 获取时间戳
813+ QString addDateStr = aElement.attribute (" ADD_DATE" );
814+ QString lastVisitStr = aElement.attribute (" LAST_VISIT" );
815+ QString lastModifiedStr = aElement.attribute (" LAST_MODIFIED" );
816+
817+ QDateTime addDate = parseTimestamp (addDateStr);
818+ QDateTime lastVisit = parseTimestamp (lastVisitStr);
819+ QDateTime lastModified = parseTimestamp (lastModifiedStr);
820+
821+ // 创建书签对象
822+ BookmarkItem item;
823+ item.url = url;
824+ item.title = title;
825+ item.createdTime = addDate.isValid () ? addDate : QDateTime::currentDateTime ();
826+ item.lastVisitTime = lastVisit;
827+ item.modifiedTime = lastModified.isValid () ? lastModified : item.createdTime ;
828+
829+ // 获取图标(如果有)
830+ QString iconUrl = aElement.attribute (" ICON" );
831+ if (!iconUrl.isEmpty ()) {
832+ item.iconUrl = iconUrl;
833+
834+ // 尝试获取ICON_URI属性
835+ QString iconUri = aElement.attribute (" ICON_URI" );
836+ if (!iconUri.isEmpty ()) {
837+ item.iconUrl = iconUri;
838+ }
839+ }
840+
841+ // 获取标签(如果有)
842+ QString tags = aElement.attribute (" TAGS" );
843+ if (!tags.isEmpty ()) {
844+ item.tags = tags.split (" ," );
845+ }
846+
847+ // 查找或创建文件夹
848+ int folderId = getOrCreateFolder (folderPath);
849+ item.folderId = folderId;
850+
851+ // 检查是否已存在
852+ auto lstExisting = getBookmarkByUrl (url);
853+ if (lstExisting.isEmpty ()) {
854+ // 插入新书签
855+ if (addBookmark (item)) {
856+ qDebug (log) << " Imported bookmark:" << title;
857+ return 1 ;
858+ }
859+ } else {
860+ foreach (auto exist, lstExisting) {
861+ // 更新现有书签
862+ exist.title = item.title ;
863+ exist.iconUrl = item.iconUrl ;
864+ exist.tags = item.tags ;
865+ exist.folderId = item.folderId ;
866+ exist.lastVisitTime = item.lastVisitTime ;
867+ exist.modifiedTime = QDateTime::currentDateTime ();
868+
869+ if (updateBookmark (exist)) {
870+ qDebug (log) << " Updated existing bookmark:" << title;
871+ }
872+ }
873+
874+ return lstExisting.count ();
875+ }
876+
877+ return 0 ;
878+ }
879+
880+ int CBookmarkDatabase::importFolder (const QDomElement &h3Element,
881+ const QString &parentPath,
882+ QMap<QString, int > &folderMap)
883+ {
884+ QString folderName = h3Element.text ();
885+ if (folderName.isEmpty ()) {
886+ return 0 ;
887+ }
888+
889+ // 构建完整路径
890+ QString folderPath = parentPath;
891+ if (!parentPath.endsWith (" /" )) {
892+ folderPath += " /" ;
893+ }
894+ folderPath += folderName;
895+
896+ // 获取文件夹属性
897+ QString addDateStr = h3Element.attribute (" ADD_DATE" );
898+ QString lastModifiedStr = h3Element.attribute (" LAST_MODIFIED" );
899+
900+ QDateTime addDate = parseTimestamp (addDateStr);
901+ QDateTime lastModified = parseTimestamp (lastModifiedStr);
902+
903+ // 查找父文件夹ID
904+ int parentFolderId = folderMap.value (parentPath, 0 );
905+
906+ // 创建文件夹
907+ int folderId = getOrCreateFolder (folderPath, parentFolderId);
908+ folderMap[folderPath] = folderId;
909+
910+ qDebug (log) << " Imported folder:" << folderName << " Path:" << folderPath << " ID:" << folderId;
911+
912+ // 处理子节点
913+ int importedCount = 0 ;
914+ QDomNode nextSibling = h3Element.nextSibling ();
915+
916+ while (!nextSibling.isNull ()) {
917+ QDomElement element = nextSibling.toElement ();
918+
919+ if (!element.isNull ()) {
920+ QString tagName = element.tagName ().toUpper ();
921+
922+ if (tagName == " DL" ) {
923+ // 递归处理子文件夹
924+ importedCount += parseBookmarkList (element, folderPath, folderMap);
925+ break ;
926+ }
927+ }
928+
929+ nextSibling = nextSibling.nextSibling ();
930+ }
931+
932+ return importedCount;
933+ }
934+
935+ int CBookmarkDatabase::getOrCreateFolder (const QString &folderPath, int parentFolderId)
936+ {
937+ if (folderPath.isEmpty () || folderPath == " /" ) {
938+ return 0 ; // 根目录
939+ }
940+
941+ // 分割路径
942+ QStringList pathParts = folderPath.split (" /" , Qt::SkipEmptyParts);
943+ if (pathParts.isEmpty ()) {
944+ return 0 ;
945+ }
946+
947+ QString folderName = pathParts.last ();
948+
949+ // 检查文件夹是否已存在
950+ QSqlQuery query (m_database);
951+ query.prepare (" SELECT id FROM bookmark_folders WHERE name = :name AND parent_id = :parent_id" );
952+ query.bindValue (" :name" , folderName);
953+ query.bindValue (" :parent_id" , parentFolderId);
954+
955+ if (query.exec () && query.next ()) {
956+ return query.value (0 ).toInt ();
957+ }
958+
959+ // 创建新文件夹
960+ query.prepare (
961+ " INSERT INTO bookmark_folders (name, parent_id, created_time) "
962+ " VALUES (:name, :parent_id, :created_time)"
963+ );
964+ query.bindValue (" :name" , folderName);
965+ query.bindValue (" :parent_id" , parentFolderId);
966+ query.bindValue (" :created_time" , QDateTime::currentDateTime ());
967+
968+ if (query.exec ()) {
969+ return query.lastInsertId ().toInt ();
970+ }
971+
972+ qWarning () << " Failed to create folder:" << folderName << query.lastError ().text ();
973+ return 0 ; // 失败时返回根目录
974+ }
975+
976+ int CBookmarkDatabase::getOrCreateFolder (const QString &folderPath)
977+ {
978+ if (folderPath.isEmpty () || folderPath == " /" ) {
979+ return 0 ;
980+ }
981+
982+ QStringList pathParts = folderPath.split (" /" , Qt::SkipEmptyParts);
983+ int currentParentId = 0 ;
984+
985+ // 逐级创建文件夹
986+ for (const QString &part : pathParts) {
987+ if (part.isEmpty ()) continue ;
988+
989+ currentParentId = getOrCreateFolder (part, currentParentId);
990+ }
991+
992+ return currentParentId;
993+ }
994+
995+ QDateTime CBookmarkDatabase::parseTimestamp (const QString ×tampStr)
996+ {
997+ if (timestampStr.isEmpty ()) {
998+ return QDateTime ();
999+ }
1000+
1001+ bool ok;
1002+ qint64 timestamp = timestampStr.toLongLong (&ok);
1003+
1004+ if (!ok) {
1005+ return QDateTime ();
1006+ }
1007+
1008+ // 处理不同精度的时间戳
1009+ if (timestamp > 10000000000 ) {
1010+ // 毫秒时间戳
1011+ return QDateTime::fromMSecsSinceEpoch (timestamp);
1012+ } else {
1013+ // 秒时间戳
1014+ return QDateTime::fromSecsSinceEpoch (timestamp);
1015+ }
1016+ }
1017+
1018+ QDomElement CBookmarkDatabase::findFirstElement (const QDomElement &parent, const QString &tagName)
1019+ {
1020+ QDomNode child = parent.firstChild ();
1021+
1022+ while (!child.isNull ()) {
1023+ QDomElement element = child.toElement ();
1024+
1025+ if (!element.isNull () && element.tagName ().toUpper () == tagName.toUpper ()) {
1026+ return element;
1027+ }
1028+
1029+ child = child.nextSibling ();
1030+ }
1031+
1032+ return QDomElement ();
1033+ }
0 commit comments