@@ -507,240 +507,6 @@ QDateTime CHistoryDatabase::getLastVisitTime()
507507 return QDateTime ();
508508}
509509
510- bool CHistoryDatabase::importFromCSV (const QString &filename)
511- {
512- QFile file (filename);
513- if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) {
514- qCritical (log) << " Failed to open file" << filename;
515- return false ;
516- }
517-
518- QTextStream in (&file);
519- #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
520- in.setEncoding (QStringConverter::Utf8);
521- #else
522- in.setCodec (" UTF-8" );
523- #endif
524- in.setGenerateByteOrderMark (true ); // 添加 UTF-8 BOM
525- int importedCount = 0 ;
526- int lineNumber = 0 ;
527-
528- // 开始事务
529- GetDatabase ().transaction ();
530-
531- try {
532- while (!in.atEnd ()) {
533- QString line = in.readLine ().trimmed ();
534- lineNumber++;
535-
536- // 跳过空行和注释
537- if (line.isEmpty () || line.startsWith (" #" )) {
538- continue ;
539- }
540-
541- // 跳过表头(第一行)
542- if (lineNumber == 1 ) {
543- // 验证表头格式
544- if (!validateCsvHeader (line)) {
545- throw QString (" Invalid CSV header format" );
546- }
547- continue ;
548- }
549-
550- // 解析 CSV 行
551- QStringList fields = parseCsvLine (line);
552-
553- if (fields.size () >= 3 ) {
554- if (importCsvRecord (fields)) {
555- importedCount++;
556- }
557- } else {
558- qWarning () << " Invalid CSV line" << lineNumber << " :" << line;
559- }
560- }
561-
562- file.close ();
563-
564- if (importedCount == 0 ) {
565- throw QString (" No valid records found in CSV file" );
566- }
567-
568- if (!GetDatabase ().commit ()) {
569- throw QString (" Failed to commit transaction: %1" ).arg (GetDatabase ().lastError ().text ());
570- }
571-
572- qDebug (log) << " Successfully imported" << importedCount << " records from CSV file" ;
573- return true ;
574- } catch (const QString &error) {
575- GetDatabase ().rollback ();
576- file.close ();
577- qCritical (log) << " CSV import failed at line" << lineNumber << " :" << error;
578- return false ;
579- }
580- return false ;
581- }
582-
583- bool CHistoryDatabase::exportToCSV (const QString &filename)
584- {
585- QFile file (filename);
586- if (!file.open (QIODevice::WriteOnly | QIODevice::Text))
587- return false ;
588-
589- QTextStream out (&file);
590- #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
591- out.setEncoding (QStringConverter::Utf8);
592- #else
593- out.setCodec (" UTF-8" );
594- #endif
595- out.setGenerateByteOrderMark (true ); // 添加 UTF-8 BOM
596-
597- // 写入表头
598- const QStringList headers = {
599- " Title" , " URL" , " Visit Time"
600- };
601- out << headers.join (" ," ) << " \n " ;
602-
603- auto items = getAllHistory ();
604- foreach (auto it, items) {
605- QStringList row;
606- row << escapeForCsv (it.title );
607- row << escapeForCsv (it.url );
608- row << it.visitTime .toString ();
609- out << row.join (" ," ) << " \n " ;
610- }
611-
612- file.close ();
613- return true ;
614- }
615-
616- QString CHistoryDatabase::escapeForCsv (const QString &text)
617- {
618- if (text.isEmpty ())
619- return " \"\" " ;
620-
621- // 判断是否需要引号
622- bool needQuotes = text.contains (' ,' ) ||
623- text.contains (' "' ) ||
624- text.contains (' \n ' ) ||
625- text.contains (' \r ' ) ||
626- text.contains (' \t ' ) ||
627- text.startsWith (' ' ) ||
628- text.endsWith (' ' ) ||
629- text.startsWith (' \t ' ) ||
630- text.endsWith (' \t ' );
631-
632- if (!needQuotes)
633- return text;
634-
635- // 转义双引号
636- QString escaped = text;
637- escaped.replace (" \" " , " \"\" " );
638-
639- return " \" " + escaped + " \" " ;
640- }
641-
642- QString CHistoryDatabase::unescapeCsvField (const QString &field)
643- {
644- if (field.isEmpty ()) {
645- return QString ();
646- }
647-
648- QString unescaped = field;
649-
650- // 去除包围的引号
651- if (unescaped.startsWith (' "' ) && unescaped.endsWith (' "' )) {
652- unescaped = unescaped.mid (1 , unescaped.length () - 2 );
653- }
654-
655- // 反转义双引号
656- unescaped.replace (" \"\" " , " \" " );
657-
658- return unescaped.trimmed ();
659- }
660-
661- QStringList CHistoryDatabase::parseCsvLine (const QString &line)
662- {
663- QStringList fields;
664- QString field;
665- bool inQuotes = false ;
666-
667- for (int i = 0 ; i < line.length (); ++i) {
668- QChar ch = line[i];
669-
670- if (ch == ' "' ) {
671- // 处理转义的双引号
672- if (i + 1 < line.length () && line[i + 1 ] == ' "' ) {
673- field += ' "' ;
674- i++; // 跳过下一个引号
675- } else {
676- inQuotes = !inQuotes;
677- }
678- } else if (ch == ' ,' && !inQuotes) {
679- fields.append (field.trimmed ());
680- field.clear ();
681- } else {
682- field += ch;
683- }
684- }
685-
686- // 添加最后一个字段
687- if (!field.isEmpty ()) {
688- fields.append (field.trimmed ());
689- }
690-
691- return fields;
692- }
693-
694- bool CHistoryDatabase::validateCsvHeader (const QString &headerLine)
695- {
696- QStringList headers = parseCsvLine (headerLine);
697-
698- // 检查必需的表头
699- if (headers.size () < 3 ) {
700- return false ;
701- }
702-
703- // 检查关键字段
704- QStringList requiredHeaders = {" Title" , " URL" , " Visit Time" };
705- for (const QString &required : requiredHeaders) {
706- if (!headers.contains (required, Qt::CaseInsensitive)) {
707- qWarning (log) << " Missing required header:" << required;
708- return false ;
709- }
710- }
711-
712- return true ;
713- }
714-
715- bool CHistoryDatabase::importCsvRecord (const QStringList &fields)
716- {
717- if (fields.size () < 3 ) {
718- return false ;
719- }
720-
721- HistoryItem item;
722-
723- // 解析字段
724- // 字段顺序:Title, URL, Visit Time
725-
726- // Title(可选)
727- item.title = fields[0 ];
728- // URL(必需)
729- item.url = fields[1 ];
730-
731- // Visit Time(必需)
732- item.visitTime = QDateTime::fromString (fields[2 ]);
733- if (!item.visitTime .isValid ()) {
734- // 尝试其他格式
735- item.visitTime = QDateTime::fromString (fields[2 ]);
736- if (!item.visitTime .isValid ()) {
737- item.visitTime = QDateTime::currentDateTime ();
738- }
739- }
740-
741- return addHistoryEntry (item.url , item.title , item.visitTime );
742- }
743-
744510bool CHistoryDatabase::ExportToJson (QJsonObject &obj)
745511{
746512 QJsonArray list;
0 commit comments