commit 44d5140a6084876a0f52464a8ad9f5e10373e635 Author: LJ Date: Thu Jan 22 19:08:28 2026 +0800 add diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef5a36e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +hidapi-win \ No newline at end of file diff --git a/.qtcreator/IIOT_BR.pro.user b/.qtcreator/IIOT_BR.pro.user new file mode 100644 index 0000000..637549a --- /dev/null +++ b/.qtcreator/IIOT_BR.pro.user @@ -0,0 +1,300 @@ + + + + + + EnvironmentId + {9cfdcceb-e0fb-42cd-9527-15eb650a3899} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + 0 + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop Qt 5.15.2 MinGW 32-bit + Desktop Qt 5.15.2 MinGW 32-bit + qt.qt5.5152.win32_mingw81_kit + 1 + 0 + 0 + + 0 + C:\Users\LJ963\workSpaces\code\Qt\iiot_-br\build\Desktop_Qt_5_15_2_MinGW_32_bit-Debug + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + 2 + + + C:\Users\LJ963\workSpaces\code\Qt\iiot_-br\build\Desktop_Qt_5_15_2_MinGW_32_bit-Release + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/build/Desktop_Qt_5_15_2_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + 0 + 0 + + 2 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + Version + 22 + + diff --git a/.qtcreator/IIOT_BR.pro.user.994229e b/.qtcreator/IIOT_BR.pro.user.994229e new file mode 100644 index 0000000..b73dfbb --- /dev/null +++ b/.qtcreator/IIOT_BR.pro.user.994229e @@ -0,0 +1,428 @@ + + + + + + EnvironmentId + {994229e1-f7a4-4814-81cd-4ddec27e5dcf} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + -fno-delayed-template-parsing + + true + Builtin.BuildSystem + + true + true + Builtin.DefaultTidyAndClazy + 8 + + + + true + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 5.12.12 MinGW 32-bit + Desktop Qt 5.12.12 MinGW 32-bit + qt.qt5.51212.win32_mingw73_kit + 1 + 0 + 0 + + 0 + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Debug + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Release + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Profile + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/data/QT/IIOT_BR/IIOT_BR/IIOT_BR.pro + D:/data/QT/IIOT_BR/IIOT_BR/IIOT_BR.pro + false + true + true + false + true + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_32_bit-Release + + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop + Desktop Qt 5.12.12 MinGW 64-bit + Desktop Qt 5.12.12 MinGW 64-bit + qt.qt5.51212.win64_mingw73_kit + 1 + 0 + 0 + + 0 + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Debug + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Release + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + D:\data\QT\IIOT_BR\build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Profile + D:/data/QT/IIOT_BR/build-IIOT_BR-Desktop_Qt_5_12_12_MinGW_64_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/data/QT/IIOT_BR/IIOT_BR/IIOT_BR.pro + D:/data/QT/IIOT_BR/IIOT_BR/IIOT_BR.pro + false + true + true + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 2 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/ExportThread.h b/ExportThread.h new file mode 100644 index 0000000..a25686a --- /dev/null +++ b/ExportThread.h @@ -0,0 +1,136 @@ +#ifndef EXPORTTHREAD_H +#define EXPORTTHREAD_H + +#include +#include +#include +#include +#include +#include "databaseutils.h" +#include "QXlsx/header/xlsxdocument.h" +#include "QXlsx/header/xlsxformat.h" +#include "QXlsx/header/xlsxcellrange.h" + +class ExportThread : public QThread +{ + Q_OBJECT + +public: + ExportThread(const QString &fileName, int allCount, int autoCount, int manualCount, int qualifiedCount, int upCount, int downCount, QObject *parent = nullptr) + : QThread(parent), fileName(fileName), all_count(allCount), auto_cnt(autoCount), manual_cnt(manualCount), qualified_cnt(qualifiedCount), up_cnt(upCount), down_cnt(downCount) {} + +protected: + void run() override + { + + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); + + qDebug() << "Export thread started."; + + int totalRows = DatabaseUtils::getResTotalRows(); + qDebug() << "Total rows in database:" << totalRows; + + if (totalRows == 0) + { + emit exportFailed("The table is empty. There is no data to export."); + return; + } + + QVector allData = DatabaseUtils::getAllData(); + qDebug() << "Data retrieved from database. Number of rows:" << allData.size(); + + if (allData.isEmpty()) + { + emit exportFailed("No data retrieved from database."); + return; + } + + QXlsx::Document xlsx; + + QXlsx::Format titleFormat; + titleFormat.setFontBold(true); + titleFormat.setFontSize(14); + titleFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); + titleFormat.setVerticalAlignment(QXlsx::Format::AlignVCenter); + + qDebug() << "Setting title."; + xlsx.write("A1", QString::fromUtf8("电阻检测软件导出表"), titleFormat); + xlsx.mergeCells(QXlsx::CellRange("A1:J1"), titleFormat); + + QXlsx::Format timeFormat; + timeFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); + timeFormat.setVerticalAlignment(QXlsx::Format::AlignVCenter); + + qDebug() << "Setting export time."; + xlsx.write("A2", QString("导出时间: %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")), timeFormat); + xlsx.mergeCells(QXlsx::CellRange("A2:J2"), timeFormat); + + QXlsx::Format infoFormat; + infoFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); + infoFormat.setVerticalAlignment(QXlsx::Format::AlignVCenter); + + QString exportInfo = QString("总检测数: %1, 合格: %2, 超过量程: %3, 小于量程: %4, 合格率: %5") + .arg(all_count) + .arg(qualified_cnt) + .arg(up_cnt) + .arg(down_cnt) + .arg(double(qualified_cnt) / all_count * 100, 0, 'f', 2); + + qDebug() << "Setting export info."; + xlsx.write("A3", exportInfo, infoFormat); + xlsx.mergeCells(QXlsx::CellRange("A3:J3"), infoFormat); + + QStringList headers = {QString::fromUtf8("序号"), QString::fromUtf8("车型"), QString::fromUtf8("护面扫码编号"), QString::fromUtf8("加热垫扫码编号"), QString::fromUtf8("实测阻值"), QString::fromUtf8("标准阻值范围"), QString::fromUtf8("检测结果"), QString::fromUtf8("手动/自动"), QString::fromUtf8("检测员"), QString::fromUtf8("检测时间")}; + QXlsx::Format headerFormat; + headerFormat.setFontBold(true); + headerFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); + headerFormat.setVerticalAlignment(QXlsx::Format::AlignVCenter); + + qDebug() << "Setting column headers."; + for (int col = 0; col < headers.size(); ++col) + { + xlsx.write(4, col + 1, headers[col], headerFormat); + } + + QXlsx::Format dataFormat; + dataFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); + dataFormat.setVerticalAlignment(QXlsx::Format::AlignVCenter); + + qDebug() << "Filling data."; + for (int row = 0; row < allData.size(); ++row) + { + const QVariantList &rowData = allData[row]; + for (int col = 0; col < rowData.size(); ++col) + { + xlsx.write(row + 5, col + 1, rowData[col], dataFormat); + } + } + + qDebug() << "Saving file to" << fileName; + if (xlsx.saveAs(fileName)) + { + qDebug() << "File saved successfully."; + emit exportSucceeded(); + } + else + { + qDebug() << "Failed to save the file."; + emit exportFailed("Failed to save the file."); + } + } + +signals: + void exportSucceeded(); + void exportFailed(const QString &error); + +private: + QString fileName; + int all_count; + int auto_cnt; + int manual_cnt; + int qualified_cnt; + int up_cnt; + int down_cnt; +}; + +#endif // EXPORTTHREAD_H diff --git a/IIOT_BR.pro b/IIOT_BR.pro new file mode 100644 index 0000000..5480328 --- /dev/null +++ b/IIOT_BR.pro @@ -0,0 +1,71 @@ +QT += core gui serialport widgets serialbus charts sql network axcontainer multimedia + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +VERSION = 1.0.0 + +CONFIG += c++11 + +RC_ICONS = res\icon.ico +TARGET = IIOT_RB +include($$PWD/QXlsx/QXlsx.pri) # QXlsx源代码 +INCLUDEPATH += $$PWD/QXlsx + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + SerialConfig.cpp \ + databaseutils.cpp \ + licensemanager.cpp \ + main.cpp \ + mainwindow.cpp \ + passworddialog.cpp \ + serialportmanager.cpp + +HEADERS += \ + ExportThread.h \ + SerialConfig.h \ + databaseutils.h \ +# hidapi-win/include/hidapi.h \ +# hidapi-win/include/hidapi_winapi.h \ + licensemanager.h \ + mainwindow.h \ + passworddialog.h \ + serialportmanager.h + +FORMS += \ + mainwindow.ui + +## 设置 HIDAPI 库的路径 +#HIDAPI_DIR = $$PWD/hidapi-win + + +#win32 { +# # x64 平台 +# contains(QMAKE_HOST.arch, x86_64) { +# LIBS += -L$$HIDAPI_DIR/x64 -lhidapi +# } else { +# LIBS += -L$$HIDAPI_DIR/x86 -lhidapi +# } +#} + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +RESOURCES += \ + res.qrc + +SUBDIRS += \ + QXlsx/QXlsx.pro + +DISTFILES += \ + QXlsx/CMakeLists.txt \ + QXlsx/QXlsx.pri \ + QXlsx/cmake/modules/CPackConfig.cmake \ + QXlsx/cmake/modules/qxlsx-config-version.cmake.in \ + QXlsx/cmake/modules/qxlsx-config.cmake.in \ + diff --git a/IIOT_BR.pro.user b/IIOT_BR.pro.user new file mode 100644 index 0000000..637549a --- /dev/null +++ b/IIOT_BR.pro.user @@ -0,0 +1,300 @@ + + + + + + EnvironmentId + {9cfdcceb-e0fb-42cd-9527-15eb650a3899} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + 0 + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop Qt 5.15.2 MinGW 32-bit + Desktop Qt 5.15.2 MinGW 32-bit + qt.qt5.5152.win32_mingw81_kit + 1 + 0 + 0 + + 0 + C:\Users\LJ963\workSpaces\code\Qt\iiot_-br\build\Desktop_Qt_5_15_2_MinGW_32_bit-Debug + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + 2 + + + C:\Users\LJ963\workSpaces\code\Qt\iiot_-br\build\Desktop_Qt_5_15_2_MinGW_32_bit-Release + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/build/Desktop_Qt_5_15_2_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + 0 + 0 + + 2 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + + Qt4ProjectManager.Qt4RunConfiguration: + C:/Users/LJ963/workSpaces/code/Qt/iiot_-br/IIOT_BR.pro + false + + true + true + true + %{RunConfig:Executable:Path} + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + Version + 22 + + diff --git a/QXlsx/CMakeLists.txt b/QXlsx/CMakeLists.txt new file mode 100644 index 0000000..e1a6cc5 --- /dev/null +++ b/QXlsx/CMakeLists.txt @@ -0,0 +1,215 @@ +# CMakeLists.txt for QXlsx Library + +cmake_minimum_required(VERSION 3.16) + +project(QXlsx + VERSION 1.4.4 + LANGUAGES CXX +) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOMOC ON) + +include(GNUInstallDirs) + +if(NOT DEFINED QT_VERSION_MAJOR) + find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui REQUIRED) +endif() +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui REQUIRED) +set(EXPORT_NAME QXlsxQt${QT_VERSION_MAJOR}) + +if (QT_VERSION_MAJOR EQUAL 6) + set(CMAKE_CXX_STANDARD 17 CACHE STRING "") +else() + set(CMAKE_CXX_STANDARD 11 CACHE STRING "") +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) + +if(NOT DEFINED QXLSX_PARENTPATH) + set(QXLSX_PARENTPATH ${CMAKE_CURRENT_SOURCE_DIR}/../) +endif() + +if(NOT DEFINED QXLSX_HEADERPATH) + set(QXLSX_HEADERPATH ${CMAKE_CURRENT_SOURCE_DIR}/../QXlsx/header/) +endif() + +if(NOT DEFINED QXLSX_SOURCEPATH) + set(QXLSX_SOURCEPATH ${CMAKE_CURRENT_SOURCE_DIR}/../QXlsx/source/) +endif() + +# Due historical reasons this value is kept off +option(BUILD_SHARED_LIBS "Build in shared lib mode" OFF) + +set(SRC_FILES + source/xlsxcellrange.cpp + source/xlsxcellrange.cpp + source/xlsxcontenttypes.cpp + source/xlsxdrawinganchor.cpp + source/xlsxrichstring.cpp + source/xlsxworkbook.cpp + source/xlsxabstractooxmlfile.cpp + source/xlsxcellreference.cpp + source/xlsxdatavalidation.cpp + source/xlsxdrawing.cpp + source/xlsxsharedstrings.cpp + source/xlsxworksheet.cpp + source/xlsxabstractsheet.cpp + source/xlsxchart.cpp + source/xlsxdatetype.cpp + source/xlsxformat.cpp + source/xlsxsimpleooxmlfile.cpp + source/xlsxzipreader.cpp + source/xlsxcell.cpp + source/xlsxchartsheet.cpp + source/xlsxdocpropsapp.cpp + source/xlsxmediafile.cpp + source/xlsxstyles.cpp + source/xlsxzipwriter.cpp + source/xlsxcellformula.cpp + source/xlsxcolor.cpp + source/xlsxdocpropscore.cpp + source/xlsxnumformatparser.cpp + source/xlsxtheme.cpp + source/xlsxcelllocation.cpp + source/xlsxconditionalformatting.cpp + source/xlsxdocument.cpp + source/xlsxrelationships.cpp + source/xlsxutility.cpp + header/xlsxabstractooxmlfile_p.h + header/xlsxchartsheet_p.h + header/xlsxdocpropsapp_p.h + header/xlsxformat_p.h + header/xlsxsharedstrings_p.h + header/xlsxworkbook_p.h + header/xlsxabstractsheet_p.h + header/xlsxcolor_p.h + header/xlsxdocpropscore_p.h + header/xlsxmediafile_p.h + header/xlsxsimpleooxmlfile_p.h + header/xlsxworksheet_p.h + header/xlsxcellformula_p.h + header/xlsxconditionalformatting_p.h + header/xlsxdocument_p.h + header/xlsxnumformatparser_p.h + header/xlsxstyles_p.h + header/xlsxzipreader_p.h + header/xlsxcell_p.h + header/xlsxcontenttypes_p.h + header/xlsxdrawinganchor_p.h + header/xlsxrelationships_p.h + header/xlsxtheme_p.h + header/xlsxzipwriter_p.h + header/xlsxchart_p.h + header/xlsxdatavalidation_p.h + header/xlsxdrawing_p.h + header/xlsxrichstring_p.h + header/xlsxutility_p.h +) + +set(QXLSX_PUBLIC_HEADERS + header/xlsxabstractooxmlfile.h + header/xlsxabstractsheet.h + header/xlsxabstractsheet_p.h + header/xlsxcellformula.h + header/xlsxcell.h + header/xlsxcelllocation.h + header/xlsxcellrange.h + header/xlsxcellreference.h + header/xlsxchart.h + header/xlsxchartsheet.h + header/xlsxconditionalformatting.h + header/xlsxdatavalidation.h + header/xlsxdatetype.h + header/xlsxdocument.h + header/xlsxformat.h + header/xlsxglobal.h + header/xlsxrichstring.h + header/xlsxworkbook.h + header/xlsxworksheet.h +) + +add_library(QXlsx + ${SRC_FILES} + ${QXLSX_PUBLIC_HEADERS} +) + +add_library(QXlsx::QXlsx ALIAS QXlsx) + +target_compile_definitions(QXlsx PRIVATE + QT_NO_KEYWORDS + QT_NO_CAST_TO_ASCII + QT_NO_CAST_FROM_ASCII + QT_NO_URL_CAST_FROM_STRING + QT_NO_CAST_FROM_BYTEARRAY + QT_USE_QSTRINGBUILDER + QT_NO_SIGNALS_SLOTS_KEYWORDS + QT_USE_FAST_OPERATOR_PLUS + QT_DISABLE_DEPRECATED_BEFORE=0x060200 +) + +if (NOT WIN32) + # Strict iterators can't be used on Windows, they lead to a link error + # when application code iterates over a QVector for instance, unless + # Qt itself was also built with strict iterators. + # See example at https://bugreports.qt.io/browse/AUTOSUITE-946 + target_compile_definitions(QXlsx PRIVATE QT_STRICT_ITERATORS) +endif() + +target_compile_features(QXlsx INTERFACE cxx_std_11) + +if (BUILD_SHARED_LIBS) + target_compile_definitions(QXlsx PUBLIC QXlsx_SHAREDLIB) +endif() + +target_link_libraries(${PROJECT_NAME} + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::GuiPrivate +) + +target_include_directories(QXlsx +PRIVATE + ${QXLSX_HEADERPATH} +PUBLIC + $ + $ +) + +set_target_properties(QXlsx PROPERTIES + OUTPUT_NAME ${EXPORT_NAME} + VERSION ${PROJECT_VERSION} + SOVERSION 0.${PROJECT_VERSION} + PUBLIC_HEADER "${QXLSX_PUBLIC_HEADERS}" +) + +install(TARGETS QXlsx + EXPORT ${EXPORT_NAME}Targets DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/QXlsxQt${QT_VERSION_MAJOR} COMPONENT devel +) + +install(EXPORT ${EXPORT_NAME}Targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXPORT_NAME}/ + FILE ${EXPORT_NAME}Targets.cmake + NAMESPACE QXlsx:: + COMPONENT devel +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/qxlsx-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_NAME}Config.cmake + @ONLY +) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/qxlsx-config-version.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_NAME}ConfigVersion.cmake + @ONLY +) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_NAME}ConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXPORT_NAME}/ +) +include(CPackConfig) diff --git a/QXlsx/QXlsx.pri b/QXlsx/QXlsx.pri new file mode 100644 index 0000000..8e2ea59 --- /dev/null +++ b/QXlsx/QXlsx.pri @@ -0,0 +1,204 @@ +######################################## +# QXlsx.pri +######################################## + +QT += core +QT += gui-private + +# TODO: Define your C++ version. c++14, c++17, etc. +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# For DLL visibility +#DEFINES += QXlsx_SHAREDLIB QXlsx_EXPORTS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +isEmpty(QXLSX_PARENTPATH) { + message( 'QXLSX_PARENTPATH is empty. use default value.' ) + QXLSX_PARENTPATH = $$PWD/../ +} else { + message( 'QXLSX_PARENTPATH is not empty.' ) + message( $${QXLSX_PARENTPATH} ) +} + +isEmpty(QXLSX_HEADERPATH) { + message( 'QXLSX_HEADERPATH is empty. use default value.' ) + QXLSX_HEADERPATH = $$PWD/../QXlsx/header/ +} else { + message( 'QXLSX_HEADERPATH is not empty.' ) + message( $${QXLSX_HEADERPATH} ) +} + +isEmpty(QXLSX_SOURCEPATH) { + message( 'QXLSX_SOURCEPATH is empty. use default value.' ) + QXLSX_SOURCEPATH = $$PWD/../QXlsx/source/ +} else { + message( 'QXLSX_SOURCEPATH is not empty.' ) + message( $${QXLSX_SOURCEPATH} ) +} + +INCLUDEPATH += $$PWD +INCLUDEPATH += $${QXLSX_PARENTPATH} +INCLUDEPATH += $${QXLSX_HEADERPATH} + +######################################## +# source code + +HEADERS += \ +$${QXLSX_HEADERPATH}xlsxabstractooxmlfile.h \ +$${QXLSX_HEADERPATH}xlsxabstractooxmlfile_p.h \ +$${QXLSX_HEADERPATH}xlsxabstractsheet.h \ +$${QXLSX_HEADERPATH}xlsxabstractsheet_p.h \ +$${QXLSX_HEADERPATH}xlsxcell.h \ +$${QXLSX_HEADERPATH}xlsxcellformula.h \ +$${QXLSX_HEADERPATH}xlsxcellformula_p.h \ +$${QXLSX_HEADERPATH}xlsxcelllocation.h \ +$${QXLSX_HEADERPATH}xlsxcellrange.h \ +$${QXLSX_HEADERPATH}xlsxcellreference.h \ +$${QXLSX_HEADERPATH}xlsxcell_p.h \ +$${QXLSX_HEADERPATH}xlsxchart.h \ +$${QXLSX_HEADERPATH}xlsxchartsheet.h \ +$${QXLSX_HEADERPATH}xlsxchartsheet_p.h \ +$${QXLSX_HEADERPATH}xlsxchart_p.h \ +$${QXLSX_HEADERPATH}xlsxcolor_p.h \ +$${QXLSX_HEADERPATH}xlsxconditionalformatting.h \ +$${QXLSX_HEADERPATH}xlsxconditionalformatting_p.h \ +$${QXLSX_HEADERPATH}xlsxcontenttypes_p.h \ +$${QXLSX_HEADERPATH}xlsxdatavalidation.h \ +$${QXLSX_HEADERPATH}xlsxdatavalidation_p.h \ +$${QXLSX_HEADERPATH}xlsxdatetype.h \ +$${QXLSX_HEADERPATH}xlsxdocpropsapp_p.h \ +$${QXLSX_HEADERPATH}xlsxdocpropscore_p.h \ +$${QXLSX_HEADERPATH}xlsxdocument.h \ +$${QXLSX_HEADERPATH}xlsxdocument_p.h \ +$${QXLSX_HEADERPATH}xlsxdrawinganchor_p.h \ +$${QXLSX_HEADERPATH}xlsxdrawing_p.h \ +$${QXLSX_HEADERPATH}xlsxformat.h \ +$${QXLSX_HEADERPATH}xlsxformat_p.h \ +$${QXLSX_HEADERPATH}xlsxglobal.h \ +$${QXLSX_HEADERPATH}xlsxmediafile_p.h \ +$${QXLSX_HEADERPATH}xlsxnumformatparser_p.h \ +$${QXLSX_HEADERPATH}xlsxrelationships_p.h \ +$${QXLSX_HEADERPATH}xlsxrichstring.h \ +$${QXLSX_HEADERPATH}xlsxrichstring_p.h \ +$${QXLSX_HEADERPATH}xlsxsharedstrings_p.h \ +$${QXLSX_HEADERPATH}xlsxsimpleooxmlfile_p.h \ +$${QXLSX_HEADERPATH}xlsxstyles_p.h \ +$${QXLSX_HEADERPATH}xlsxtheme_p.h \ +$${QXLSX_HEADERPATH}xlsxutility_p.h \ +$${QXLSX_HEADERPATH}xlsxworkbook.h \ +$${QXLSX_HEADERPATH}xlsxworkbook_p.h \ +$${QXLSX_HEADERPATH}xlsxworksheet.h \ +$${QXLSX_HEADERPATH}xlsxworksheet_p.h \ +$${QXLSX_HEADERPATH}xlsxzipreader_p.h \ +$${QXLSX_HEADERPATH}xlsxzipwriter_p.h + +SOURCES += \ +$${QXLSX_SOURCEPATH}xlsxabstractooxmlfile.cpp \ +$${QXLSX_SOURCEPATH}xlsxabstractsheet.cpp \ +$${QXLSX_SOURCEPATH}xlsxcell.cpp \ +$${QXLSX_SOURCEPATH}xlsxcellformula.cpp \ +$${QXLSX_SOURCEPATH}xlsxcelllocation.cpp \ +$${QXLSX_SOURCEPATH}xlsxcellrange.cpp \ +$${QXLSX_SOURCEPATH}xlsxcellreference.cpp \ +$${QXLSX_SOURCEPATH}xlsxchart.cpp \ +$${QXLSX_SOURCEPATH}xlsxchartsheet.cpp \ +$${QXLSX_SOURCEPATH}xlsxcolor.cpp \ +$${QXLSX_SOURCEPATH}xlsxconditionalformatting.cpp \ +$${QXLSX_SOURCEPATH}xlsxcontenttypes.cpp \ +$${QXLSX_SOURCEPATH}xlsxdatavalidation.cpp \ +$${QXLSX_SOURCEPATH}xlsxdatetype.cpp \ +$${QXLSX_SOURCEPATH}xlsxdocpropsapp.cpp \ +$${QXLSX_SOURCEPATH}xlsxdocpropscore.cpp \ +$${QXLSX_SOURCEPATH}xlsxdocument.cpp \ +$${QXLSX_SOURCEPATH}xlsxdrawing.cpp \ +$${QXLSX_SOURCEPATH}xlsxdrawinganchor.cpp \ +$${QXLSX_SOURCEPATH}xlsxformat.cpp \ +$${QXLSX_SOURCEPATH}xlsxmediafile.cpp \ +$${QXLSX_SOURCEPATH}xlsxnumformatparser.cpp \ +$${QXLSX_SOURCEPATH}xlsxrelationships.cpp \ +$${QXLSX_SOURCEPATH}xlsxrichstring.cpp \ +$${QXLSX_SOURCEPATH}xlsxsharedstrings.cpp \ +$${QXLSX_SOURCEPATH}xlsxsimpleooxmlfile.cpp \ +$${QXLSX_SOURCEPATH}xlsxstyles.cpp \ +$${QXLSX_SOURCEPATH}xlsxtheme.cpp \ +$${QXLSX_SOURCEPATH}xlsxutility.cpp \ +$${QXLSX_SOURCEPATH}xlsxworkbook.cpp \ +$${QXLSX_SOURCEPATH}xlsxworksheet.cpp \ +$${QXLSX_SOURCEPATH}xlsxzipreader.cpp \ +$${QXLSX_SOURCEPATH}xlsxzipwriter.cpp + + +######################################## +# custom setting for compiler & system + +win32-g++ { + message("compiling for windows g++. mingw or msys or cygwin.") + INCLUDEPATH += $${QXLSX_HEADERPATH}win32-gcc + CONFIG(debug, debug|release) { + } else { + } +} +win32-msvc2013 { + message("Compiling for Visual Studio 2013") + INCLUDEPATH += $${QXLSX_HEADERPATH}msvc2013 + CONFIG(debug, debug|release) { + } else { + } +} +win32-msvc2015 { + message("Compiling for Visual Studio 2015") + INCLUDEPATH += $${QXLSX_HEADERPATH}msvc2015 + CONFIG(debug, debug|release) { + } else { + } +} +win32-msvc2017 { + message("Compiling for Visual Studio 2017") + INCLUDEPATH += $${QXLSX_HEADERPATH}msvc2017 + CONFIG(debug, debug|release) { + } else { + } +} +win32-msvc2019 { + message("Compiling for Visual Studio 2019") + INCLUDEPATH += $${QXLSX_HEADERPATH}msvc2019 + CONFIG(debug, debug|release) { + } else { + } +} +unix { + !contains(QT_ARCH, x86_64){ + LIB=lib32 + message("compiling for 32bit linux/unix system") + } else { + LIB=lib64 + message("compiling for 64bit linux/unix system") + } + + INCLUDEPATH += $${QXLSX_HEADERPATH}unix-gcc + + # target.path = /usr/lib + # INSTALLS += target + + CONFIG(debug, debug|release) { + } else { + } +} +mac { + message("compiling for mac os") + INCLUDEPATH += $${QXLSX_HEADERPATH}mac + CONFIG(debug, debug|release) { + } else { + } +} + diff --git a/QXlsx/QXlsx.pro b/QXlsx/QXlsx.pro new file mode 100644 index 0000000..c4946b8 --- /dev/null +++ b/QXlsx/QXlsx.pro @@ -0,0 +1,32 @@ +# QXlsx.pro + +TARGET = QXlsx +TEMPLATE = lib +CONFIG += staticlib +QT += core +QT += gui-private + +##################################################################### +# set debug/release build environment +# +# CONFIG += debug_and_release +# release: DESTDIR = lib-release +# debug: DESTDIR = lib-debug + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +QXLSX_PARENTPATH=$$PWD/ +QXLSX_HEADERPATH=$$PWD/header/ +QXLSX_SOURCEPATH=$$PWD/source/ +include($$PWD/QXlsx.pri) + + diff --git a/QXlsx/cmake/modules/CPackConfig.cmake b/QXlsx/cmake/modules/CPackConfig.cmake new file mode 100644 index 0000000..07dd1ab --- /dev/null +++ b/QXlsx/cmake/modules/CPackConfig.cmake @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: (C) 2021 Daniel Nicoletti +# SPDX-License-Identifier: MIT + +set(CPACK_PACKAGE_VENDOR "Daniel Nicoletti") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "QXlsx library.") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/../README.md") +set(CPACK_PACKAGE_CONTACT "Daniel Nicoletti ") + +if(UNIX) + if(NOT CPACK_GENERATOR) + set(CPACK_GENERATOR "DEB") + endif() + + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + set(CPACK_STRIP_FILES 1) + if(${CMAKE_VERSION} VERSION_GREATER "3.5") + set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + endif() +endif() + +include(CPack) diff --git a/QXlsx/cmake/modules/qxlsx-config-version.cmake.in b/QXlsx/cmake/modules/qxlsx-config-version.cmake.in new file mode 100644 index 0000000..68cbf8f --- /dev/null +++ b/QXlsx/cmake/modules/qxlsx-config-version.cmake.in @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: (C) 2021 Daniel Nicoletti +# SPDX-License-Identifier: MIT + +SET(PACKAGE_VERSION @PROJECT_VERSION@) +IF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) + SET(PACKAGE_VERSION_EXACT "true") +ENDIF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) +IF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_COMPATIBLE "true") +ELSE (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_UNSUITABLE "true") +ENDIF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) +IF (PACKAGE_VERSION_UNSUITABLE) + MESSAGE("VERSION CHECK FAILED FOR ${PACKAGE_FIND_NAME}. WANTED ${PACKAGE_FIND_VERSION}, HAVE ${PACKAGE_VERSION}") +ENDIF(PACKAGE_VERSION_UNSUITABLE) diff --git a/QXlsx/cmake/modules/qxlsx-config.cmake.in b/QXlsx/cmake/modules/qxlsx-config.cmake.in new file mode 100644 index 0000000..d72276b --- /dev/null +++ b/QXlsx/cmake/modules/qxlsx-config.cmake.in @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: (C) 2021 Daniel Nicoletti +# SPDX-License-Identifier: MIT + +# - Config information for QXlsx +# This file defines: +# +# QXlsx_INCLUDE_DIR - the QXlsx include directory +# QXlsx_LIBRARY - Link these to use QXlsx + +SET(prefix "@CMAKE_INSTALL_PREFIX@") +SET(exec_prefix "@CMAKE_INSTALL_PREFIX@") +SET(QXlsx_FOUND "TRUE") + +include("${CMAKE_CURRENT_LIST_DIR}/@EXPORT_NAME@Targets.cmake") diff --git a/QXlsx/header/xlsxabstractooxmlfile.h b/QXlsx/header/xlsxabstractooxmlfile.h new file mode 100644 index 0000000..5de9db1 --- /dev/null +++ b/QXlsx/header/xlsxabstractooxmlfile.h @@ -0,0 +1,43 @@ +// xlsxabstractooxmlfile.h + +#ifndef QXLSX_XLSXABSTRACTOOXMLFILE_H +#define QXLSX_XLSXABSTRACTOOXMLFILE_H + +#include "xlsxglobal.h" + +QT_BEGIN_NAMESPACE_XLSX + +class Relationships; +class AbstractOOXmlFilePrivate; + +class QXLSX_EXPORT AbstractOOXmlFile +{ + Q_DECLARE_PRIVATE(AbstractOOXmlFile) + +public: + enum CreateFlag { F_NewFromScratch, F_LoadFromExists }; + +public: + virtual ~AbstractOOXmlFile(); + + virtual void saveToXmlFile(QIODevice *device) const = 0; + virtual bool loadFromXmlFile(QIODevice *device) = 0; + + virtual QByteArray saveToXmlData() const; + virtual bool loadFromXmlData(const QByteArray &data); + + Relationships *relationships() const; + + void setFilePath(const QString path); + QString filePath() const; + +protected: + AbstractOOXmlFile(CreateFlag flag); + AbstractOOXmlFile(AbstractOOXmlFilePrivate *d); + + AbstractOOXmlFilePrivate *d_ptr; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXABSTRACTOOXMLFILE_H diff --git a/QXlsx/header/xlsxabstractooxmlfile_p.h b/QXlsx/header/xlsxabstractooxmlfile_p.h new file mode 100644 index 0000000..027e8a5 --- /dev/null +++ b/QXlsx/header/xlsxabstractooxmlfile_p.h @@ -0,0 +1,30 @@ +// xlsxabstractooxmlfile_p.h + +#ifndef XLSXOOXMLFILE_P_H +#define XLSXOOXMLFILE_P_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxglobal.h" +#include "xlsxrelationships_p.h" + +QT_BEGIN_NAMESPACE_XLSX + +class AbstractOOXmlFilePrivate +{ + Q_DECLARE_PUBLIC(AbstractOOXmlFile) + +public: + AbstractOOXmlFilePrivate(AbstractOOXmlFile *q, AbstractOOXmlFile::CreateFlag flag); + virtual ~AbstractOOXmlFilePrivate(); + +public: + QString filePathInPackage; // such as "xl/worksheets/sheet1.xml" + + Relationships *relationships; + AbstractOOXmlFile::CreateFlag flag; + AbstractOOXmlFile *q_ptr; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXOOXMLFILE_P_H diff --git a/QXlsx/header/xlsxabstractsheet.h b/QXlsx/header/xlsxabstractsheet.h new file mode 100644 index 0000000..674a267 --- /dev/null +++ b/QXlsx/header/xlsxabstractsheet.h @@ -0,0 +1,49 @@ +// xlsxabstractsheet.h + +#ifndef XLSXABSTRACTSHEET_H +#define XLSXABSTRACTSHEET_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxglobal.h" + +QT_BEGIN_NAMESPACE_XLSX + +class Workbook; +class Drawing; +class AbstractSheetPrivate; + +class QXLSX_EXPORT AbstractSheet : public AbstractOOXmlFile +{ + Q_DECLARE_PRIVATE(AbstractSheet) + +public: + Workbook *workbook() const; + +public: + // NOTE: If all Qt compiler supports C++1x, recommend to use a 'class enum'. + enum SheetType { ST_WorkSheet, ST_ChartSheet, ST_DialogSheet, ST_MacroSheet }; + enum SheetState { SS_Visible, SS_Hidden, SS_VeryHidden }; + +public: + QString sheetName() const; + SheetType sheetType() const; + SheetState sheetState() const; + void setSheetState(SheetState ss); + bool isHidden() const; + bool isVisible() const; + void setHidden(bool hidden); + void setVisible(bool visible); + +protected: + friend class Workbook; + AbstractSheet(const QString &sheetName, int sheetId, Workbook *book, AbstractSheetPrivate *d); + virtual AbstractSheet *copy(const QString &distName, int distId) const = 0; + void setSheetName(const QString &sheetName); + void setSheetType(SheetType type); + int sheetId() const; + + Drawing *drawing() const; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXABSTRACTSHEET_H diff --git a/QXlsx/header/xlsxabstractsheet_p.h b/QXlsx/header/xlsxabstractsheet_p.h new file mode 100644 index 0000000..6b5568f --- /dev/null +++ b/QXlsx/header/xlsxabstractsheet_p.h @@ -0,0 +1,35 @@ +// xlsxabstractsheet_p/h + +#ifndef XLSXABSTRACTSHEET_P_H +#define XLSXABSTRACTSHEET_P_H + +#include "xlsxabstractooxmlfile_p.h" +#include "xlsxabstractsheet.h" +#include "xlsxdrawing_p.h" +#include "xlsxglobal.h" + +#include + +#include + +QT_BEGIN_NAMESPACE_XLSX + +class AbstractSheetPrivate : public AbstractOOXmlFilePrivate +{ + Q_DECLARE_PUBLIC(AbstractSheet) +public: + AbstractSheetPrivate(AbstractSheet *p, AbstractSheet::CreateFlag flag); + ~AbstractSheetPrivate(); + + Workbook *workbook; + std::shared_ptr drawing; + + QString name; + int id; + AbstractSheet::SheetState sheetState; + AbstractSheet::SheetType type; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXABSTRACTSHEET_P_H diff --git a/QXlsx/header/xlsxcell.h b/QXlsx/header/xlsxcell.h new file mode 100644 index 0000000..aa47ce1 --- /dev/null +++ b/QXlsx/header/xlsxcell.h @@ -0,0 +1,81 @@ +// xlsxcell.h + +#ifndef QXLSX_XLSXCELL_H +#define QXLSX_XLSXCELL_H + +#include "xlsxformat.h" +#include "xlsxglobal.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Worksheet; +class Format; +class CellFormula; +class CellPrivate; +class WorksheetPrivate; + +class QXLSX_EXPORT Cell +{ + Q_DECLARE_PRIVATE(Cell) + +private: + friend class Worksheet; + friend class WorksheetPrivate; + +public: + enum CellType // See ECMA 376, 18.18.11. ST_CellType (Cell Type) for more information. + { + BooleanType, + DateType, + ErrorType, + InlineStringType, + NumberType, + SharedStringType, + StringType, + CustomType, // custom or un-defined cell type + }; + +public: + Cell(const QVariant &data = QVariant(), + CellType type = NumberType, + const Format &format = Format(), + Worksheet *parent = nullptr, + qint32 styleIndex = (-1)); + Cell(const Cell *const cell); + ~Cell(); + +public: + CellPrivate *const d_ptr; // See D-pointer and Q-pointer of Qt, for more information. + +public: + CellType cellType() const; + QVariant value() const; + QVariant readValue() const; + Format format() const; + + bool hasFormula() const; + CellFormula formula() const; + + bool isDateTime() const; + QVariant dateTime() const; // QDateTime, QDate, QTime + + bool isRichString() const; + + qint32 styleNumber() const; + + static bool isDateType(CellType cellType, const Format &format); +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXCELL_H diff --git a/QXlsx/header/xlsxcell_p.h b/QXlsx/header/xlsxcell_p.h new file mode 100644 index 0000000..d7d71c2 --- /dev/null +++ b/QXlsx/header/xlsxcell_p.h @@ -0,0 +1,43 @@ +// xlsxcell_p.h + +#ifndef XLSXCELL_P_H +#define XLSXCELL_P_H + +#include "xlsxcell.h" +#include "xlsxcellformula.h" +#include "xlsxcellrange.h" +#include "xlsxglobal.h" +#include "xlsxrichstring.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class CellPrivate +{ + Q_DECLARE_PUBLIC(Cell) +public: + CellPrivate(Cell *p); + CellPrivate(const CellPrivate *const cp); + +public: + Worksheet *parent; + Cell *q_ptr; + +public: + Cell::CellType cellType; + QVariant value; + + CellFormula formula; + Format format; + + RichString richString; + + qint32 styleNumber; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXCELL_P_H diff --git a/QXlsx/header/xlsxcellformula.h b/QXlsx/header/xlsxcellformula.h new file mode 100644 index 0000000..4b96c9a --- /dev/null +++ b/QXlsx/header/xlsxcellformula.h @@ -0,0 +1,56 @@ +// xlsxcellformula.h + +#ifndef QXLSX_XLSXCELLFORMULA_H +#define QXLSX_XLSXCELLFORMULA_H + +#include "xlsxglobal.h" + +#include + +class QXmlStreamWriter; +class QXmlStreamReader; + +QT_BEGIN_NAMESPACE_XLSX + +class CellFormulaPrivate; +class CellRange; +class Worksheet; +class WorksheetPrivate; + +class QXLSX_EXPORT CellFormula +{ +public: + enum FormulaType { NormalType, ArrayType, DataTableType, SharedType }; + +public: + CellFormula(); + CellFormula(const char *formula, FormulaType type = NormalType); + CellFormula(const QString &formula, FormulaType type = NormalType); + CellFormula(const QString &formula, const CellRange &ref, FormulaType type); + CellFormula(const CellFormula &other); + ~CellFormula(); + +public: + CellFormula &operator=(const CellFormula &other); + bool isValid() const; + + FormulaType formulaType() const; + QString formulaText() const; + CellRange reference() const; + int sharedIndex() const; + + bool operator==(const CellFormula &formula) const; + bool operator!=(const CellFormula &formula) const; + + bool saveToXml(QXmlStreamWriter &writer) const; + bool loadFromXml(QXmlStreamReader &reader); + +private: + friend class Worksheet; + friend class WorksheetPrivate; + QExplicitlySharedDataPointer d; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXCELLFORMULA_H diff --git a/QXlsx/header/xlsxcellformula_p.h b/QXlsx/header/xlsxcellformula_p.h new file mode 100644 index 0000000..6e8c8fc --- /dev/null +++ b/QXlsx/header/xlsxcellformula_p.h @@ -0,0 +1,33 @@ +// xlsxcellformula_p.h + +#ifndef XLSXCELLFORMULA_P_H +#define XLSXCELLFORMULA_P_H + +#include "xlsxcellformula.h" +#include "xlsxcellrange.h" +#include "xlsxglobal.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class CellFormulaPrivate : public QSharedData +{ +public: + CellFormulaPrivate(const QString &formula, + const CellRange &reference, + CellFormula::FormulaType type); + CellFormulaPrivate(const CellFormulaPrivate &other); + ~CellFormulaPrivate(); + + QString formula; // formula contents + CellFormula::FormulaType type; + CellRange reference; + bool ca; // Calculate Cell + int si; // Shared group index +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXCELLFORMULA_P_H diff --git a/QXlsx/header/xlsxcelllocation.h b/QXlsx/header/xlsxcelllocation.h new file mode 100644 index 0000000..8a5d54f --- /dev/null +++ b/QXlsx/header/xlsxcelllocation.h @@ -0,0 +1,33 @@ +// xlsxcelllocation.h + +#ifndef CELL_LOCATION_H +#define CELL_LOCATION_H + +#include "xlsxglobal.h" + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Cell; + +class QXLSX_EXPORT CellLocation +{ +public: + CellLocation(); + + int col; + int row; + + std::shared_ptr cell; +}; + +QT_END_NAMESPACE_XLSX +#endif diff --git a/QXlsx/header/xlsxcellrange.h b/QXlsx/header/xlsxcellrange.h new file mode 100644 index 0000000..1e37d40 --- /dev/null +++ b/QXlsx/header/xlsxcellrange.h @@ -0,0 +1,74 @@ +// xlsxcellrange.h + +#ifndef QXLSX_XLSXCELLRANGE_H +#define QXLSX_XLSXCELLRANGE_H + +#include "xlsxcellreference.h" +#include "xlsxglobal.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +// dev57 +class QXLSX_EXPORT CellRange +{ +public: + CellRange(); + CellRange(int firstRow, int firstColumn, int lastRow, int lastColumn); + CellRange(const CellReference &topLeft, const CellReference &bottomRight); + CellRange(const QString &range); + CellRange(const char *range); + CellRange(const CellRange &other); + ~CellRange(); + + QString toString(bool row_abs = false, bool col_abs = false) const; + bool isValid() const; + inline void setFirstRow(int row) { top = row; } + inline void setLastRow(int row) { bottom = row; } + inline void setFirstColumn(int col) { left = col; } + inline void setLastColumn(int col) { right = col; } + inline int firstRow() const { return top; } + inline int lastRow() const { return bottom; } + inline int firstColumn() const { return left; } + inline int lastColumn() const { return right; } + inline int rowCount() const { return bottom - top + 1; } + inline int columnCount() const { return right - left + 1; } + inline CellReference topLeft() const { return CellReference(top, left); } + inline CellReference topRight() const { return CellReference(top, right); } + inline CellReference bottomLeft() const { return CellReference(bottom, left); } + inline CellReference bottomRight() const { return CellReference(bottom, right); } + + inline void operator=(const CellRange &other) + { + top = other.top; + bottom = other.bottom; + left = other.left; + right = other.right; + } + inline bool operator==(const CellRange &other) const + { + return top == other.top && bottom == other.bottom && left == other.left && + right == other.right; + } + inline bool operator!=(const CellRange &other) const + { + return top != other.top || bottom != other.bottom || left != other.left || + right != other.right; + } + +private: + void init(const QString &range); + + int top; + int left; + int bottom; + int right; +}; + +QT_END_NAMESPACE_XLSX + +Q_DECLARE_TYPEINFO(QXlsx::CellRange, Q_MOVABLE_TYPE); + +#endif // QXLSX_XLSXCELLRANGE_H diff --git a/QXlsx/header/xlsxcellreference.h b/QXlsx/header/xlsxcellreference.h new file mode 100644 index 0000000..e240270 --- /dev/null +++ b/QXlsx/header/xlsxcellreference.h @@ -0,0 +1,48 @@ +// xlsxcellreference.h + +#ifndef QXLSX_XLSXCELLREFERENCE_H +#define QXLSX_XLSXCELLREFERENCE_H + +#include "xlsxglobal.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +class QXLSX_EXPORT CellReference +{ +public: + CellReference(); + CellReference(int row, int column); + CellReference(const QString &cell); + CellReference(const char *cell); + CellReference(const CellReference &other); + ~CellReference(); + + QString toString(bool row_abs = false, bool col_abs = false) const; + static CellReference fromString(const QString &cell); + bool isValid() const; + inline void setRow(int row) { _row = row; } + inline void setColumn(int col) { _column = col; } + inline int row() const { return _row; } + inline int column() const { return _column; } + + inline bool operator==(const CellReference &other) const + { + return _row == other._row && _column == other._column; + } + inline bool operator!=(const CellReference &other) const + { + return _row != other._row || _column != other._column; + } + +private: + void init(const QString &cell); + int _row, _column; +}; + +QT_END_NAMESPACE_XLSX + +Q_DECLARE_TYPEINFO(QXlsx::CellReference, Q_MOVABLE_TYPE); + +#endif // QXLSX_XLSXCELLREFERENCE_H diff --git a/QXlsx/header/xlsxchart.h b/QXlsx/header/xlsxchart.h new file mode 100644 index 0000000..ba68d26 --- /dev/null +++ b/QXlsx/header/xlsxchart.h @@ -0,0 +1,77 @@ +// xlsxchart.h + +#ifndef QXLSX_CHART_H +#define QXLSX_CHART_H + +#include "xlsxabstractooxmlfile.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class AbstractSheet; +class Worksheet; +class ChartPrivate; +class CellRange; +class DrawingAnchor; + +class QXLSX_EXPORT Chart : public AbstractOOXmlFile +{ + Q_DECLARE_PRIVATE(Chart) +public: + enum ChartType { // 16 type of chart (ECMA 376) + CT_NoStatementChart = 0, // Zero is internally used for unknown types + CT_AreaChart, + CT_Area3DChart, + CT_LineChart, + CT_Line3DChart, + CT_StockChart, + CT_RadarChart, + CT_ScatterChart, + CT_PieChart, + CT_Pie3DChart, + CT_DoughnutChart, + CT_BarChart, + CT_Bar3DChart, + CT_OfPieChart, + CT_SurfaceChart, + CT_Surface3DChart, + CT_BubbleChart, + }; + enum ChartAxisPos { None = (-1), Left = 0, Right, Top, Bottom }; + +private: + friend class AbstractSheet; + friend class Worksheet; + friend class Chartsheet; + friend class DrawingAnchor; + +private: + Chart(AbstractSheet *parent, CreateFlag flag); + +public: + ~Chart(); + +public: + void addSeries(const CellRange &range, + AbstractSheet *sheet = nullptr, + bool headerH = false, + bool headerV = false, + bool swapHeaders = false); + void setChartType(ChartType type); + void setChartStyle(int id); + void setAxisTitle(Chart::ChartAxisPos pos, QString axisTitle); + void setChartTitle(QString strchartTitle); + void setChartLegend(Chart::ChartAxisPos legendPos, bool overlap = false); + void setGridlinesEnable(bool majorGridlinesEnable = false, bool minorGridlinesEnable = false); + +public: + bool loadFromXmlFile(QIODevice *device) override; + void saveToXmlFile(QIODevice *device) const override; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_CHART_H diff --git a/QXlsx/header/xlsxchart_p.h b/QXlsx/header/xlsxchart_p.h new file mode 100644 index 0000000..8460229 --- /dev/null +++ b/QXlsx/header/xlsxchart_p.h @@ -0,0 +1,148 @@ +// xlsxchart_p.h + +#ifndef QXLSX_CHART_P_H +#define QXLSX_CHART_P_H + +#include "xlsxabstractooxmlfile_p.h" +#include "xlsxchart.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class XlsxSeries +{ +public: + // At present, we care about number cell ranges only! + QString numberDataSource_numRef; // yval, val + QString axDataSource_numRef; // xval, cat + QString headerH_numRef; + QString headerV_numRef; + bool swapHeader = false; +}; + +class XlsxAxis +{ +public: + enum Type { T_None = (-1), T_Cat, T_Val, T_Date, T_Ser }; + enum AxisPos { None = (-1), Left, Right, Top, Bottom }; + +public: + XlsxAxis() {} + + XlsxAxis(Type t, XlsxAxis::AxisPos p, int id, int crossId, QString axisTitle = QString()) + { + type = t; + axisPos = p; + axisId = id; + crossAx = crossId; + + if (!axisTitle.isEmpty()) { + axisNames[p] = axisTitle; + } + } + +public: + Type type; + XlsxAxis::AxisPos axisPos; + int axisId; + int crossAx; + QMap axisNames; +}; + +class ChartPrivate : public AbstractOOXmlFilePrivate +{ + Q_DECLARE_PUBLIC(Chart) + +public: + ChartPrivate(Chart *q, Chart::CreateFlag flag); + ~ChartPrivate(); + +public: + bool loadXmlChart(QXmlStreamReader &reader); + bool loadXmlPlotArea(QXmlStreamReader &reader); + +protected: + bool loadXmlPlotAreaElement(QXmlStreamReader &reader); + +public: + bool loadXmlXxxChart(QXmlStreamReader &reader); + bool loadXmlSer(QXmlStreamReader &reader); + QString loadXmlNumRef(QXmlStreamReader &reader); + QString loadXmlStrRef(QXmlStreamReader &reader); + bool loadXmlChartTitle(QXmlStreamReader &reader); + bool loadXmlChartLegend(QXmlStreamReader &reader); + +protected: + bool loadXmlChartTitleTx(QXmlStreamReader &reader); + bool loadXmlChartTitleTxRich(QXmlStreamReader &reader); + bool loadXmlChartTitleTxRichP(QXmlStreamReader &reader); + bool loadXmlChartTitleTxRichP_R(QXmlStreamReader &reader); + +protected: + bool loadXmlAxisCatAx(QXmlStreamReader &reader); + bool loadXmlAxisDateAx(QXmlStreamReader &reader); + bool loadXmlAxisSerAx(QXmlStreamReader &reader); + bool loadXmlAxisValAx(QXmlStreamReader &reader); + bool loadXmlAxisEG_AxShared(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Scaling(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Overlay(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Tx(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Tx_Rich(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Tx_Rich_P(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Tx_Rich_P_pPr(QXmlStreamReader &reader, XlsxAxis *axis); + bool loadXmlAxisEG_AxShared_Title_Tx_Rich_P_R(QXmlStreamReader &reader, XlsxAxis *axis); + + QString readSubTree(QXmlStreamReader &reader); + +public: + void saveXmlChart(QXmlStreamWriter &writer) const; + void saveXmlChartTitle(QXmlStreamWriter &writer) const; + void saveXmlPieChart(QXmlStreamWriter &writer) const; + void saveXmlBarChart(QXmlStreamWriter &writer) const; + void saveXmlLineChart(QXmlStreamWriter &writer) const; + void saveXmlScatterChart(QXmlStreamWriter &writer) const; + void saveXmlAreaChart(QXmlStreamWriter &writer) const; + void saveXmlDoughnutChart(QXmlStreamWriter &writer) const; + void saveXmlSer(QXmlStreamWriter &writer, XlsxSeries *ser, int id) const; + void saveXmlAxis(QXmlStreamWriter &writer) const; + void saveXmlChartLegend(QXmlStreamWriter &writer) const; + +protected: + void saveXmlAxisCatAx(QXmlStreamWriter &writer, XlsxAxis *axis) const; + void saveXmlAxisDateAx(QXmlStreamWriter &writer, XlsxAxis *axis) const; + void saveXmlAxisSerAx(QXmlStreamWriter &writer, XlsxAxis *axis) const; + void saveXmlAxisValAx(QXmlStreamWriter &writer, XlsxAxis *axis) const; + + void saveXmlAxisEG_AxShared(QXmlStreamWriter &writer, XlsxAxis *axis) const; + void saveXmlAxisEG_AxShared_Title(QXmlStreamWriter &writer, XlsxAxis *axis) const; + QString GetAxisPosString(XlsxAxis::AxisPos axisPos) const; + QString GetAxisName(XlsxAxis *ptrXlsxAxis) const; + +public: + Chart::ChartType chartType; + QList> seriesList; + QList> axisList; + QMap axisNames; + QString chartTitle; + AbstractSheet *sheet; + Chart::ChartAxisPos legendPos; + bool legendOverlay; + bool majorGridlinesEnabled; + bool minorGridlinesEnabled; + + QString layout; // only for storing a read file +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_CHART_P_H diff --git a/QXlsx/header/xlsxchartsheet.h b/QXlsx/header/xlsxchartsheet.h new file mode 100644 index 0000000..889cac6 --- /dev/null +++ b/QXlsx/header/xlsxchartsheet.h @@ -0,0 +1,38 @@ +// xlsxchartsheet.h + +#ifndef XLSXCHARTSHEET_H +#define XLSXCHARTSHEET_H + +#include "xlsxabstractsheet.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Workbook; +class DocumentPrivate; +class ChartsheetPrivate; +class Chart; + +class QXLSX_EXPORT Chartsheet : public AbstractSheet +{ + Q_DECLARE_PRIVATE(Chartsheet) + +public: + ~Chartsheet(); + Chart *chart(); + +private: + friend class DocumentPrivate; + friend class Workbook; + + Chartsheet(const QString &sheetName, int sheetId, Workbook *book, CreateFlag flag); + Chartsheet *copy(const QString &distName, int distId) const override; + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXCHARTSHEET_H diff --git a/QXlsx/header/xlsxchartsheet_p.h b/QXlsx/header/xlsxchartsheet_p.h new file mode 100644 index 0000000..406807e --- /dev/null +++ b/QXlsx/header/xlsxchartsheet_p.h @@ -0,0 +1,25 @@ +// xlsxchartsheet_p.h + +#ifndef XLSXCHARTSHEET_P_H +#define XLSXCHARTSHEET_P_H + +#include "xlsxabstractsheet_p.h" +#include "xlsxchartsheet.h" +#include "xlsxglobal.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +class ChartsheetPrivate : public AbstractSheetPrivate +{ + Q_DECLARE_PUBLIC(Chartsheet) +public: + ChartsheetPrivate(Chartsheet *p, Chartsheet::CreateFlag flag); + ~ChartsheetPrivate(); + + Chart *chart; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXCHARTSHEET_P_H diff --git a/QXlsx/header/xlsxcolor_p.h b/QXlsx/header/xlsxcolor_p.h new file mode 100644 index 0000000..9528fa9 --- /dev/null +++ b/QXlsx/header/xlsxcolor_p.h @@ -0,0 +1,59 @@ +// xlsxcolor_p.h + +#ifndef QXLSX_XLSXCOLOR_P_H +#define QXLSX_XLSXCOLOR_P_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Styles; + +class XlsxColor +{ +public: + explicit XlsxColor(const QColor &color = QColor()); + explicit XlsxColor(const QString &theme, const QString &tint = QString()); + explicit XlsxColor(int index); + + bool isThemeColor() const; + bool isIndexedColor() const; + bool isRgbColor() const; + bool isInvalid() const; + + QColor rgbColor() const; + int indexedColor() const; + QStringList themeColor() const; + + operator QVariant() const; + + static QColor fromARGBString(const QString &c); + static QString toARGBString(const QColor &c); + + bool saveToXml(QXmlStreamWriter &writer, const QString &node = QString()) const; + bool loadFromXml(QXmlStreamReader &reader); + +private: + QVariant val; +}; + +#if !defined(QT_NO_DATASTREAM) +QDataStream &operator<<(QDataStream &, const XlsxColor &); +QDataStream &operator>>(QDataStream &, XlsxColor &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const XlsxColor &c); +#endif + +QT_END_NAMESPACE_XLSX + +Q_DECLARE_METATYPE(QXlsx::XlsxColor) + +#endif // QXLSX_XLSXCOLOR_P_H diff --git a/QXlsx/header/xlsxconditionalformatting.h b/QXlsx/header/xlsxconditionalformatting.h new file mode 100644 index 0000000..4fbdb5c --- /dev/null +++ b/QXlsx/header/xlsxconditionalformatting.h @@ -0,0 +1,131 @@ +// xlsxconditionalformatting.h + +#ifndef QXLSX_XLSXCONDITIONALFORMATTING_H +#define QXLSX_XLSXCONDITIONALFORMATTING_H + +#include "xlsxcellrange.h" +#include "xlsxcellreference.h" +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include +#include +#include + +class ConditionalFormattingTest; + +QT_BEGIN_NAMESPACE_XLSX + +class Format; +class Worksheet; +class Styles; +class ConditionalFormattingPrivate; + +class QXLSX_EXPORT ConditionalFormatting +{ +public: + enum HighlightRuleType { + Highlight_LessThan, + Highlight_LessThanOrEqual, + Highlight_Equal, + Highlight_NotEqual, + Highlight_GreaterThanOrEqual, + Highlight_GreaterThan, + Highlight_Between, + Highlight_NotBetween, + + Highlight_ContainsText, + Highlight_NotContainsText, + Highlight_BeginsWith, + Highlight_EndsWith, + + Highlight_TimePeriod, + + Highlight_Duplicate, + Highlight_Unique, + Highlight_Blanks, + Highlight_NoBlanks, + Highlight_Errors, + Highlight_NoErrors, + + Highlight_Top, + Highlight_TopPercent, + Highlight_Bottom, + Highlight_BottomPercent, + + Highlight_AboveAverage, + Highlight_AboveOrEqualAverage, + Highlight_AboveStdDev1, + Highlight_AboveStdDev2, + Highlight_AboveStdDev3, + Highlight_BelowAverage, + Highlight_BelowOrEqualAverage, + Highlight_BelowStdDev1, + Highlight_BelowStdDev2, + Highlight_BelowStdDev3, + + Highlight_Expression + }; + + enum ValueObjectType { VOT_Formula, VOT_Max, VOT_Min, VOT_Num, VOT_Percent, VOT_Percentile }; + +public: + ConditionalFormatting(); + ConditionalFormatting(const ConditionalFormatting &other); + ~ConditionalFormatting(); + +public: + bool addHighlightCellsRule(HighlightRuleType type, + const Format &format, + bool stopIfTrue = false); + bool addHighlightCellsRule(HighlightRuleType type, + const QString &formula1, + const Format &format, + bool stopIfTrue = false); + bool addHighlightCellsRule(HighlightRuleType type, + const QString &formula1, + const QString &formula2, + const Format &format, + bool stopIfTrue = false); + bool addDataBarRule(const QColor &color, bool showData = true, bool stopIfTrue = false); + bool addDataBarRule(const QColor &color, + ValueObjectType type1, + const QString &val1, + ValueObjectType type2, + const QString &val2, + bool showData = true, + bool stopIfTrue = false); + bool + add2ColorScaleRule(const QColor &minColor, const QColor &maxColor, bool stopIfTrue = false); + bool add3ColorScaleRule(const QColor &minColor, + const QColor &midColor, + const QColor &maxColor, + bool stopIfTrue = false); + + QList ranges() const; + + void addCell(const CellReference &cell); + void addCell(int row, int col); + void addRange(int firstRow, int firstCol, int lastRow, int lastCol); + void addRange(const CellRange &range); + + // needed by QSharedDataPointer!! + ConditionalFormatting &operator=(const ConditionalFormatting &other); + +private: + friend class Worksheet; + friend class ::ConditionalFormattingTest; + +private: + bool saveToXml(QXmlStreamWriter &writer) const; + bool loadFromXml(QXmlStreamReader &reader, Styles *styles = nullptr); + + QSharedDataPointer d; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXCONDITIONALFORMATTING_H diff --git a/QXlsx/header/xlsxconditionalformatting_p.h b/QXlsx/header/xlsxconditionalformatting_p.h new file mode 100644 index 0000000..262e5f3 --- /dev/null +++ b/QXlsx/header/xlsxconditionalformatting_p.h @@ -0,0 +1,102 @@ +// xlsxconditionalformatting_p.h + +#ifndef XLSXCONDITIONALFORMATTING_P_H +#define XLSXCONDITIONALFORMATTING_P_H + +#include "xlsxcolor_p.h" +#include "xlsxconditionalformatting.h" +#include "xlsxformat.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class XlsxCfVoData +{ +public: + XlsxCfVoData() + : gte(true) + { + } + + XlsxCfVoData(ConditionalFormatting::ValueObjectType type, const QString &value, bool gte = true) + : type(type) + , value(value) + , gte(gte) + { + } + + ConditionalFormatting::ValueObjectType type; + QString value; + bool gte; +}; + +class XlsxCfRuleData +{ +public: + enum Attribute { + A_type, + A_dxfId, + // A_priority, + A_stopIfTrue, + A_aboveAverage, + A_percent, + A_bottom, + A_operator, + A_text, + A_timePeriod, + A_rank, + A_stdDev, + A_equalAverage, + + A_dxfFormat, + A_formula1, + A_formula2, + A_formula3, + A_formula1_temp, + + A_color1, + A_color2, + A_color3, + + A_cfvo1, + A_cfvo2, + A_cfvo3, + + A_hideData + }; + + XlsxCfRuleData() + : priority(1) + { + } + + int priority; + Format dxfFormat; + QMap attrs; +}; + +class ConditionalFormattingPrivate : public QSharedData +{ +public: + ConditionalFormattingPrivate(); + ConditionalFormattingPrivate(const ConditionalFormattingPrivate &other); + ~ConditionalFormattingPrivate(); + + void writeCfVo(QXmlStreamWriter &writer, const XlsxCfVoData &cfvo) const; + bool readCfVo(QXmlStreamReader &reader, XlsxCfVoData &cfvo); + bool readCfRule(QXmlStreamReader &reader, XlsxCfRuleData *cfRule, Styles *styles); + bool readCfDataBar(QXmlStreamReader &reader, XlsxCfRuleData *cfRule); + bool readCfColorScale(QXmlStreamReader &reader, XlsxCfRuleData *cfRule); + + QList> cfRules; + QList ranges; +}; + +QT_END_NAMESPACE_XLSX + +Q_DECLARE_METATYPE(QXlsx::XlsxCfVoData) +#endif // XLSXCONDITIONALFORMATTING_P_H diff --git a/QXlsx/header/xlsxcontenttypes_p.h b/QXlsx/header/xlsxcontenttypes_p.h new file mode 100644 index 0000000..32c8613 --- /dev/null +++ b/QXlsx/header/xlsxcontenttypes_p.h @@ -0,0 +1,56 @@ +// xlsxcontenttypes_p.h + +#ifndef XLSXCONTENTTYPES_H +#define XLSXCONTENTTYPES_H + +#include "xlsxabstractooxmlfile.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class ContentTypes : public AbstractOOXmlFile +{ +public: + ContentTypes(CreateFlag flag); + + void addDefault(const QString &key, const QString &value); + void addOverride(const QString &key, const QString &value); + + // Convenient function for addOverride() + void addDocPropCore(); + void addDocPropApp(); + void addStyles(); + void addTheme(); + void addWorkbook(); + void addWorksheetName(const QString &name); + void addChartsheetName(const QString &name); + void addChartName(const QString &name); + void addDrawingName(const QString &name); + void addCommentName(const QString &name); + void addTableName(const QString &name); + void addExternalLinkName(const QString &name); + void addSharedString(); + void addVmlName(); + void addCalcChain(); + void addVbaProject(); + + void clearOverrides(); + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + +private: + QMap m_defaults; + QMap m_overrides; + + QString m_package_prefix; + QString m_document_prefix; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXCONTENTTYPES_H diff --git a/QXlsx/header/xlsxdatavalidation.h b/QXlsx/header/xlsxdatavalidation.h new file mode 100644 index 0000000..aea3bea --- /dev/null +++ b/QXlsx/header/xlsxdatavalidation.h @@ -0,0 +1,93 @@ +// xlsxvalidation.h + +#ifndef QXLSX_XLSXDATAVALIDATION_H +#define QXLSX_XLSXDATAVALIDATION_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include +#include + +class QXmlStreamReader; +class QXmlStreamWriter; + +QT_BEGIN_NAMESPACE_XLSX + +class Worksheet; +class CellRange; +class CellReference; + +class DataValidationPrivate; +class QXLSX_EXPORT DataValidation +{ +public: + enum ValidationType { None, Whole, Decimal, List, Date, Time, TextLength, Custom }; + + enum ValidationOperator { + Between, + NotBetween, + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual + }; + + enum ErrorStyle { Stop, Warning, Information }; + + DataValidation(); + DataValidation(ValidationType type, + ValidationOperator op = Between, + const QString &formula1 = QString(), + const QString &formula2 = QString(), + bool allowBlank = false); + DataValidation(const DataValidation &other); + ~DataValidation(); + + ValidationType validationType() const; + ValidationOperator validationOperator() const; + ErrorStyle errorStyle() const; + QString formula1() const; + QString formula2() const; + bool allowBlank() const; + QString errorMessage() const; + QString errorMessageTitle() const; + QString promptMessage() const; + QString promptMessageTitle() const; + bool isPromptMessageVisible() const; + bool isErrorMessageVisible() const; + QList ranges() const; + + void setValidationType(ValidationType type); + void setValidationOperator(ValidationOperator op); + void setErrorStyle(ErrorStyle es); + void setFormula1(const QString &formula); + void setFormula2(const QString &formula); + void setErrorMessage(const QString &error, const QString &title = QString()); + void setPromptMessage(const QString &prompt, const QString &title = QString()); + void setAllowBlank(bool enable); + void setPromptMessageVisible(bool visible); + void setErrorMessageVisible(bool visible); + + void addCell(const CellReference &cell); + void addCell(int row, int col); + void addRange(int firstRow, int firstCol, int lastRow, int lastCol); + void addRange(const CellRange &range); + + DataValidation &operator=(const DataValidation &other); + + bool saveToXml(QXmlStreamWriter &writer) const; + static DataValidation loadFromXml(QXmlStreamReader &reader); + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXDATAVALIDATION_H diff --git a/QXlsx/header/xlsxdatavalidation_p.h b/QXlsx/header/xlsxdatavalidation_p.h new file mode 100644 index 0000000..ebb407c --- /dev/null +++ b/QXlsx/header/xlsxdatavalidation_p.h @@ -0,0 +1,41 @@ +// xlsxdatavalidation_p.h + +#ifndef XLSXDATAVALIDATION_P_H +#define XLSXDATAVALIDATION_P_H + +#include "xlsxdatavalidation.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class DataValidationPrivate : public QSharedData +{ +public: + DataValidationPrivate(); + DataValidationPrivate(DataValidation::ValidationType type, + DataValidation::ValidationOperator op, + const QString &formula1, + const QString &formula2, + bool allowBlank); + DataValidationPrivate(const DataValidationPrivate &other); + ~DataValidationPrivate(); + + DataValidation::ValidationType validationType; + DataValidation::ValidationOperator validationOperator; + DataValidation::ErrorStyle errorStyle; + bool allowBlank; + bool isPromptMessageVisible; + bool isErrorMessageVisible; + QString formula1; + QString formula2; + QString errorMessage; + QString errorMessageTitle; + QString promptMessage; + QString promptMessageTitle; + QList ranges; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXDATAVALIDATION_P_H diff --git a/QXlsx/header/xlsxdatetype.h b/QXlsx/header/xlsxdatetype.h new file mode 100644 index 0000000..cf09f4c --- /dev/null +++ b/QXlsx/header/xlsxdatetype.h @@ -0,0 +1,48 @@ +// xlsxdatetype.h + +#ifndef QXLSX_XLSXDATETYPE_H +#define QXLSX_XLSXDATETYPE_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class QXLSX_EXPORT DateType +{ +public: + DateType(); + /* + DateType(bool is1904 = false); + DateType(double d, bool is1904 = false); + DateType(QDateTime qdt, bool is1904 = false); + DateType(QDate qd, bool is1904 = false); + DateType(QTime qt, bool is1904 = false); + public: + enum currentDateType { DateAndTimeType, OnlyDateType, OnlyTimeType }; + public: + currentDateType getType(); + bool getValue(QDateTime* pQdt); + bool getValue(QDate* pQd); + bool getValue(QTime* pQt); + bool getValue(double* pD); + + protected: + + protected: + bool isSet; + double dValue; + bool is1904Type; + currentDateType dType; + */ +}; + +QT_END_NAMESPACE_XLSX +#endif diff --git a/QXlsx/header/xlsxdocpropsapp_p.h b/QXlsx/header/xlsxdocpropsapp_p.h new file mode 100644 index 0000000..e326a02 --- /dev/null +++ b/QXlsx/header/xlsxdocpropsapp_p.h @@ -0,0 +1,40 @@ +// xlsxdocpropsapp_p.h + +#ifndef XLSXDOCPROPSAPP_H +#define XLSXDOCPROPSAPP_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxglobal.h" + +#include +#include +#include + +class QIODevice; + +QT_BEGIN_NAMESPACE_XLSX + +class DocPropsApp : public AbstractOOXmlFile +{ +public: + DocPropsApp(CreateFlag flag); + + void addPartTitle(const QString &title); + void addHeadingPair(const QString &name, int value); + + bool setProperty(const QString &name, const QString &value); + QString property(const QString &name) const; + QStringList propertyNames() const; + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + +private: + QStringList m_titlesOfPartsList; + QList> m_headingPairsList; + QMap m_properties; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXDOCPROPSAPP_H diff --git a/QXlsx/header/xlsxdocpropscore_p.h b/QXlsx/header/xlsxdocpropscore_p.h new file mode 100644 index 0000000..9dd827b --- /dev/null +++ b/QXlsx/header/xlsxdocpropscore_p.h @@ -0,0 +1,34 @@ +// xlsxdocpropscore_p.h + +#ifndef XLSXDOCPROPSCORE_H +#define XLSXDOCPROPSCORE_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxglobal.h" + +#include +#include + +class QIODevice; + +QT_BEGIN_NAMESPACE_XLSX + +class DocPropsCore : public AbstractOOXmlFile +{ +public: + explicit DocPropsCore(CreateFlag flag); + + bool setProperty(const QString &name, const QString &value); + QString property(const QString &name) const; + QStringList propertyNames() const; + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + +private: + QMap m_properties; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXDOCPROPSCORE_H diff --git a/QXlsx/header/xlsxdocument.h b/QXlsx/header/xlsxdocument.h new file mode 100644 index 0000000..85ad347 --- /dev/null +++ b/QXlsx/header/xlsxdocument.h @@ -0,0 +1,145 @@ +// xlsxdocument.h + +#ifndef QXLSX_XLSXDOCUMENT_H +#define QXLSX_XLSXDOCUMENT_H + +#include "xlsxformat.h" +#include "xlsxglobal.h" +#include "xlsxworksheet.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Workbook; +class Cell; +class CellRange; +class DataValidation; +class ConditionalFormatting; +class Chart; +class CellReference; +class DocumentPrivate; + +class QXLSX_EXPORT Document : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(Document) // D-Pointer. Qt classes have a Q_DECLARE_PRIVATE + // macro in the public class. The macro reads: qglobal.h +public: + explicit Document(QObject *parent = nullptr); + Document(const QString &xlsxName, QObject *parent = nullptr); + Document(QIODevice *device, QObject *parent = nullptr); + ~Document(); + + bool write(const CellReference &cell, const QVariant &value, const Format &format = Format()); + bool write(int row, int col, const QVariant &value, const Format &format = Format()); + + QVariant read(const CellReference &cell) const; + QVariant read(int row, int col) const; + + int insertImage(int row, int col, const QImage &image); + bool getImage(int imageIndex, QImage &img); + bool getImage(int row, int col, QImage &img); + uint getImageCount(); + + Chart *insertChart(int row, int col, const QSize &size); + + bool mergeCells(const CellRange &range, const Format &format = Format()); + bool unmergeCells(const CellRange &range); + + bool setColumnWidth(const CellRange &range, double width); + bool setColumnFormat(const CellRange &range, const Format &format); + bool setColumnHidden(const CellRange &range, bool hidden); + bool setColumnWidth(int column, double width); + bool setColumnFormat(int column, const Format &format); + bool setColumnHidden(int column, bool hidden); + bool setColumnWidth(int colFirst, int colLast, double width); + bool setColumnFormat(int colFirst, int colLast, const Format &format); + bool setColumnHidden(int colFirst, int colLast, bool hidden); + + double columnWidth(int column); + Format columnFormat(int column); + bool isColumnHidden(int column); + + bool setRowHeight(int row, double height); + bool setRowFormat(int row, const Format &format); + bool setRowHidden(int row, bool hidden); + bool setRowHeight(int rowFirst, int rowLast, double height); + bool setRowFormat(int rowFirst, int rowLast, const Format &format); + bool setRowHidden(int rowFirst, int rowLast, bool hidden); + + double rowHeight(int row); + Format rowFormat(int row); + bool isRowHidden(int row); + + bool groupRows(int rowFirst, int rowLast, bool collapsed = true); + bool groupColumns(int colFirst, int colLast, bool collapsed = true); + + bool addDataValidation(const DataValidation &validation); + bool addConditionalFormatting(const ConditionalFormatting &cf); + + Cell *cellAt(const CellReference &cell) const; + Cell *cellAt(int row, int col) const; + + bool defineName(const QString &name, + const QString &formula, + const QString &comment = QString(), + const QString &scope = QString()); + + CellRange dimension() const; + + QString documentProperty(const QString &name) const; + void setDocumentProperty(const QString &name, const QString &property); + QStringList documentPropertyNames() const; + + QStringList sheetNames() const; + bool addSheet(const QString &name = QString(), + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet); + bool insertSheet(int index, + const QString &name = QString(), + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet); + bool selectSheet(const QString &name); + bool selectSheet(int index); + bool renameSheet(const QString &oldName, const QString &newName); + bool copySheet(const QString &srcName, const QString &distName = QString()); + bool moveSheet(const QString &srcName, int distIndex); + bool deleteSheet(const QString &name); + + Workbook *workbook() const; + AbstractSheet *sheet(const QString &sheetName) const; + AbstractSheet *currentSheet() const; + Worksheet *currentWorksheet() const; + + bool save() const; + bool saveAs(const QString &xlsXname) const; + bool saveAs(QIODevice *device) const; + + // copy style from one xlsx file to other + static bool copyStyle(const QString &from, const QString &to); + + bool isLoadPackage() const; + bool load() const; // equals to isLoadPackage() + + bool changeimage(int filenoinmidea, QString newfile); // add by liufeijin20181025 + + bool autosizeColumnWidth(const CellRange &range); + bool autosizeColumnWidth(int column); + bool autosizeColumnWidth(int colFirst, int colLast); + bool autosizeColumnWidth(void); + +private: + QMap getMaximalColumnWidth(int firstRow = 1, int lastRow = INT_MAX); + +private: + Q_DISABLE_COPY(Document) // Disables the use of copy constructors and + // assignment operators for the given Class. + DocumentPrivate *const d_ptr; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXDOCUMENT_H diff --git a/QXlsx/header/xlsxdocument_p.h b/QXlsx/header/xlsxdocument_p.h new file mode 100644 index 0000000..978df5e --- /dev/null +++ b/QXlsx/header/xlsxdocument_p.h @@ -0,0 +1,41 @@ +// xlsxdocument_p.h + +#ifndef XLSXDOCUMENT_P_H +#define XLSXDOCUMENT_P_H + +#include "xlsxcontenttypes_p.h" +#include "xlsxdocument.h" +#include "xlsxglobal.h" +#include "xlsxworkbook.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class DocumentPrivate +{ + Q_DECLARE_PUBLIC(Document) +public: + DocumentPrivate(Document *p); + void init(); + + bool loadPackage(QIODevice *device); + bool savePackage(QIODevice *device) const; + + // copy style from one xlsx file to other + static bool copyStyle(const QString &from, const QString &to); + + Document *q_ptr; + const QString defaultPackageName; // default name when package name not specified + QString packageName; // name of the .xlsx file + + QMap documentProperties; // core, app and custom properties + QSharedPointer workbook; + std::shared_ptr contentTypes; + bool isLoad; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXDOCUMENT_P_H diff --git a/QXlsx/header/xlsxdrawing_p.h b/QXlsx/header/xlsxdrawing_p.h new file mode 100644 index 0000000..75b6b1d --- /dev/null +++ b/QXlsx/header/xlsxdrawing_p.h @@ -0,0 +1,38 @@ +// xlsxdrwaing_p.h + +#ifndef QXLSX_DRAWING_H +#define QXLSX_DRAWING_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxrelationships_p.h" + +#include +#include +#include + +class QIODevice; +class QXmlStreamWriter; + +QT_BEGIN_NAMESPACE_XLSX + +class DrawingAnchor; +class Workbook; +class AbstractSheet; +class MediaFile; + +class Drawing : public AbstractOOXmlFile +{ +public: + Drawing(AbstractSheet *sheet, CreateFlag flag); + ~Drawing(); + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + + AbstractSheet *sheet; + Workbook *workbook; + QList anchors; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_DRAWING_H diff --git a/QXlsx/header/xlsxdrawinganchor_p.h b/QXlsx/header/xlsxdrawinganchor_p.h new file mode 100644 index 0000000..a876da5 --- /dev/null +++ b/QXlsx/header/xlsxdrawinganchor_p.h @@ -0,0 +1,163 @@ +// xlsxdrawinganchor_p.h + +#ifndef QXLSX_XLSXDRAWINGANCHOR_P_H +#define QXLSX_XLSXDRAWINGANCHOR_P_H + +#include "xlsxglobal.h" + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Drawing; +class MediaFile; +class Chart; + +// Helper class +struct XlsxMarker { + XlsxMarker() {} + XlsxMarker(int row, int column, int rowOffset, int colOffset) + : cell(QPoint(row, column)) + , offset(rowOffset, colOffset) + { + } + + int row() const { return cell.x(); } + int col() const { return cell.y(); } + int rowOff() const { return offset.width(); } + int colOff() const { return offset.height(); } + + QPoint cell; + QSize offset; +}; + +class DrawingAnchor +{ +public: + enum ObjectType { GraphicFrame, Shape, GroupShape, ConnectionShape, Picture, Unknown }; + + DrawingAnchor(Drawing *drawing, ObjectType objectType); + virtual ~DrawingAnchor(); + + void setObjectPicture(const QImage &img); + bool getObjectPicture(QImage &img); + + void setObjectGraphicFrame(QSharedPointer chart); + + virtual bool loadFromXml(QXmlStreamReader &reader) = 0; + virtual void saveToXml(QXmlStreamWriter &writer) const = 0; + + virtual int row() const; + virtual int col() const; + +protected: + QPoint loadXmlPos(QXmlStreamReader &reader); + QSize loadXmlExt(QXmlStreamReader &reader); + XlsxMarker loadXmlMarker(QXmlStreamReader &reader, const QString &node); + void loadXmlObject(QXmlStreamReader &reader); + void loadXmlObjectShape(QXmlStreamReader &reader); + void loadXmlObjectGroupShape(QXmlStreamReader &reader); + void loadXmlObjectGraphicFrame(QXmlStreamReader &reader); + void loadXmlObjectConnectionShape(QXmlStreamReader &reader); + void loadXmlObjectPicture(QXmlStreamReader &reader); + + void saveXmlPos(QXmlStreamWriter &writer, const QPoint &pos) const; + void saveXmlExt(QXmlStreamWriter &writer, const QSize &ext) const; + void saveXmlMarker(QXmlStreamWriter &writer, + const XlsxMarker &marker, + const QString &node) const; + void saveXmlObject(QXmlStreamWriter &writer) const; + void saveXmlObjectShape(QXmlStreamWriter &writer) const; + void saveXmlObjectGroupShape(QXmlStreamWriter &writer) const; + void saveXmlObjectGraphicFrame(QXmlStreamWriter &writer) const; + void saveXmlObjectConnectionShape(QXmlStreamWriter &writer) const; + void saveXmlObjectPicture(QXmlStreamWriter &writer) const; + + Drawing *m_drawing; + ObjectType m_objectType; + std::shared_ptr m_pictureFile; + QSharedPointer m_chartFile; + + int m_id; + +public: + int getm_id(); + +protected: + // liufeij {{ + void setObjectShape(const QImage &img); // liufeij + + QString editASName; + // below only for twocellanchor shape + QPoint posTA; // for shape liufeij 20181024 + QSize extTA; // for shape liufeij 20181024 + int rotWithShapeTA; //// for shape liufeij 20181024 + int dpiTA; //// for shape liufeij 20181024 + QString sp_textlink, sp_macro, sp_blip_cstate, sp_blip_rembed; + + // BELOW only for cxnSp shape + QString cxnSp_filpV, cxnSp_macro; + // below for cxnsp and sp + QString xsp_cNvPR_name, xsp_cNvPR_id; // x measns shape and cxnSp together using + QString xbwMode; // same as above + QString xIn_algn, xIn_cmpd, xIn_cap, xIn_w; // cxnSp only need xIn_w + QString xprstGeom_prst; + QString x_headEnd_w, x_headEnd_len, x_headEnd_tyep; + QString x_tailEnd_w, x_tailEnd_len, x_tailEnd_tyep; + QString Style_inref_idx, style_fillref_idx, style_effectref_idx, style_forntref_idx; + QString Style_inref_val, style_fillref_val, style_effectref_val, style_forntref_val; + // liufeij }} +}; + +class DrawingAbsoluteAnchor : public DrawingAnchor +{ +public: + DrawingAbsoluteAnchor(Drawing *drawing, ObjectType objectType = Unknown); + + QPoint pos; + QSize ext; + + bool loadFromXml(QXmlStreamReader &reader) override; + void saveToXml(QXmlStreamWriter &writer) const override; +}; + +class DrawingOneCellAnchor : public DrawingAnchor +{ +public: + DrawingOneCellAnchor(Drawing *drawing, ObjectType objectType = Unknown); + + XlsxMarker from; + QSize ext; + + int row() const override; + int col() const override; + + bool loadFromXml(QXmlStreamReader &reader) override; + void saveToXml(QXmlStreamWriter &writer) const override; +}; + +class DrawingTwoCellAnchor : public DrawingAnchor +{ +public: + DrawingTwoCellAnchor(Drawing *drawing, ObjectType objectType = Unknown); + + XlsxMarker from; + XlsxMarker to; + + int row() const override; + int col() const override; + + bool loadFromXml(QXmlStreamReader &reader) override; + void saveToXml(QXmlStreamWriter &writer) const override; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXDRAWINGANCHOR_P_H diff --git a/QXlsx/header/xlsxformat.h b/QXlsx/header/xlsxformat.h new file mode 100644 index 0000000..5644e5f --- /dev/null +++ b/QXlsx/header/xlsxformat.h @@ -0,0 +1,255 @@ +// xlsxformat.h + +#ifndef QXLSX_FORMAT_H +#define QXLSX_FORMAT_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include +#include + +class FormatTest; + +QT_BEGIN_NAMESPACE_XLSX + +class Styles; +class Worksheet; +class WorksheetPrivate; +class RichStringPrivate; +class SharedStrings; + +class FormatPrivate; + +class QXLSX_EXPORT Format +{ +public: + enum FontScript { FontScriptNormal, FontScriptSuper, FontScriptSub }; + + enum FontUnderline { + FontUnderlineNone, + FontUnderlineSingle, + FontUnderlineDouble, + FontUnderlineSingleAccounting, + FontUnderlineDoubleAccounting + }; + + enum HorizontalAlignment { + AlignHGeneral, + AlignLeft, + AlignHCenter, + AlignRight, + AlignHFill, + AlignHJustify, + AlignHMerge, + AlignHDistributed + }; + + enum VerticalAlignment { + AlignTop, + AlignVCenter, + AlignBottom, + AlignVJustify, + AlignVDistributed + }; + + enum BorderStyle { + BorderNone, + BorderThin, + BorderMedium, + BorderDashed, + BorderDotted, + BorderThick, + BorderDouble, + BorderHair, + BorderMediumDashed, + BorderDashDot, + BorderMediumDashDot, + BorderDashDotDot, + BorderMediumDashDotDot, + BorderSlantDashDot + }; + + enum DiagonalBorderType { + DiagonalBorderNone, + DiagonalBorderDown, + DiagonalBorderUp, + DiagnoalBorderBoth + }; + + enum FillPattern { + PatternNone, + PatternSolid, + PatternMediumGray, + PatternDarkGray, + PatternLightGray, + PatternDarkHorizontal, + PatternDarkVertical, + PatternDarkDown, + PatternDarkUp, + PatternDarkGrid, + PatternDarkTrellis, + PatternLightHorizontal, + PatternLightVertical, + PatternLightDown, + PatternLightUp, + PatternLightTrellis, + PatternGray125, + PatternGray0625, + PatternLightGrid + }; + + Format(); + Format(const Format &other); + Format &operator=(const Format &rhs); + ~Format(); + + int numberFormatIndex() const; + void setNumberFormatIndex(int format); + QString numberFormat() const; + void setNumberFormat(const QString &format); + void setNumberFormat(int id, const QString &format); + bool isDateTimeFormat() const; + + int fontSize() const; + void setFontSize(int size); + bool fontItalic() const; + void setFontItalic(bool italic); + bool fontStrikeOut() const; + void setFontStrikeOut(bool); + QColor fontColor() const; + void setFontColor(const QColor &); + bool fontBold() const; + void setFontBold(bool bold); + FontScript fontScript() const; + void setFontScript(FontScript); + FontUnderline fontUnderline() const; + void setFontUnderline(FontUnderline); + bool fontOutline() const; + void setFontOutline(bool outline); + QString fontName() const; + void setFontName(const QString &); + QFont font() const; + void setFont(const QFont &font); + + HorizontalAlignment horizontalAlignment() const; + void setHorizontalAlignment(HorizontalAlignment align); + VerticalAlignment verticalAlignment() const; + void setVerticalAlignment(VerticalAlignment align); + bool textWrap() const; + void setTextWrap(bool textWrap); + int rotation() const; + void setRotation(int rotation); + int indent() const; + void setIndent(int indent); + bool shrinkToFit() const; + void setShrinkToFit(bool shink); + + void setBorderStyle(BorderStyle style); + void setBorderColor(const QColor &color); + BorderStyle leftBorderStyle() const; + void setLeftBorderStyle(BorderStyle style); + QColor leftBorderColor() const; + void setLeftBorderColor(const QColor &color); + BorderStyle rightBorderStyle() const; + void setRightBorderStyle(BorderStyle style); + QColor rightBorderColor() const; + void setRightBorderColor(const QColor &color); + BorderStyle topBorderStyle() const; + void setTopBorderStyle(BorderStyle style); + QColor topBorderColor() const; + void setTopBorderColor(const QColor &color); + BorderStyle bottomBorderStyle() const; + void setBottomBorderStyle(BorderStyle style); + QColor bottomBorderColor() const; + void setBottomBorderColor(const QColor &color); + BorderStyle diagonalBorderStyle() const; + void setDiagonalBorderStyle(BorderStyle style); + DiagonalBorderType diagonalBorderType() const; + void setDiagonalBorderType(DiagonalBorderType style); + QColor diagonalBorderColor() const; + void setDiagonalBorderColor(const QColor &color); + + FillPattern fillPattern() const; + void setFillPattern(FillPattern pattern); + QColor patternForegroundColor() const; + void setPatternForegroundColor(const QColor &color); + QColor patternBackgroundColor() const; + void setPatternBackgroundColor(const QColor &color); + + bool locked() const; + void setLocked(bool locked); + bool hidden() const; + void setHidden(bool hidden); + + void mergeFormat(const Format &modifier); + bool isValid() const; + bool isEmpty() const; + + bool operator==(const Format &format) const; + bool operator!=(const Format &format) const; + + QVariant property(int propertyId, const QVariant &defaultValue = QVariant()) const; + void setProperty(int propertyId, + const QVariant &value, + const QVariant &clearValue = QVariant(), + bool detach = true); + void clearProperty(int propertyId); + bool hasProperty(int propertyId) const; + + bool boolProperty(int propertyId, bool defaultValue = false) const; + int intProperty(int propertyId, int defaultValue = 0) const; + double doubleProperty(int propertyId, double defaultValue = 0.0) const; + QString stringProperty(int propertyId, const QString &defaultValue = QString()) const; + QColor colorProperty(int propertyId, const QColor &defaultValue = QColor()) const; + + bool hasNumFmtData() const; + bool hasFontData() const; + bool hasFillData() const; + bool hasBorderData() const; + bool hasAlignmentData() const; + bool hasProtectionData() const; + + bool fontIndexValid() const; + int fontIndex() const; + QByteArray fontKey() const; + bool borderIndexValid() const; + QByteArray borderKey() const; + int borderIndex() const; + bool fillIndexValid() const; + QByteArray fillKey() const; + int fillIndex() const; + + QByteArray formatKey() const; + bool xfIndexValid() const; + int xfIndex() const; + bool dxfIndexValid() const; + int dxfIndex() const; + + void fixNumberFormat(int id, const QString &format); + void setFontIndex(int index); + void setBorderIndex(int index); + void setFillIndex(int index); + void setXfIndex(int index); + void setDxfIndex(int index); + +private: + friend class Styles; + friend class ::FormatTest; + friend QDebug operator<<(QDebug, const Format &f); + + int theme() const; + + QExplicitlySharedDataPointer d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const Format &f); +#endif + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_FORMAT_H diff --git a/QXlsx/header/xlsxformat_p.h b/QXlsx/header/xlsxformat_p.h new file mode 100644 index 0000000..87d2aa6 --- /dev/null +++ b/QXlsx/header/xlsxformat_p.h @@ -0,0 +1,128 @@ +// xlsxformat_p.h +#ifndef XLSXFORMAT_P_H +#define XLSXFORMAT_P_H + +#include "xlsxformat.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class FormatPrivate : public QSharedData +{ +public: + enum FormatType { + FT_Invalid = 0, + FT_NumFmt = 0x01, + FT_Font = 0x02, + FT_Alignment = 0x04, + FT_Border = 0x08, + FT_Fill = 0x10, + FT_Protection = 0x20 + }; + + enum Property { + P_STARTID, + + // numFmt + P_NumFmt_Id, + P_NumFmt_FormatCode, + + // font + P_Font_STARTID, + P_Font_Size = P_Font_STARTID, + P_Font_Italic, + P_Font_StrikeOut, + P_Font_Color, + P_Font_Bold, + P_Font_Script, + P_Font_Underline, + P_Font_Outline, + P_Font_Shadow, + P_Font_Name, + P_Font_Family, + P_Font_Charset, + P_Font_Scheme, + P_Font_Condense, + P_Font_Extend, + P_Font_ENDID, + + // border + P_Border_STARTID, + P_Border_LeftStyle = P_Border_STARTID, + P_Border_RightStyle, + P_Border_TopStyle, + P_Border_BottomStyle, + P_Border_DiagonalStyle, + P_Border_LeftColor, + P_Border_RightColor, + P_Border_TopColor, + P_Border_BottomColor, + P_Border_DiagonalColor, + P_Border_DiagonalType, + P_Border_ENDID, + + // fill + P_Fill_STARTID, + P_Fill_Pattern = P_Fill_STARTID, + P_Fill_BgColor, + P_Fill_FgColor, + P_Fill_ENDID, + + // alignment + P_Alignment_STARTID, + P_Alignment_AlignH = P_Alignment_STARTID, + P_Alignment_AlignV, + P_Alignment_Wrap, + P_Alignment_Rotation, + P_Alignment_Indent, + P_Alignment_ShinkToFit, + P_Alignment_ENDID, + + // protection + P_Protection_Locked, + P_Protection_Hidden, + + P_ENDID + }; + + FormatPrivate(); + FormatPrivate(const FormatPrivate &other); + ~FormatPrivate(); + + bool dirty; // The key re-generation is need. + QByteArray formatKey; + + bool font_dirty; + bool font_index_valid; + QByteArray font_key; + int font_index; + + bool fill_dirty; + bool fill_index_valid; + QByteArray fill_key; + int fill_index; + + bool border_dirty; + bool border_index_valid; + QByteArray border_key; + int border_index; + + int xf_index; + bool xf_indexValid; + + bool is_dxf_fomat; + int dxf_index; + bool dxf_indexValid; + + int theme; + + QMap properties; +}; + +QT_END_NAMESPACE_XLSX + +#endif diff --git a/QXlsx/header/xlsxglobal.h b/QXlsx/header/xlsxglobal.h new file mode 100644 index 0000000..ef92e02 --- /dev/null +++ b/QXlsx/header/xlsxglobal.h @@ -0,0 +1,33 @@ +// xlsxglobal.h + +#ifndef XLSXGLOBAL_H +#define XLSXGLOBAL_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(QXlsx_SHAREDLIB) +# if defined(QXlsx_EXPORTS) +# define QXLSX_EXPORT Q_DECL_EXPORT +# else +# define QXLSX_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXLSX_EXPORT +#endif + +#define QT_BEGIN_NAMESPACE_XLSX namespace QXlsx { +#define QT_END_NAMESPACE_XLSX } + +#define QXLSX_USE_NAMESPACE using namespace QXlsx; + +#endif // XLSXGLOBAL_H diff --git a/QXlsx/header/xlsxmediafile_p.h b/QXlsx/header/xlsxmediafile_p.h new file mode 100644 index 0000000..30b9adb --- /dev/null +++ b/QXlsx/header/xlsxmediafile_p.h @@ -0,0 +1,46 @@ +// xlsxmediafile_p.h + +#ifndef QXLSX_XLSXMEDIAFILE_H +#define QXLSX_XLSXMEDIAFILE_H + +#include "xlsxglobal.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class MediaFile +{ +public: + MediaFile(const QString &fileName); + MediaFile(const QByteArray &bytes, const QString &suffix, const QString &mimeType = QString()); + +public: + void set(const QByteArray &bytes, const QString &suffix, const QString &mimeType = QString()); + QString suffix() const; + QString mimeType() const; + QByteArray contents() const; + + bool isIndexValid() const; + int index() const; + void setIndex(int idx); + QByteArray hashKey() const; + + void setFileName(const QString &name); + QString fileName() const; + +protected: + QString m_fileName; + QByteArray m_contents; + QString m_suffix; + QString m_mimeType; + + int m_index; + bool m_indexValid; + QByteArray m_hashKey; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXMEDIAFILE_H diff --git a/QXlsx/header/xlsxnumformatparser_p.h b/QXlsx/header/xlsxnumformatparser_p.h new file mode 100644 index 0000000..dfa81e9 --- /dev/null +++ b/QXlsx/header/xlsxnumformatparser_p.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef QXLSX_NUMFORMATPARSER_H +#define QXLSX_NUMFORMATPARSER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "xlsxglobal.h" + +QT_BEGIN_NAMESPACE_XLSX + +class NumFormatParser +{ +public: + static bool isDateTime(const QString &formatCode); +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_NUMFORMATPARSER_H diff --git a/QXlsx/header/xlsxrelationships_p.h b/QXlsx/header/xlsxrelationships_p.h new file mode 100644 index 0000000..5595687 --- /dev/null +++ b/QXlsx/header/xlsxrelationships_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXRELATIONSHIPS_H +#define XLSXRELATIONSHIPS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "xlsxglobal.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +struct XlsxRelationship { + QString id; + QString type; + QString target; + QString targetMode; +}; + +class Relationships +{ +public: + Relationships(); + + QList documentRelationships(const QString &relativeType) const; + QList packageRelationships(const QString &relativeType) const; + QList msPackageRelationships(const QString &relativeType) const; + QList worksheetRelationships(const QString &relativeType) const; + + void addDocumentRelationship(const QString &relativeType, const QString &target); + void addPackageRelationship(const QString &relativeType, const QString &target); + void addMsPackageRelationship(const QString &relativeType, const QString &target); + void addWorksheetRelationship(const QString &relativeType, + const QString &target, + const QString &targetMode = QString()); + + void saveToXmlFile(QIODevice *device) const; + QByteArray saveToXmlData() const; + bool loadFromXmlFile(QIODevice *device); + bool loadFromXmlData(const QByteArray &data); + XlsxRelationship getRelationshipById(const QString &id) const; + + void clear(); + int count() const; + bool isEmpty() const; + +private: + QList relationships(const QString &type) const; + void addRelationship(const QString &type, + const QString &target, + const QString &targetMode = QString()); + + QList m_relationships; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXRELATIONSHIPS_H diff --git a/QXlsx/header/xlsxrichstring.h b/QXlsx/header/xlsxrichstring.h new file mode 100644 index 0000000..0eda23c --- /dev/null +++ b/QXlsx/header/xlsxrichstring.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXRICHSTRING_H +#define XLSXRICHSTRING_H + +#include "xlsxformat.h" +#include "xlsxglobal.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX +class RichStringPrivate; +class RichString; +// qHash is a friend, but we can't use default arguments for friends (§8.3.6.4) +uint qHash(const RichString &rs, uint seed = 0) Q_DECL_NOTHROW; + +class QXLSX_EXPORT RichString +{ +public: + RichString(); + explicit RichString(const QString &text); + RichString(const RichString &other); + ~RichString(); + + bool isRichString() const; + bool isNull() const; + bool isEmtpy() const; + QString toPlainString() const; + QString toHtml() const; + void setHtml(const QString &text); + + int fragmentCount() const; + void addFragment(const QString &text, const Format &format); + QString fragmentText(int index) const; + Format fragmentFormat(int index) const; + + operator QVariant() const; + + RichString &operator=(const RichString &other); + +private: + friend uint qHash(const RichString &rs, uint seed) Q_DECL_NOTHROW; + friend bool operator==(const RichString &rs1, const RichString &rs2); + friend bool operator!=(const RichString &rs1, const RichString &rs2); + friend bool operator<(const RichString &rs1, const RichString &rs2); + friend QDebug operator<<(QDebug dbg, const RichString &rs); + + QSharedDataPointer d; +}; + +bool operator==(const RichString &rs1, const RichString &rs2); +bool operator!=(const RichString &rs1, const RichString &rs2); +bool operator<(const RichString &rs1, const RichString &rs2); +bool operator==(const RichString &rs1, const QString &rs2); +bool operator==(const QString &rs1, const RichString &rs2); +bool operator!=(const RichString &rs1, const QString &rs2); +bool operator!=(const QString &rs1, const RichString &rs2); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const RichString &rs); +#endif + +QT_END_NAMESPACE_XLSX + +Q_DECLARE_METATYPE(QXlsx::RichString) + +#endif // XLSXRICHSTRING_H diff --git a/QXlsx/header/xlsxrichstring_p.h b/QXlsx/header/xlsxrichstring_p.h new file mode 100644 index 0000000..8b00fe3 --- /dev/null +++ b/QXlsx/header/xlsxrichstring_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXRICHSTRING_P_H +#define XLSXRICHSTRING_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "xlsxrichstring.h" + +QT_BEGIN_NAMESPACE_XLSX + +class RichStringPrivate : public QSharedData +{ +public: + RichStringPrivate(); + RichStringPrivate(const RichStringPrivate &other); + ~RichStringPrivate(); + + QByteArray idKey() const; + + QStringList fragmentTexts; + QList fragmentFormats; + QByteArray _idKey; + bool _dirty; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXRICHSTRING_P_H diff --git a/QXlsx/header/xlsxsharedstrings_p.h b/QXlsx/header/xlsxsharedstrings_p.h new file mode 100644 index 0000000..75d563d --- /dev/null +++ b/QXlsx/header/xlsxsharedstrings_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXSHAREDSTRINGS_H +#define XLSXSHAREDSTRINGS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "xlsxabstractooxmlfile.h" +#include "xlsxglobal.h" +#include "xlsxrichstring.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class XlsxSharedStringInfo +{ +public: + XlsxSharedStringInfo(int index = 0, int count = 1) + : index(index) + , count(count) + { + } + + int index; + int count; +}; + +class SharedStrings : public AbstractOOXmlFile +{ +public: + SharedStrings(CreateFlag flag); + int count() const; + bool isEmpty() const; + + int addSharedString(const QString &string); + int addSharedString(const RichString &string); + void removeSharedString(const QString &string); + void removeSharedString(const RichString &string); + void incRefByStringIndex(int idx); + + int getSharedStringIndex(const QString &string) const; + int getSharedStringIndex(const RichString &string) const; + RichString getSharedString(int index) const; + QList getSharedStrings() const; + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + +private: + void readString(QXmlStreamReader &reader); // + void readRichStringPart(QXmlStreamReader &reader, RichString &rich); // + void readPlainStringPart(QXmlStreamReader &reader, RichString &rich); // + Format readRichStringPart_rPr(QXmlStreamReader &reader); + void writeRichStringPart_rPr(QXmlStreamWriter &writer, const Format &format) const; + + QHash m_stringTable; // for fast lookup + QList m_stringList; + int m_stringCount; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXSHAREDSTRINGS_H diff --git a/QXlsx/header/xlsxsimpleooxmlfile_p.h b/QXlsx/header/xlsxsimpleooxmlfile_p.h new file mode 100644 index 0000000..cb8c26e --- /dev/null +++ b/QXlsx/header/xlsxsimpleooxmlfile_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXSIMPLEOOXMLFILE_H +#define XLSXSIMPLEOOXMLFILE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "xlsxabstractooxmlfile.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class SimpleOOXmlFile : public AbstractOOXmlFile +{ +public: + SimpleOOXmlFile(CreateFlag flag); + + void saveToXmlFile(QIODevice *device) const override; + QByteArray saveToXmlData() const override; + bool loadFromXmlData(const QByteArray &data) override; + bool loadFromXmlFile(QIODevice *device) override; + + QByteArray xmlData; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXSIMPLEOOXMLFILE_H diff --git a/QXlsx/header/xlsxstyles_p.h b/QXlsx/header/xlsxstyles_p.h new file mode 100644 index 0000000..77c68ab --- /dev/null +++ b/QXlsx/header/xlsxstyles_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef XLSXSTYLES_H +#define XLSXSTYLES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// class StylesTest; + +#include "xlsxabstractooxmlfile.h" +#include "xlsxformat.h" +#include "xlsxglobal.h" + +QT_BEGIN_NAMESPACE_XLSX + +class Format; +class XlsxColor; + +struct XlsxFormatNumberData { + XlsxFormatNumberData() + : formatIndex(0) + { + } + + int formatIndex; + QString formatString; +}; + +class Styles : public AbstractOOXmlFile +{ +public: + Styles(CreateFlag flag); + ~Styles(); + void addXfFormat(const Format &format, bool force = false); + Format xfFormat(int idx) const; + void addDxfFormat(const Format &format, bool force = false); + Format dxfFormat(int idx) const; + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + + QColor getColorByIndex(int idx); + +private: + friend class Format; + // friend class ::StylesTest; + + void fixNumFmt(const Format &format); + + void writeNumFmts(QXmlStreamWriter &writer) const; + void writeFonts(QXmlStreamWriter &writer) const; + void writeFont(QXmlStreamWriter &writer, const Format &font, bool isDxf = false) const; + void writeFills(QXmlStreamWriter &writer) const; + void writeFill(QXmlStreamWriter &writer, const Format &fill, bool isDxf = false) const; + void writeBorders(QXmlStreamWriter &writer) const; + void writeBorder(QXmlStreamWriter &writer, const Format &border, bool isDxf = false) const; + void writeSubBorder(QXmlStreamWriter &writer, + const QString &type, + int style, + const XlsxColor &color) const; + void writeCellXfs(QXmlStreamWriter &writer) const; + void writeDxfs(QXmlStreamWriter &writer) const; + void writeDxf(QXmlStreamWriter &writer, const Format &format) const; + void writeColors(QXmlStreamWriter &writer) const; + + bool readNumFmts(QXmlStreamReader &reader); + bool readFonts(QXmlStreamReader &reader); + bool readFont(QXmlStreamReader &reader, Format &format); + bool readFills(QXmlStreamReader &reader); + bool readFill(QXmlStreamReader &reader, Format &format); + bool readBorders(QXmlStreamReader &reader); + bool readBorder(QXmlStreamReader &reader, Format &format); + bool readSubBorder(QXmlStreamReader &reader, + const QString &name, + Format::BorderStyle &style, + XlsxColor &color); + bool readCellXfs(QXmlStreamReader &reader); + bool readDxfs(QXmlStreamReader &reader); + bool readDxf(QXmlStreamReader &reader); + bool readColors(QXmlStreamReader &reader); + bool readIndexedColors(QXmlStreamReader &reader); + + bool readCellStyleXfs(QXmlStreamReader &reader); + + QHash m_builtinNumFmtsHash; + QMap> m_customNumFmtIdMap; + QHash> m_customNumFmtsHash; + int m_nextCustomNumFmtId; + QList m_fontsList; + QList m_fillsList; + QList m_bordersList; + QHash m_fontsHash; + QHash m_fillsHash; + QHash m_bordersHash; + + QVector m_indexedColors; + bool m_isIndexedColorsDefault; + + QList m_xf_formatsList; + QHash m_xf_formatsHash; + + QList m_dxf_formatsList; + QHash m_dxf_formatsHash; + + bool m_emptyFormatAdded; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXSTYLES_H diff --git a/QXlsx/header/xlsxtheme_p.h b/QXlsx/header/xlsxtheme_p.h new file mode 100644 index 0000000..94d16e8 --- /dev/null +++ b/QXlsx/header/xlsxtheme_p.h @@ -0,0 +1,29 @@ +// xlsxtheme_p.h + +#ifndef XLSXTHEME_H +#define XLSXTHEME_H + +#include "xlsxabstractooxmlfile.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class Theme : public AbstractOOXmlFile +{ +public: + Theme(CreateFlag flag); + + void saveToXmlFile(QIODevice *device) const override; + QByteArray saveToXmlData() const override; + bool loadFromXmlData(const QByteArray &data) override; + bool loadFromXmlFile(QIODevice *device) override; + + QByteArray xmlData; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXTHEME_H diff --git a/QXlsx/header/xlsxutility_p.h b/QXlsx/header/xlsxutility_p.h new file mode 100644 index 0000000..a1aebd7 --- /dev/null +++ b/QXlsx/header/xlsxutility_p.h @@ -0,0 +1,44 @@ +// xlsxutility_p.h + +#ifndef XLSXUTILITY_H +#define XLSXUTILITY_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class CellReference; + +bool parseXsdBoolean(const QString &value, bool defaultValue = false); +QString xsdBoolean(bool value); + +QStringList splitPath(const QString &path); +QString getRelFilePath(const QString &filePath); + +double datetimeToNumber(const QDateTime &dt, bool is1904 = false); +QVariant datetimeFromNumber(double num, bool is1904 = false); +double timeToNumber(const QTime &t); + +QString createSafeSheetName(const QString &nameProposal); +QString escapeSheetName(const QString &sheetName); +QString unescapeSheetName(const QString &sheetName); + +bool isSpaceReserveNeeded(const QString &string); + +QString convertSharedFormula(const QString &rootFormula, + const CellReference &rootCell, + const CellReference &cell); + +QT_END_NAMESPACE_XLSX +#endif // XLSXUTILITY_H diff --git a/QXlsx/header/xlsxworkbook.h b/QXlsx/header/xlsxworkbook.h new file mode 100644 index 0000000..2e3e796 --- /dev/null +++ b/QXlsx/header/xlsxworkbook.h @@ -0,0 +1,103 @@ +// xlsxworkbook.h + +#ifndef XLSXWORKBOOK_H +#define XLSXWORKBOOK_H + +#include "xlsxabstractooxmlfile.h" +#include "xlsxabstractsheet.h" +#include "xlsxglobal.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class SharedStrings; +class Styles; +class Drawing; +class Document; +class Theme; +class Relationships; +class DocumentPrivate; +class MediaFile; +class Chart; +class Chartsheet; +class Worksheet; +class WorkbookPrivate; + +class QXLSX_EXPORT Workbook : public AbstractOOXmlFile +{ + Q_DECLARE_PRIVATE(Workbook) +public: + ~Workbook(); + + int sheetCount() const; + AbstractSheet *sheet(int index) const; + + AbstractSheet *addSheet(const QString &name = QString(), + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet); + AbstractSheet *insertSheet(int index, + const QString &name = QString(), + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet); + bool renameSheet(int index, const QString &name); + bool deleteSheet(int index); + bool copySheet(int index, const QString &newName = QString()); + bool moveSheet(int srcIndex, int distIndex); + + AbstractSheet *activeSheet() const; + bool setActiveSheet(int index); + + // void addChart(); + bool defineName(const QString &name, + const QString &formula, + const QString &comment = QString(), + const QString &scope = QString()); + bool isDate1904() const; + void setDate1904(bool date1904); + bool isStringsToNumbersEnabled() const; + void setStringsToNumbersEnabled(bool enable = true); + bool isStringsToHyperlinksEnabled() const; + void setStringsToHyperlinksEnabled(bool enable = true); + bool isHtmlToRichStringEnabled() const; + void setHtmlToRichStringEnabled(bool enable = true); + QString defaultDateFormat() const; + void setDefaultDateFormat(const QString &format); + + // internal used member + void addMediaFile(std::shared_ptr media, bool force = false); + QList> mediaFiles() const; + void addChartFile(QSharedPointer chartFile); + QList> chartFiles() const; + +private: + friend class Worksheet; + friend class Chartsheet; + friend class WorksheetPrivate; + friend class Document; + friend class DocumentPrivate; + + Workbook(Workbook::CreateFlag flag); + + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; + + SharedStrings *sharedStrings() const; + Styles *styles(); + Theme *theme(); + QList images(); + QList drawings(); + QList> getSheetsByTypes(AbstractSheet::SheetType type) const; + QStringList worksheetNames() const; + AbstractSheet *addSheet(const QString &name, + int sheetId, + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet); +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXWORKBOOK_H diff --git a/QXlsx/header/xlsxworkbook_p.h b/QXlsx/header/xlsxworkbook_p.h new file mode 100644 index 0000000..8684701 --- /dev/null +++ b/QXlsx/header/xlsxworkbook_p.h @@ -0,0 +1,79 @@ +// xlsxworkbook_p.h + +#ifndef XLSXWORKBOOK_P_H +#define XLSXWORKBOOK_P_H + +#include "xlsxabstractooxmlfile_p.h" +#include "xlsxrelationships_p.h" +#include "xlsxsimpleooxmlfile_p.h" +#include "xlsxtheme_p.h" +#include "xlsxworkbook.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +struct XlsxDefineNameData { + XlsxDefineNameData() + : sheetId(-1) + { + } + XlsxDefineNameData(const QString &name, + const QString &formula, + const QString &comment, + int sheetId = -1) + : name(name) + , formula(formula) + , comment(comment) + , sheetId(sheetId) + { + } + QString name; + QString formula; + QString comment; + // using internal sheetId, instead of the localSheetId(order in the workbook) + int sheetId; +}; + +class WorkbookPrivate : public AbstractOOXmlFilePrivate +{ + Q_DECLARE_PUBLIC(Workbook) +public: + WorkbookPrivate(Workbook *q, Workbook::CreateFlag flag); + + QSharedPointer sharedStrings; + QList> sheets; + QList> externalLinks; + QStringList sheetNames; + QSharedPointer styles; + QSharedPointer theme; + QList> mediaFiles; + QList> chartFiles; + QList definedNamesList; + + bool strings_to_numbers_enabled; + bool strings_to_hyperlinks_enabled; + bool html_to_richstring_enabled; + bool date1904; + QString defaultDateFormat; + + int x_window; + int y_window; + int window_width; + int window_height; + + int activesheetIndex; + int firstsheet; + int table_count; + + // Used to generate new sheet name and id + int last_worksheet_index; + int last_chartsheet_index; + int last_sheet_id; +}; + +QT_END_NAMESPACE_XLSX + +#endif // XLSXWORKBOOK_P_H diff --git a/QXlsx/header/xlsxworksheet.h b/QXlsx/header/xlsxworksheet.h new file mode 100644 index 0000000..7c9232a --- /dev/null +++ b/QXlsx/header/xlsxworksheet.h @@ -0,0 +1,198 @@ +// xlsxworksheet.h + +#ifndef XLSXWORKSHEET_H +#define XLSXWORKSHEET_H + +#include "xlsxabstractsheet.h" +#include "xlsxcell.h" +#include "xlsxcelllocation.h" +#include "xlsxcellrange.h" +#include "xlsxcellreference.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WorksheetTest; + +QT_BEGIN_NAMESPACE_XLSX + +class DocumentPrivate; +class Workbook; +class Format; +class Drawing; +class DataValidation; +class ConditionalFormatting; +class CellRange; +class RichString; +class Relationships; +class Chart; + +class WorksheetPrivate; +class QXLSX_EXPORT Worksheet : public AbstractSheet +{ + Q_DECLARE_PRIVATE(Worksheet) + +private: + friend class DocumentPrivate; + friend class Workbook; + friend class ::WorksheetTest; + Worksheet(const QString &sheetName, int sheetId, Workbook *book, CreateFlag flag); + Worksheet *copy(const QString &distName, int distId) const override; + +public: + ~Worksheet(); + +public: + bool write(const CellReference &row_column, + const QVariant &value, + const Format &format = Format()); + bool write(int row, int column, const QVariant &value, const Format &format = Format()); + + QVariant read(const CellReference &row_column) const; + QVariant read(int row, int column) const; + + bool writeString(const CellReference &row_column, + const QString &value, + const Format &format = Format()); + bool writeString(int row, int column, const QString &value, const Format &format = Format()); + bool writeString(const CellReference &row_column, + const RichString &value, + const Format &format = Format()); + bool writeString(int row, int column, const RichString &value, const Format &format = Format()); + + bool writeInlineString(const CellReference &row_column, + const QString &value, + const Format &format = Format()); + bool writeInlineString(int row, + int column, + const QString &value, + const Format &format = Format()); + + bool writeNumeric(const CellReference &row_column, + double value, + const Format &format = Format()); + bool writeNumeric(int row, int column, double value, const Format &format = Format()); + + bool writeFormula(const CellReference &row_column, + const CellFormula &formula, + const Format &format = Format(), + double result = 0); + bool writeFormula(int row, + int column, + const CellFormula &formula, + const Format &format = Format(), + double result = 0); + + bool writeBlank(const CellReference &row_column, const Format &format = Format()); + bool writeBlank(int row, int column, const Format &format = Format()); + + bool writeBool(const CellReference &row_column, bool value, const Format &format = Format()); + bool writeBool(int row, int column, bool value, const Format &format = Format()); + + bool writeDateTime(const CellReference &row_column, + const QDateTime &dt, + const Format &format = Format()); + bool writeDateTime(int row, int column, const QDateTime &dt, const Format &format = Format()); + + // dev67 + bool writeDate(const CellReference &row_column, + const QDate &dt, + const Format &format = Format()); + bool writeDate(int row, int column, const QDate &dt, const Format &format = Format()); + + bool + writeTime(const CellReference &row_column, const QTime &t, const Format &format = Format()); + bool writeTime(int row, int column, const QTime &t, const Format &format = Format()); + + bool writeHyperlink(const CellReference &row_column, + const QUrl &url, + const Format &format = Format(), + const QString &display = QString(), + const QString &tip = QString()); + bool writeHyperlink(int row, + int column, + const QUrl &url, + const Format &format = Format(), + const QString &display = QString(), + const QString &tip = QString()); + + bool addDataValidation(const DataValidation &validation); + bool addConditionalFormatting(const ConditionalFormatting &cf); + + Cell *cellAt(const CellReference &row_column) const; + Cell *cellAt(int row, int column) const; + + int insertImage(int row, int column, const QImage &image); + bool getImage(int imageIndex, QImage &img); + bool getImage(int row, int column, QImage &img); + uint getImageCount(); + + Chart *insertChart(int row, int column, const QSize &size); + + bool mergeCells(const CellRange &range, const Format &format = Format()); + bool unmergeCells(const CellRange &range); + QList mergedCells() const; + + bool setColumnWidth(const CellRange &range, double width); + bool setColumnFormat(const CellRange &range, const Format &format); + bool setColumnHidden(const CellRange &range, bool hidden); + bool setColumnWidth(int colFirst, int colLast, double width); + bool setColumnFormat(int colFirst, int colLast, const Format &format); + bool setColumnHidden(int colFirst, int colLast, bool hidden); + + double columnWidth(int column); + Format columnFormat(int column); + bool isColumnHidden(int column); + + bool setRowHeight(int rowFirst, int rowLast, double height); + bool setRowFormat(int rowFirst, int rowLast, const Format &format); + bool setRowHidden(int rowFirst, int rowLast, bool hidden); + + double rowHeight(int row); + Format rowFormat(int row); + bool isRowHidden(int row); + + bool groupRows(int rowFirst, int rowLast, bool collapsed = true); + bool groupColumns(int colFirst, int colLast, bool collapsed = true); + bool groupColumns(const CellRange &range, bool collapsed = true); + CellRange dimension() const; + + bool isWindowProtected() const; + void setWindowProtected(bool protect); + bool isFormulasVisible() const; + void setFormulasVisible(bool visible); + bool isGridLinesVisible() const; + void setGridLinesVisible(bool visible); + bool isRowColumnHeadersVisible() const; + void setRowColumnHeadersVisible(bool visible); + bool isZerosVisible() const; + void setZerosVisible(bool visible); + bool isRightToLeft() const; + void setRightToLeft(bool enable); + bool isSelected() const; + void setSelected(bool select); + bool isRulerVisible() const; + void setRulerVisible(bool visible); + bool isOutlineSymbolsVisible() const; + void setOutlineSymbolsVisible(bool visible); + bool isWhiteSpaceVisible() const; + void setWhiteSpaceVisible(bool visible); + bool setStartPage(int spagen); // add by liufeijin20181028 + + QVector getFullCells(int *maxRow, int *maxCol); + +private: + void saveToXmlFile(QIODevice *device) const override; + bool loadFromXmlFile(QIODevice *device) override; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXWORKSHEET_H diff --git a/QXlsx/header/xlsxworksheet_p.h b/QXlsx/header/xlsxworksheet_p.h new file mode 100644 index 0000000..30f0846 --- /dev/null +++ b/QXlsx/header/xlsxworksheet_p.h @@ -0,0 +1,249 @@ +// xlsxworksheet_p.h + +#ifndef XLSXWORKSHEET_P_H +#define XLSXWORKSHEET_P_H + +#include "xlsxabstractsheet_p.h" +#include "xlsxcell.h" +#include "xlsxcellformula.h" +#include "xlsxconditionalformatting.h" +#include "xlsxdatavalidation.h" +#include "xlsxworksheet.h" + +#include +#include +#include +#include +#include +#include +#include + +class QXmlStreamWriter; +class QXmlStreamReader; + +QT_BEGIN_NAMESPACE_XLSX + +const int XLSX_ROW_MAX = 1048576; +const int XLSX_COLUMN_MAX = 16384; +const int XLSX_STRING_MAX = 32767; + +class SharedStrings; + +struct XlsxHyperlinkData { + enum LinkType { External, Internal }; + + XlsxHyperlinkData(LinkType linkType = External, + const QString &target = QString(), + const QString &location = QString(), + const QString &display = QString(), + const QString &tip = QString()) + : linkType(linkType) + , target(target) + , location(location) + , display(display) + , tooltip(tip) + { + } + + LinkType linkType; + QString target; // For External link + QString location; + QString display; + QString tooltip; +}; + +// ECMA-376 Part1 18.3.1.81 +struct XlsxSheetFormatProps { + XlsxSheetFormatProps( + int baseColWidth = 8, + bool customHeight = false, + double defaultColWidth = + 8.430f, // https://learn.microsoft.com/en-us/office/troubleshoot/excel/determine-column-widths + double defaultRowHeight = 15, + quint8 outlineLevelCol = 0, + quint8 outlineLevelRow = 0, + bool thickBottom = false, + bool thickTop = false, + bool zeroHeight = false) + : baseColWidth(baseColWidth) + , customHeight(customHeight) + , defaultColWidth(defaultColWidth) + , defaultRowHeight(defaultRowHeight) + , outlineLevelCol(outlineLevelCol) + , outlineLevelRow(outlineLevelRow) + , thickBottom(thickBottom) + , thickTop(thickTop) + , zeroHeight(zeroHeight) + { + } + + int baseColWidth; + bool customHeight; + double defaultColWidth; + double defaultRowHeight; + quint8 outlineLevelCol; + quint8 outlineLevelRow; + bool thickBottom; + bool thickTop; + bool zeroHeight; +}; + +struct XlsxRowInfo { + XlsxRowInfo(double height = 0, const Format &format = Format(), bool hidden = false) + : customHeight(false) + , height(height) + , format(format) + , hidden(hidden) + , outlineLevel(0) + , collapsed(false) + { + } + + bool customHeight; + double height; + Format format; + bool hidden; + int outlineLevel; + bool collapsed; +}; + +struct XlsxColumnInfo { + XlsxColumnInfo(int firstColumn, // = 0, + int lastColumn, // = 1, + bool isSetWidth, + double width = 0, + const Format &format = Format(), + bool hidden = false) + : width(width) + , format(format) + , firstColumn(firstColumn) + , lastColumn(lastColumn) + , outlineLevel(0) + , isSetWidth(isSetWidth) + , customWidth(false) + , hidden(hidden) + , collapsed(false) + { + } + + double width; + Format format; + int firstColumn; + int lastColumn; + int outlineLevel; + bool isSetWidth; + bool customWidth; + bool hidden; + bool collapsed; +}; + +class WorksheetPrivate : public AbstractSheetPrivate +{ + Q_DECLARE_PUBLIC(Worksheet) + +public: + WorksheetPrivate(Worksheet *p, Worksheet::CreateFlag flag); + ~WorksheetPrivate(); + +public: + int checkDimensions(int row, int col, bool ignore_row = false, bool ignore_col = false); + Format cellFormat(int row, int col) const; + QString generateDimensionString() const; + void calculateSpans() const; + void splitColsInfo(int colFirst, int colLast); + void validateDimension(); + + void saveXmlSheetData(QXmlStreamWriter &writer) const; + void saveXmlCellData(QXmlStreamWriter &writer, + int row, + int col, + std::shared_ptr cell) const; + void saveXmlMergeCells(QXmlStreamWriter &writer) const; + void saveXmlHyperlinks(QXmlStreamWriter &writer) const; + void saveXmlDrawings(QXmlStreamWriter &writer) const; + void saveXmlDataValidations(QXmlStreamWriter &writer) const; + + int rowPixelsSize(int row) const; + int colPixelsSize(int col) const; + + void loadXmlSheetData(QXmlStreamReader &reader); + void loadXmlColumnsInfo(QXmlStreamReader &reader); + void loadXmlMergeCells(QXmlStreamReader &reader); + void loadXmlDataValidations(QXmlStreamReader &reader); + void loadXmlSheetFormatProps(QXmlStreamReader &reader); + void loadXmlSheetViews(QXmlStreamReader &reader); + void loadXmlHyperlinks(QXmlStreamReader &reader); + + QList> getRowInfoList(int rowFirst, int rowLast); + QList> getColumnInfoList(int colFirst, int colLast); + QList getColumnIndexes(int colFirst, int colLast); + bool isColumnRangeValid(int colFirst, int colLast); + + SharedStrings *sharedStrings() const; + +public: + QMap>> cellTable; + + QMap> comments; + QMap>> urlTable; + QList merges; + QMap> rowsInfo; + QMap> colsInfo; + QMap> colsInfoHelper; + + QList dataValidationsList; + QList conditionalFormattingList; + + QMap sharedFormulaMap; // shared formula map + + CellRange dimension; + + mutable QMap row_spans; + QMap row_sizes; + QMap col_sizes; + + // pagesetup and print settings add by liufeijin 20181028, liufeijin + QString PpaperSize; + QString Pscale; + QString PfirstPageNumber; + QString Porientation; + QString PuseFirstPageNumber; + QString PhorizontalDpi; + QString PverticalDpi; + QString Prid; + QString Pcopies; + + // pageMargins, liufeijin + QString PMheader; + QString PMfooter; + QString PMtop; + QString PMbotton; + QString PMleft; + QString PMright; + + // header footer, liufeijin + QString MoodFooter; + QString ModdHeader; + QString MoodalignWithMargins; // add align 20190619 + + XlsxSheetFormatProps sheetFormatProps; + + bool windowProtection; + bool showFormulas; + bool showGridLines; + bool showRowColHeaders; + bool showZeros; + bool rightToLeft; + bool tabSelected; + bool showRuler; + bool showOutlineSymbols; + bool showWhiteSpace; + + QRegularExpression urlPattern; + +private: + static double calculateColWidth(int characters); +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXWORKSHEET_P_H diff --git a/QXlsx/header/xlsxzipreader_p.h b/QXlsx/header/xlsxzipreader_p.h new file mode 100644 index 0000000..e2c965f --- /dev/null +++ b/QXlsx/header/xlsxzipreader_p.h @@ -0,0 +1,36 @@ +// xlsxzipreader_p.h + +#ifndef QXLSX_XLSXZIPREADER_P_H +#define QXLSX_XLSXZIPREADER_P_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include + +class QZipReader; + +QT_BEGIN_NAMESPACE_XLSX + +class ZipReader +{ +public: + explicit ZipReader(const QString &fileName); + explicit ZipReader(QIODevice *device); + ~ZipReader(); + bool exists() const; + QStringList filePaths() const; + QByteArray fileData(const QString &fileName) const; + +private: + Q_DISABLE_COPY(ZipReader) + void init(); + QScopedPointer m_reader; + QStringList m_filePaths; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXZIPREADER_P_H diff --git a/QXlsx/header/xlsxzipwriter_p.h b/QXlsx/header/xlsxzipwriter_p.h new file mode 100644 index 0000000..799e77c --- /dev/null +++ b/QXlsx/header/xlsxzipwriter_p.h @@ -0,0 +1,34 @@ +// xlsxzipwriter_p.h + +#ifndef QXLSX_ZIPWRITER_H +#define QXLSX_ZIPWRITER_H + +#include "xlsxglobal.h" + +#include +#include +#include + +class QZipWriter; + +QT_BEGIN_NAMESPACE_XLSX + +class ZipWriter +{ +public: + explicit ZipWriter(const QString &filePath); + explicit ZipWriter(QIODevice *device); + ~ZipWriter(); + + void addFile(const QString &filePath, QIODevice *device); + void addFile(const QString &filePath, const QByteArray &data); + bool error() const; + void close(); + +private: + QZipWriter *m_writer; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_ZIPWRITER_H diff --git a/QXlsx/source/xlsxabstractooxmlfile.cpp b/QXlsx/source/xlsxabstractooxmlfile.cpp new file mode 100644 index 0000000..4702592 --- /dev/null +++ b/QXlsx/source/xlsxabstractooxmlfile.cpp @@ -0,0 +1,98 @@ +// xlsxabstractooxmlfile.cpp + +#include "xlsxabstractooxmlfile.h" + +#include "xlsxabstractooxmlfile_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +AbstractOOXmlFilePrivate::AbstractOOXmlFilePrivate( + AbstractOOXmlFile *q, + AbstractOOXmlFile::CreateFlag flag = AbstractOOXmlFile::F_NewFromScratch) + : relationships(new Relationships) + , flag(flag) + , q_ptr(q) +{ +} + +AbstractOOXmlFilePrivate::~AbstractOOXmlFilePrivate() +{ + if (relationships) { + delete relationships; + } +} + +/*! + * \internal + * + * \class AbstractOOXmlFile + * + * Base class of all the ooxml part file. + */ + +AbstractOOXmlFile::AbstractOOXmlFile(CreateFlag flag) + : d_ptr(new AbstractOOXmlFilePrivate(this, flag)) +{ +} + +AbstractOOXmlFile::AbstractOOXmlFile(AbstractOOXmlFilePrivate *d) + : d_ptr(d) +{ +} + +AbstractOOXmlFile::~AbstractOOXmlFile() +{ + delete d_ptr; +} + +QByteArray AbstractOOXmlFile::saveToXmlData() const +{ + QByteArray data; + QBuffer buffer(&data); + buffer.open(QIODevice::WriteOnly); + saveToXmlFile(&buffer); + + return data; +} + +bool AbstractOOXmlFile::loadFromXmlData(const QByteArray &data) +{ + QBuffer buffer; + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + + return loadFromXmlFile(&buffer); +} + +/*! + * \internal + */ +void AbstractOOXmlFile::setFilePath(const QString path) +{ + Q_D(AbstractOOXmlFile); + d->filePathInPackage = path; +} + +/*! + * \internal + */ +QString AbstractOOXmlFile::filePath() const +{ + Q_D(const AbstractOOXmlFile); + return d->filePathInPackage; +} + +/*! + * \internal + */ +Relationships *AbstractOOXmlFile::relationships() const +{ + Q_D(const AbstractOOXmlFile); + return d->relationships; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxabstractsheet.cpp b/QXlsx/source/xlsxabstractsheet.cpp new file mode 100644 index 0000000..bf243bf --- /dev/null +++ b/QXlsx/source/xlsxabstractsheet.cpp @@ -0,0 +1,189 @@ +// xlsxabstractsheet.cpp + +#include "xlsxabstractsheet.h" + +#include "xlsxabstractsheet_p.h" +#include "xlsxworkbook.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +AbstractSheetPrivate::AbstractSheetPrivate(AbstractSheet *p, AbstractSheet::CreateFlag flag) + : AbstractOOXmlFilePrivate(p, flag) +{ + type = AbstractSheet::ST_WorkSheet; + sheetState = AbstractSheet::SS_Visible; +} + +AbstractSheetPrivate::~AbstractSheetPrivate() +{ +} + +/*! + \class AbstractSheet + \inmodule QtXlsx + \brief Base class for worksheet, chartsheet, etc. +*/ + +/*! + \enum AbstractSheet::SheetType + + \value ST_WorkSheet + \value ST_ChartSheet + \omitvalue ST_DialogSheet + \omitvalue ST_MacroSheet +*/ + +/*! + \enum AbstractSheet::SheetState + + \value SS_Visible + \value SS_Hidden + \value SS_VeryHidden User can't make a veryHidden sheet visible in normal way. +*/ + +/*! + \fn AbstractSheet::copy(const QString &distName, int distId) const + + Copies the current sheet to a sheet called \a distName with \a distId. + Returns the new sheet. + */ + +/*! + * \internal + */ +AbstractSheet::AbstractSheet(const QString &name, + int id, + Workbook *workbook, + AbstractSheetPrivate *d) + : AbstractOOXmlFile(d) +{ + d_func()->name = name; + d_func()->id = id; + d_func()->workbook = workbook; +} + +/*! + * Returns the name of the sheet. + */ +QString AbstractSheet::sheetName() const +{ + Q_D(const AbstractSheet); + return d->name; +} + +/*! + * \internal + */ +void AbstractSheet::setSheetName(const QString &sheetName) +{ + Q_D(AbstractSheet); + d->name = sheetName; +} + +/*! + * Returns the type of the sheet. + */ +AbstractSheet::SheetType AbstractSheet::sheetType() const +{ + Q_D(const AbstractSheet); + return d->type; +} + +/*! + * \internal + */ +void AbstractSheet::setSheetType(SheetType type) +{ + Q_D(AbstractSheet); + d->type = type; +} + +/*! + * Returns the state of the sheet. + * + * \sa isHidden(), isVisible(), setSheetState() + */ +AbstractSheet::SheetState AbstractSheet::sheetState() const +{ + Q_D(const AbstractSheet); + return d->sheetState; +} + +/*! + * Set the state of the sheet to \a state. + */ +void AbstractSheet::setSheetState(SheetState state) +{ + Q_D(AbstractSheet); + d->sheetState = state; +} + +/*! + * Returns true if the sheet is not visible, otherwise false will be returned. + * + * \sa sheetState(), setHidden() + */ +bool AbstractSheet::isHidden() const +{ + Q_D(const AbstractSheet); + return d->sheetState != SS_Visible; +} + +/*! + * Returns true if the sheet is visible. + */ +bool AbstractSheet::isVisible() const +{ + return !isHidden(); +} + +/*! + * Make the sheet hidden or visible based on \a hidden. + */ +void AbstractSheet::setHidden(bool hidden) +{ + Q_D(AbstractSheet); + if (hidden == isHidden()) + return; + + d->sheetState = hidden ? SS_Hidden : SS_Visible; +} + +/*! + * Convenience function, equivalent to setHidden(! \a visible). + */ +void AbstractSheet::setVisible(bool visible) +{ + setHidden(!visible); +} + +/*! + * \internal + */ +int AbstractSheet::sheetId() const +{ + Q_D(const AbstractSheet); + return d->id; +} + +/*! + * \internal + */ +Drawing *AbstractSheet::drawing() const +{ + Q_D(const AbstractSheet); + return d->drawing.get(); +} + +/*! + * Return the workbook + */ +Workbook *AbstractSheet::workbook() const +{ + Q_D(const AbstractSheet); + return d->workbook; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcell.cpp b/QXlsx/source/xlsxcell.cpp new file mode 100644 index 0000000..7dba4e4 --- /dev/null +++ b/QXlsx/source/xlsxcell.cpp @@ -0,0 +1,334 @@ +// xlsxcell.cpp + +#include "xlsxcell.h" + +#include "xlsxcell_p.h" +#include "xlsxformat.h" +#include "xlsxformat_p.h" +#include "xlsxutility_p.h" +#include "xlsxworkbook.h" +#include "xlsxworksheet.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +CellPrivate::CellPrivate(Cell *p) + : q_ptr(p) +{ +} + +CellPrivate::CellPrivate(const CellPrivate *const cp) + : parent(cp->parent) + , cellType(cp->cellType) + , value(cp->value) + , formula(cp->formula) + , format(cp->format) + , richString(cp->richString) + , styleNumber(cp->styleNumber) +{ +} + +/*! + \class Cell + \inmodule QtXlsx + \brief The Cell class provides a API that is used to handle the worksheet cell. + +*/ + +/*! + \enum Cell::CellType + \value BooleanType Boolean type + \value NumberType Number type, can be blank or used with formula + \value ErrorType Error type + \value SharedStringType Shared string type + \value StringType String type, can be used with formula + \value InlineStringType Inline string type + */ + +/*! + * \internal + * Created by Worksheet only. + */ +// qint32 styleIndex = (-1) +Cell::Cell(const QVariant &data, + CellType type, + const Format &format, + Worksheet *parent, + qint32 styleIndex) + : d_ptr(new CellPrivate(this)) +{ + d_ptr->value = data; + d_ptr->cellType = type; + d_ptr->format = format; + d_ptr->parent = parent; + d_ptr->styleNumber = styleIndex; +} + +/*! + * \internal + */ +Cell::Cell(const Cell *const cell) + : d_ptr(new CellPrivate(cell->d_ptr)) +{ + d_ptr->q_ptr = this; +} + +/*! + * Destroys the Cell and cleans up. + */ +Cell::~Cell() +{ + if (nullptr != d_ptr) + delete d_ptr; +} + +/*! + * Return the dataType of this Cell + */ +Cell::CellType Cell::cellType() const +{ + Q_D(const Cell); + + return d->cellType; +} + +/*! + * Return the data content of this Cell + */ +QVariant Cell::value() const +{ + Q_D(const Cell); + + return d->value; +} + +/*! + * Return the data content of this Cell for reading + */ +QVariant Cell::readValue() const +{ + Q_D(const Cell); + + QVariant ret; // return value + ret = d->value; + + Format fmt = this->format(); + + if (isDateTime()) { + QVariant vDT = dateTime(); + if (vDT.isNull()) { + return QVariant(); + } + +// https://github.com/QtExcel/QXlsx/issues/171 +// https://www.qt.io/blog/whats-new-in-qmetatype-qvariant +#if QT_VERSION >= 0x060000 // Qt 6.0 or over + if (vDT.metaType().id() == QMetaType::QDateTime) { + ret = vDT; + } else if (vDT.metaType().id() == QMetaType::QDate) { + ret = vDT; + } else if (vDT.metaType().id() == QMetaType::QTime) { + ret = vDT; + } else { + return QVariant(); + } +#else + if (vDT.type() == QVariant::DateTime) { + ret = vDT; + } else if (vDT.type() == QVariant::Date) { + ret = vDT; + } else if (vDT.type() == QVariant::Time) { + ret = vDT; + } else { + return QVariant(); + } +#endif + + // QDateTime dt = dateTime(); + // ret = dt; + + // QString strFormat = fmt.numberFormat(); + // if (!strFormat.isEmpty()) + // { + // // TODO: use number format + // } + + // qint32 styleNo = d->styleNumber; + + // if (styleNo == 10) + // { + // } + + // if (styleNo == 11) + // { + // QTime timeValue = dt.time(); // only time. (HH:mm:ss) + // ret = timeValue; + // return ret; + // } + + // if (styleNo == 12) + // { + // } + + // if (styleNo == 13) // (HH:mm:ss) + // { + // double dValue = d->value.toDouble(); + // int day = int(dValue); // unit is day. + // double deciamlPointValue1 = dValue - double(day); + + // double dHour = deciamlPointValue1 * (double(1.0) / double(24.0)); + // int hour = int(dHour); + + // double deciamlPointValue2 = deciamlPointValue1 - (double(hour) * (double(1.0) / + // double(24.0))); double dMin = deciamlPointValue2 * (double(1.0) / double(60.0)); int min + // = int(dMin); + + // double deciamlPointValue3 = deciamlPointValue2 - (double(min) * (double(1.0) / + // double(60.0))); double dSec = deciamlPointValue3 * (double(1.0) / double(60.0)); int sec + // = int(dSec); + + // int totalHour = hour + (day * 24); + + // QString strTime; + // strTime = QString("%1:%2:%3").arg(totalHour).arg(min).arg(sec); + // ret = strTime; + + // return ret; + // } + + // return ret; + // */ + } + + if (hasFormula()) { + QString formulaString = this->formula().formulaText(); + ret = formulaString; + return ret; // return formula string + } + + return ret; +} + +/*! + * Return the style used by this Cell. If no style used, 0 will be returned. + */ +Format Cell::format() const +{ + Q_D(const Cell); + + return d->format; +} + +/*! + * Returns true if the cell has one formula. + */ +bool Cell::hasFormula() const +{ + Q_D(const Cell); + + return d->formula.isValid(); +} + +/*! + * Return the formula contents if the dataType is Formula + */ +CellFormula Cell::formula() const +{ + Q_D(const Cell); + + return d->formula; +} + +/*! + * Returns whether the value is probably a dateTime or not + */ +bool Cell::isDateTime() const +{ + Q_D(const Cell); + + Cell::CellType cellType = d->cellType; + double dValue = d->value.toDouble(); // number + // QString strValue = d->value.toString().toUtf8(); + bool isValidFormat = d->format.isValid(); + bool isDateTimeFormat = d->format.isDateTimeFormat(); // datetime format + + // dev67 + if (cellType == NumberType || cellType == DateType || cellType == CustomType) { + if (dValue >= 0 && isValidFormat && isDateTimeFormat) { + return true; + } + } + + return false; +} + +/*! + * Return the data time value. + */ +/* +QDateTime Cell::dateTime() const +{ + Q_D(const Cell); + + if (!isDateTime()) + return QDateTime(); + + return datetimeFromNumber(d->value.toDouble(), d->parent->workbook()->isDate1904()); +} +*/ +QVariant Cell::dateTime() const +{ + Q_D(const Cell); + + if (!isDateTime()) { + return QVariant(); + } + + // dev57 + + QVariant ret; + double dValue = d->value.toDouble(); + bool isDate1904 = d->parent->workbook()->isDate1904(); + ret = datetimeFromNumber(dValue, isDate1904); + return ret; +} + +/*! + * Returns whether the cell is probably a rich string or not + */ +bool Cell::isRichString() const +{ + Q_D(const Cell); + + if (d->cellType != SharedStringType && d->cellType != InlineStringType && + d->cellType != StringType) { + return false; + } + + return d->richString.isRichString(); +} + +qint32 Cell::styleNumber() const +{ + Q_D(const Cell); + + qint32 ret = d->styleNumber; + return ret; +} + +bool Cell::isDateType(CellType cellType, const Format &format) +{ + if (cellType == NumberType || cellType == DateType || cellType == CustomType) { + return format.isValid() && format.isDateTimeFormat(); + } + return false; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcellformula.cpp b/QXlsx/source/xlsxcellformula.cpp new file mode 100644 index 0000000..c614e6b --- /dev/null +++ b/QXlsx/source/xlsxcellformula.cpp @@ -0,0 +1,438 @@ +// xlsxcellformula.cpp + +#include "xlsxcellformula.h" + +#include "xlsxcellformula_p.h" +#include "xlsxutility_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +CellFormulaPrivate::CellFormulaPrivate(const QString &formula_, + const CellRange &ref_, + CellFormula::FormulaType type_) + : formula(formula_) + , type(type_) + , reference(ref_) + , ca(false) + , si(0) +{ + // Remove the formula '=' sign if exists + if (formula.startsWith(QLatin1String("="))) + formula.remove(0, 1); + else if (formula.startsWith(QLatin1String("{=")) && formula.endsWith(QLatin1String("}"))) + formula = formula.mid(2, formula.length() - 3); +} + +CellFormulaPrivate::CellFormulaPrivate(const CellFormulaPrivate &other) + : QSharedData(other) + , formula(other.formula) + , type(other.type) + , reference(other.reference) + , ca(other.ca) + , si(other.si) +{ +} + +CellFormulaPrivate::~CellFormulaPrivate() +{ +} + +/*! + \class CellFormula + \inmodule QtXlsx + \brief The CellFormula class provides a API that is used to handle the cell formula. + +*/ + +/*! + \enum CellFormula::FormulaType + \value NormalType + \value ArrayType + \value DataTableType + \value SharedType +*/ + +/*! + * Creates a new formula. + */ +CellFormula::CellFormula() +{ + // The d pointer is initialized with a null pointer +} + +/*! + * Creates a new formula with the given \a formula and \a type. + */ +CellFormula::CellFormula(const char *formula, FormulaType type) + : d(new CellFormulaPrivate(QString::fromLatin1(formula), CellRange(), type)) +{ +} + +/*! + * Creates a new formula with the given \a formula and \a type. + */ +CellFormula::CellFormula(const QString &formula, FormulaType type) + : d(new CellFormulaPrivate(formula, CellRange(), type)) +{ +} + +/*! + * Creates a new formula with the given \a formula, \a ref and \a type. + */ +CellFormula::CellFormula(const QString &formula, const CellRange &ref, FormulaType type) + : d(new CellFormulaPrivate(formula, ref, type)) +{ +} + +/*! + Creates a new formula with the same attributes as the \a other formula. + */ +CellFormula::CellFormula(const CellFormula &other) + : d(other.d) +{ +} + +/*! + Assigns the \a other formula to this formula, and returns a + reference to this formula. + */ +CellFormula &CellFormula::operator=(const CellFormula &other) +{ + d = other.d; + return *this; +} + +/*! + * Destroys this formula. + */ +CellFormula::~CellFormula() +{ +} + +/*! + * Returns the type of the formula. + */ +CellFormula::FormulaType CellFormula::formulaType() const +{ + return d ? d->type : NormalType; +} + +/*! + * Returns the contents of the formula. + */ +QString CellFormula::formulaText() const +{ + return d ? d->formula : QString(); +} + +/*! + * Returns the reference cells of the formula. For normal formula, + * this will return an invalid CellRange object. + */ +CellRange CellFormula::reference() const +{ + return d ? d->reference : CellRange(); +} + +/*! + * Returns whether the formula is valid. + */ +bool CellFormula::isValid() const +{ + return d; +} + +/*! + * Returns the shared index for shared formula. + */ +int CellFormula::sharedIndex() const +{ + return d && d->type == SharedType ? d->si : (-1); +} + +/* aca (Always Calculate Array) // not-implemented attribute + * + * Only applies to array formulas. + * + * true indicates that the entire array shall be calculated in full. + * If false the individual cells of the array shall be calculated as needed. + * + * The aca value shall be ignored unless the value of the corresponding + * t attribute is array. + * + * [Note: The primary case where an array formula must be calculated in + * part instead of in full is when some cells in the array depend on other + * cells that are semi-calculated, e.g., contains the function =(). end note] + * + * The possible values for this attribute are defined by the W3C XML Schema + * boolean datatype. + */ + +/* bx (Assigns Value to Name) // not-implemented attribute + * + * Specifies that this formula assigns a value to a name. + * + * The possible values for this attribute are defined by the W3C XML + * Schema boolean datatype. + */ + +/* del1 (Input 1 Deleted) // not-implemented attribute + * + * Whether the first input cell for data table has been deleted. + * Applies to data table formula only. Written on master cell of data table + * formula only. + * + * The possible values for this attribute are defined by the W3C XML Schema + * boolean datatype. + */ + +/* del2 (Input 2 Deleted) // not-implemented attribute + * + * Whether the second input cell for data table has been deleted. + * Applies to data table formula only. Written on master cell of data + * table formula only. + * + * The possible values for this attribute are defined by the W3C XML Schema + * boolean datatype. + */ + +/* dt2D (Data Table 2-D) // not-implemented attribute + * + * Data table is two-dimensional. Only applies to the data tables function. + * Written on master cell of data table formula only. + * + * The possible values for this attribute are defined by the W3C XML Schema + * boolean datatype. + */ + +/* dtr (Data Table Row) // not-implemented attribute + * + * true if one-dimensional data table is a row, otherwise it's a column. + * Only applies to the data tables function. Written on master cell of data + * table formula only. + * + * The possible values for this attribute are defined by the W3C XML Schema + * boolean datatype. + */ + +/* r1 (Data Table Cell 1) // not-implemented attribute + * + * First input cell for data table. Only applies to the data tables array + * function "TABLE()". Written on master cell of data table formula only. + * + * The possible values for this attribute are defined by the ST_CellRef + * simple type (§18.18.7). + */ + +/* r2 (Input Cell 2) // not-implemented attribute + * + * Second input cell for data table when dt2D is '1'. Only applies to the + * data tables array function "TABLE()".Written on master cell of data table + * formula only. + * + * The possible values for this attribute are defined by the ST_CellRef + * simple type (§18.18.7). + */ + +/*! + * \internal + * \remark pair with loadFromXml() + */ +bool CellFormula::saveToXml(QXmlStreamWriter &writer) const +{ + + // t (Formula Type) + // + // Type of formula. + // The possible values for this attribute are defined by the + // ST_CellFormulaType simple type (§18.18.6). + // + // 18.18.6 ST_CellFormulaType (Formula Type) + // array (Array Formula) + // dataTable (Table Formula) + // normal (Normal) + // shared (Shared Formula) + + QString t; + switch (d->type) { + case CellFormula::ArrayType: + t = QStringLiteral("array"); + break; + case CellFormula::SharedType: + t = QStringLiteral("shared"); + break; + case CellFormula::NormalType: + t = QStringLiteral("normal"); + break; + case CellFormula::DataTableType: + t = QStringLiteral("dataTable"); + break; + default: // undefined type + return false; + break; + } + + // f (Formula) + // + // Formula for the cell. The formula expression is contained in the + // character node of this element. + writer.writeStartElement(QStringLiteral("f")); + + if (!t.isEmpty()) { + writer.writeAttribute(QStringLiteral("t"), t); // write type(t) + } + + // ref (Range of Cells) + // + // Range of cells which the formula applies to. + // Only required for shared formula, array formula or data table. + // Only written on the master formula, + // not subsequent formulas belonging to the same shared group, array, + // or data table. + // The possible values for this attribute are defined by the ST_Ref + // simple type (§18.18.62). + + if (d->type == CellFormula::SharedType || d->type == CellFormula::ArrayType || + d->type == CellFormula::DataTableType) { + if (d->reference.isValid()) { + writer.writeAttribute(QStringLiteral("ref"), d->reference.toString()); + } + } + + // ca (Calculate Cell) + // + // Indicates that this formula needs to be recalculated the next time + // calculation is performed. [Example: This is always set on volatile + // functions, like =(), and circular references. end example] + // The possible values for this attribute are defined by the W3C XML + // Schema boolean datatype. + // + // 3.2.2 boolean + // 3.2.2.1 Lexical representation + // An instance of a datatype that is defined as ·boolean· can have the + // following legal literals {true, false, 1, 0}. + + if (d->ca) { + writer.writeAttribute(QStringLiteral("ca"), QStringLiteral("1")); + } + + // si (Shared Group Index) + // Optional attribute to optimize load performance by sharing formulas. + // + // When a formula is a shared formula (t value is shared) then this value + // indicates the group to which this particular cell's formula belongs. The + // first formula in a group of shared formulas is saved in the f element. + // This is considered the 'master' formula cell. Subsequent cells sharing + // this formula need not have the formula written in their f element. + // Instead, the attribute si value for a particular cell is used to figure + // what the formula expression should be based on the cell's relative + // location to the master formula cell. + + if (d->type == CellFormula::SharedType) { + int si = d->si; + writer.writeAttribute(QStringLiteral("si"), QString::number(si)); + } + + if (!d->formula.isEmpty()) { + QString strFormula = d->formula; + writer.writeCharacters(strFormula); // write formula + } + + writer.writeEndElement(); // f + + return true; +} + +/*! + * \internal + * \remark pair with saveToXml() + */ +bool CellFormula::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("f")); + if (!d) + d = new CellFormulaPrivate(QString(), CellRange(), NormalType); + + QXmlStreamAttributes attributes = reader.attributes(); + QString typeString = attributes.value(QLatin1String("t")).toString(); + + // branch: shared-formula + // + if (typeString == QLatin1String("array")) { + d->type = ArrayType; + } else if (typeString == QLatin1String("shared")) { + d->type = SharedType; + } else if (typeString == QLatin1String("normal")) { + d->type = NormalType; + } else if (typeString == QLatin1String("dataTable")) { + d->type = DataTableType; + } else { + /* + // undefined type + // qDebug() << "Undefined type" << typeString; + return false; + // */ + + // dev40 {{ + // https://github.com/QtExcel/QXlsx/issues/38 + d->type = NormalType; // Change: normal Type is not mentioned in the xml file!!!!! + // }} + } + + // branch: shared-formula + // + // ref (Range of Cells) + // Range of cells which the formula applies to. + // Only required for shared formula, array formula or data table. + if (d->type == CellFormula::SharedType || d->type == CellFormula::ArrayType || + d->type == CellFormula::DataTableType) { + if (attributes.hasAttribute(QLatin1String("ref"))) { + QString refString = attributes.value(QLatin1String("ref")).toString(); + d->reference = CellRange(refString); + } + } + + // branch: shared-formula + // + // si (Shared Group Index) + // Optional attribute to optimize load performance by sharing formulas. + // When a formula is a shared formula (t value is shared) then this value + // indicates the group to which this particular cell's formula belongs. + if (d->type == CellFormula::SharedType) { + QString ca = attributes.value(QLatin1String("si")).toString(); + d->ca = parseXsdBoolean(ca, false); + + if (attributes.hasAttribute(QLatin1String("si"))) { + d->si = attributes.value(QLatin1String("si")).toInt(); + } + } + + d->formula = reader.readElementText(); // read formula + + return true; +} + +/*! + * \internal + */ +bool CellFormula::operator==(const CellFormula &formula) const +{ + return d->formula == formula.d->formula && d->type == formula.d->type && d->si == formula.d->si; +} + +/*! + * \internal + */ +bool CellFormula::operator!=(const CellFormula &formula) const +{ + return d->formula != formula.d->formula || d->type != formula.d->type || d->si != formula.d->si; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcelllocation.cpp b/QXlsx/source/xlsxcelllocation.cpp new file mode 100644 index 0000000..543c734 --- /dev/null +++ b/QXlsx/source/xlsxcelllocation.cpp @@ -0,0 +1,24 @@ +// xlsxcelllocation.cpp + +#include "xlsxcelllocation.h" + +#include "xlsxcell.h" +#include "xlsxglobal.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +CellLocation::CellLocation() +{ + col = -1; + row = -1; + + cell.reset(); +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcellrange.cpp b/QXlsx/source/xlsxcellrange.cpp new file mode 100644 index 0000000..eabadd6 --- /dev/null +++ b/QXlsx/source/xlsxcellrange.cpp @@ -0,0 +1,139 @@ +// xlsxcellrange.cpp + +#include "xlsxcellrange.h" + +#include "xlsxcellreference.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +/*! + \class CellRange + \brief For a range "A1:B2" or single cell "A1" + \inmodule QtXlsx + + The CellRange class stores the top left and bottom + right rows and columns of a range in a worksheet. +*/ + +/*! + Constructs an range, i.e. a range + whose rowCount() and columnCount() are 0. +*/ +CellRange::CellRange() + : top(-1) + , left(-1) + , bottom(-2) + , right(-2) +{ +} + +/*! + Constructs the range from the given \a top, \a + left, \a bottom and \a right rows and columns. + + \sa topRow(), leftColumn(), bottomRow(), rightColumn() +*/ +CellRange::CellRange(int top, int left, int bottom, int right) + : top(top) + , left(left) + , bottom(bottom) + , right(right) +{ +} + +CellRange::CellRange(const CellReference &topLeft, const CellReference &bottomRight) + : top(topLeft.row()) + , left(topLeft.column()) + , bottom(bottomRight.row()) + , right(bottomRight.column()) +{ +} + +/*! + \overload + Constructs the range form the given \a range string. +*/ +CellRange::CellRange(const QString &range) +{ + init(range); +} + +/*! + \overload + Constructs the range form the given \a range string. +*/ +CellRange::CellRange(const char *range) +{ + init(QString::fromLatin1(range)); +} + +void CellRange::init(const QString &range) +{ + QStringList rs = range.split(QLatin1Char(':')); + if (rs.size() == 2) { + CellReference start(rs[0]); + CellReference end(rs[1]); + top = start.row(); + left = start.column(); + bottom = end.row(); + right = end.column(); + } else { + CellReference p(rs[0]); + top = p.row(); + left = p.column(); + bottom = p.row(); + right = p.column(); + } +} + +/*! + Constructs a the range by copying the given \a + other range. +*/ +CellRange::CellRange(const CellRange &other) + : top(other.top) + , left(other.left) + , bottom(other.bottom) + , right(other.right) +{ +} + +/*! + Destroys the range. +*/ +CellRange::~CellRange() +{ +} + +/*! + Convert the range to string notation, such as "A1:B5". +*/ +QString CellRange::toString(bool row_abs, bool col_abs) const +{ + if (!isValid()) + return QString(); + + if (left == right && top == bottom) { + // Single cell + return CellReference(top, left).toString(row_abs, col_abs); + } + + QString cell_1 = CellReference(top, left).toString(row_abs, col_abs); + QString cell_2 = CellReference(bottom, right).toString(row_abs, col_abs); + return cell_1 + QLatin1String(":") + cell_2; +} + +/*! + * Returns true if the Range is valid. + */ +bool CellRange::isValid() const +{ + return left <= right && top <= bottom; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcellreference.cpp b/QXlsx/source/xlsxcellreference.cpp new file mode 100644 index 0000000..df885cf --- /dev/null +++ b/QXlsx/source/xlsxcellreference.cpp @@ -0,0 +1,161 @@ +// xlsxcellreference.cpp + +#include "xlsxcellreference.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +namespace { + +int intPow(int x, int p) +{ + if (p == 0) + return 1; + if (p == 1) + return x; + + int tmp = intPow(x, p / 2); + if (p % 2 == 0) + return tmp * tmp; + else + return x * tmp * tmp; +} + +QString col_to_name(int col_num) +{ + static thread_local QMap col_cache; + + auto it = col_cache.find(col_num); + if (it == col_cache.end()) { + QString col_str; + int remainder; + while (col_num) { + remainder = col_num % 26; + if (remainder == 0) + remainder = 26; + col_str.prepend(QChar('A' + remainder - 1)); + col_num = (col_num - 1) / 26; + } + it = col_cache.insert(col_num, col_str); + } + + return it.value(); +} + +int col_from_name(const QString &col_str) +{ + int col = 0; + int expn = 0; + for (int i = col_str.size() - 1; i > -1; --i) { + col += (col_str[i].unicode() - 'A' + 1) * intPow(26, expn); + expn++; + } + + return col; +} +} // namespace + +/*! + \class CellReference + \brief For one single cell such as "A1" + \inmodule QtXlsx + + The CellReference class stores the cell location in a worksheet. +*/ + +/*! + Constructs an invalid Cell Reference +*/ +CellReference::CellReference() + : _row(-1) + , _column(-1) +{ +} + +/*! + Constructs the Reference from the given \a row, and \a column. +*/ +CellReference::CellReference(int row, int column) + : _row(row) + , _column(column) +{ +} + +/*! + \overload + Constructs the Reference form the given \a cell string. +*/ +CellReference::CellReference(const QString &cell) +{ + init(cell); +} + +/*! + \overload + Constructs the Reference form the given \a cell string. +*/ +CellReference::CellReference(const char *cell) +{ + init(QString::fromLatin1(cell)); +} + +void CellReference::init(const QString &cell_str) +{ + static thread_local QRegularExpression re(QStringLiteral("^\\$?([A-Z]{1,3})\\$?(\\d+)$")); + QRegularExpressionMatch match = re.match(cell_str); + if (match.hasMatch()) { + const QString col_str = match.captured(1); + const QString row_str = match.captured(2); + _row = row_str.toInt(); + _column = col_from_name(col_str); + } +} + +/*! + Constructs a Reference by copying the given \a + other Reference. +*/ +CellReference::CellReference(const CellReference &other) + : _row(other._row) + , _column(other._column) +{ +} + +/*! + Destroys the Reference. +*/ +CellReference::~CellReference() +{ +} + +/*! + Convert the Reference to string notation, such as "A1" or "$A$1". + If current object is invalid, an empty string will be returned. +*/ +QString CellReference::toString(bool row_abs, bool col_abs) const +{ + if (!isValid()) + return QString(); + + QString cell_str; + if (col_abs) + cell_str.append(QLatin1Char('$')); + cell_str.append(col_to_name(_column)); + if (row_abs) + cell_str.append(QLatin1Char('$')); + cell_str.append(QString::number(_row)); + return cell_str; +} + +/*! + * Returns true if the Reference is valid. + */ +bool CellReference::isValid() const +{ + return _row > 0 && _column > 0; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxchart.cpp b/QXlsx/source/xlsxchart.cpp new file mode 100644 index 0000000..4af5912 --- /dev/null +++ b/QXlsx/source/xlsxchart.cpp @@ -0,0 +1,2100 @@ +// xlsxchart.cpp + +#include "xlsxcellrange.h" +#include "xlsxchart_p.h" +#include "xlsxutility_p.h" +#include "xlsxworksheet.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ChartPrivate::ChartPrivate(Chart *q, Chart::CreateFlag flag) + : AbstractOOXmlFilePrivate(q, flag) + , chartType(static_cast(0)) +{ +} + +ChartPrivate::~ChartPrivate() +{ +} + +/*! + * \internal + */ +Chart::Chart(AbstractSheet *parent, CreateFlag flag) + : AbstractOOXmlFile(new ChartPrivate(this, flag)) +{ + Q_D(Chart); + + d_func()->sheet = parent; + + // d->legendPos = Chart::ChartAxisPos::None; + d->legendPos = Chart::None; + d->legendOverlay = false; + d->majorGridlinesEnabled = false; + d->minorGridlinesEnabled = false; +} + +/*! + * Destroys the chart. + */ +Chart::~Chart() +{ +} + +/*! + * Add the data series which is in the range \a range of the \a sheet. + */ +void Chart::addSeries(const CellRange &range, + AbstractSheet *sheet, + bool headerH, + bool headerV, + bool swapHeaders) +{ + Q_D(Chart); + + if (!range.isValid()) + return; + if (sheet && sheet->sheetType() != AbstractSheet::ST_WorkSheet) + return; + if (!sheet && d->sheet->sheetType() != AbstractSheet::ST_WorkSheet) + return; + + QString sheetName = sheet ? sheet->sheetName() : d->sheet->sheetName(); + // In case sheetName contains space or ' + sheetName = escapeSheetName(sheetName); + + if (range.columnCount() == 1 || range.rowCount() == 1) { + auto series = std::make_shared(); + series->numberDataSource_numRef = + sheetName + QLatin1String("!") + range.toString(true, true); + d->seriesList.append(series); + } else if ((range.columnCount() < range.rowCount()) || swapHeaders) { + // Column based series + int firstDataRow = range.firstRow(); + int firstDataColumn = range.firstColumn(); + + QString axDataSouruce_numRef; + if (d->chartType == CT_ScatterChart || d->chartType == CT_BubbleChart) { + firstDataColumn += 1; + CellRange subRange( + range.firstRow(), range.firstColumn(), range.lastRow(), range.firstColumn()); + axDataSouruce_numRef = sheetName + QLatin1String("!") + subRange.toString(true, true); + } + + if (headerH) { + firstDataRow += 1; + } + if (headerV) { + firstDataColumn += 1; + } + + for (int col = firstDataColumn; col <= range.lastColumn(); ++col) { + CellRange subRange(firstDataRow, col, range.lastRow(), col); + auto series = std::make_shared(); + series->axDataSource_numRef = axDataSouruce_numRef; + series->numberDataSource_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + + if (headerH) { + CellRange subRange(range.firstRow(), col, range.firstRow(), col); + series->headerH_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + } else { + series->headerH_numRef = QString(); + } + if (headerV) { + CellRange subRange( + firstDataRow, range.firstColumn(), range.lastRow(), range.firstColumn()); + series->headerV_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + } else { + series->headerV_numRef = QString(); + } + series->swapHeader = swapHeaders; + + d->seriesList.append(series); + } + + } else { + // Row based series + int firstDataRow = range.firstRow(); + int firstDataColumn = range.firstColumn(); + + QString axDataSouruce_numRef; + if (d->chartType == CT_ScatterChart || d->chartType == CT_BubbleChart) { + firstDataRow += 1; + CellRange subRange( + range.firstRow(), range.firstColumn(), range.firstRow(), range.lastColumn()); + axDataSouruce_numRef = sheetName + QLatin1String("!") + subRange.toString(true, true); + } + + if (headerH) { + firstDataRow += 1; + } + if (headerV) { + firstDataColumn += 1; + } + + for (int row = firstDataRow; row <= range.lastRow(); ++row) { + CellRange subRange(row, firstDataColumn, row, range.lastColumn()); + auto series = std::make_shared(); + series->axDataSource_numRef = axDataSouruce_numRef; + series->numberDataSource_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + + if (headerH) { + CellRange subRange( + range.firstRow(), firstDataColumn, range.firstRow(), range.lastColumn()); + series->headerH_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + } else { + series->headerH_numRef = QString(); + } + + if (headerV) { + CellRange subRange(row, range.firstColumn(), row, range.firstColumn()); + series->headerV_numRef = + sheetName + QLatin1String("!") + subRange.toString(true, true); + } else { + series->headerV_numRef = QString(); + } + series->swapHeader = swapHeaders; + + d->seriesList.append(series); + } + } +} + +/*! + * Set the type of the chart to \a type + */ +void Chart::setChartType(ChartType type) +{ + Q_D(Chart); + + d->chartType = type; +} + +/*! + * \internal + * + */ +void Chart::setChartStyle(int id) +{ + Q_UNUSED(id) + //! Todo +} + +void Chart::setAxisTitle(Chart::ChartAxisPos pos, QString axisTitle) +{ + Q_D(Chart); + + if (axisTitle.isEmpty()) + return; + + // dev24 : fixed for old compiler + if (pos == Chart::Left) { + d->axisNames[XlsxAxis::Left] = axisTitle; + } else if (pos == Chart::Top) { + d->axisNames[XlsxAxis::Top] = axisTitle; + } else if (pos == Chart::Right) { + d->axisNames[XlsxAxis::Right] = axisTitle; + } else if (pos == Chart::Bottom) { + d->axisNames[XlsxAxis::Bottom] = axisTitle; + } +} + +// dev25 +void Chart::setChartTitle(QString strchartTitle) +{ + Q_D(Chart); + + d->chartTitle = strchartTitle; +} + +void Chart::setChartLegend(Chart::ChartAxisPos legendPos, bool overlay) +{ + Q_D(Chart); + + d->legendPos = legendPos; + d->legendOverlay = overlay; +} + +void Chart::setGridlinesEnable(bool majorGridlinesEnable, bool minorGridlinesEnable) +{ + Q_D(Chart); + + d->majorGridlinesEnabled = majorGridlinesEnable; + d->minorGridlinesEnabled = minorGridlinesEnable; +} + +/*! + * \internal + */ +void Chart::saveToXmlFile(QIODevice *device) const +{ + Q_D(const Chart); + + /* + + + + + + + + + ... + + + + + + ... + + + + + + */ + + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + + // L.4.13.2.2 Chart + // + // chartSpace is the root node, which contains an element defining the chart, + // and an element defining the print settings for the chart. + + writer.writeStartElement(QStringLiteral("c:chartSpace")); + + writer.writeAttribute(QStringLiteral("xmlns:c"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/chart")); + writer.writeAttribute(QStringLiteral("xmlns:a"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/main")); + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + + /* + * chart is the root element for the chart. If the chart is a 3D chart, + * then a view3D element is contained, which specifies the 3D view. + * It then has a plot area, which defines a layout and contains an element + * that corresponds to, and defines, the type of chart. + */ + + d->saveXmlChart(writer); + + writer.writeEndElement(); // c:chartSpace + writer.writeEndDocument(); +} + +/*! + * \internal + */ +bool Chart::loadFromXmlFile(QIODevice *device) +{ + Q_D(Chart); + + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("chart")) { + if (!d->loadXmlChart(reader)) { + return false; + } + } + } + } + + return true; +} + +bool ChartPrivate::loadXmlChart(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("chart")); + + // qDebug() << "-------------- loadXmlChart"; + + while (!reader.atEnd()) { + reader.readNextStartElement(); + + // qDebug() << "-------------1- " << reader.name(); + + if (reader.tokenType() == QXmlStreamReader::StartElement) { + + if (reader.name() == QLatin1String("plotArea")) { + if (!loadXmlPlotArea(reader)) { + return false; + } + } else if (reader.name() == QLatin1String("title")) { + //! Todo + + if (loadXmlChartTitle(reader)) { + } + } + // else if (reader.name() == QLatin1String("legend")) + // { + // loadXmlChartLegend(reader); + // qDebug() << "-------------- loadXmlChartLegend"; + // } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("chart")) { + break; + } + } + return true; +} + +// TO DEBUG: loop is not work, when i looping second element. +/* +dchrt_CT_PlotArea = + element layout { dchrt_CT_Layout }?, + (element areaChart { dchrt_CT_AreaChart } + | element area3DChart { dchrt_ CT_Area3DChart } + | element lineChart { dchrt_CT_LineChart } + | element line3DChart { dchrt_CT_Line3DChart } + | element stockChart { dchrt_CT_StockChart } + | element radarChart { dchrt_CT_RadarChart } + | element scatterChart { dchrt_CT_ScatterChart } + | element pieChart { dchrt_CT_PieChart } + | element pie3DChart { dchrt_CT_Pie3DChart } + | element doughnutChart { dchrt_CT_DoughnutChart } + | element barChart { dchrt_CT_BarChart } + | element bar3DChart { dchrt_CT_Bar3DChart } + | element ofPieChart { dchrt_CT_OfPieChart } + | element surfaceChart { dchrt_CT_SurfaceChart } + | element surface3DChart { dchrt_CT_Surface3DChart } + | element bubbleChart { dchrt_CT_BubbleChart })+, + (element valAx { dchrt_CT_ValAx } + | element catAx { dchrt_CT_CatAx } + | element dateAx { dchrt_CT_DateAx } + | element serAx { dchrt_CT_SerAx })*, + element dTable { dchrt_CT_DTable }?, + element spPr { a_CT_ShapeProperties }?, + element extLst { dchrt_CT_ExtensionList }? + */ +bool ChartPrivate::loadXmlPlotArea(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("plotArea")); + + // TO DEBUG: + + reader.readNext(); + + while (!reader.atEnd()) { + // qDebug() << "-------------2- " << reader.name(); + + if (reader.isStartElement()) { + if (!loadXmlPlotAreaElement(reader)) { + qDebug() << "[debug] failed to load plotarea element."; + return false; + } else if (reader.name() == QLatin1String("legend")) // Why here? + { + loadXmlChartLegend(reader); + // qDebug() << "-------------- loadXmlChartLegend"; + } + + reader.readNext(); + } else { + reader.readNext(); + } + } + + return true; +} + +bool ChartPrivate::loadXmlPlotAreaElement(QXmlStreamReader &reader) +{ + if (reader.name() == QLatin1String("layout")) { + //! ToDo extract attributes + layout = readSubTree(reader); + } else if (reader.name().endsWith(QLatin1String("Chart"))) { + // for pieChart, barChart, ... (choose one) + if (!loadXmlXxxChart(reader)) { + qDebug() << "[debug] failed to load chart"; + return false; + } + } else if (reader.name() == QLatin1String("catAx")) // choose one : catAx, dateAx, serAx, valAx + { + // qDebug() << "loadXmlAxisCatAx()"; + loadXmlAxisCatAx(reader); + } else if (reader.name() == QLatin1String("dateAx")) // choose one : catAx, dateAx, serAx, valAx + { + // qDebug() << "loadXmlAxisDateAx()"; + loadXmlAxisDateAx(reader); + } else if (reader.name() == QLatin1String("serAx")) // choose one : catAx, dateAx, serAx, valAx + { + // qDebug() << "loadXmlAxisSerAx()"; + loadXmlAxisSerAx(reader); + } else if (reader.name() == QLatin1String("valAx")) // choose one : catAx, dateAx, serAx, valAx + { + // qDebug() << "loadXmlAxisValAx()"; + loadXmlAxisValAx(reader); + } else if (reader.name() == QLatin1String("dTable")) { + //! ToDo + // dTable "CT_DTable" + // reader.skipCurrentElement(); + } else if (reader.name() == QLatin1String("spPr")) { + //! ToDo + // spPr "a:CT_ShapeProperties" + // reader.skipCurrentElement(); + } else if (reader.name() == QLatin1String("extLst")) { + //! ToDo + // extLst "CT_ExtensionList" + // reader.skipCurrentElement(); + } + + return true; +} + +bool ChartPrivate::loadXmlXxxChart(QXmlStreamReader &reader) +{ + const auto &name = reader.name(); + + if (name == QLatin1String("areaChart")) { + chartType = Chart::CT_AreaChart; + } else if (name == QLatin1String("area3DChart")) { + chartType = Chart::CT_Area3DChart; + } else if (name == QLatin1String("lineChart")) { + chartType = Chart::CT_LineChart; + } else if (name == QLatin1String("line3DChart")) { + chartType = Chart::CT_Line3DChart; + } else if (name == QLatin1String("stockChart")) { + chartType = Chart::CT_StockChart; + } else if (name == QLatin1String("radarChart")) { + chartType = Chart::CT_RadarChart; + } else if (name == QLatin1String("scatterChart")) { + chartType = Chart::CT_ScatterChart; + } else if (name == QLatin1String("pieChart")) { + chartType = Chart::CT_PieChart; + } else if (name == QLatin1String("pie3DChart")) { + chartType = Chart::CT_Pie3DChart; + } else if (name == QLatin1String("doughnutChart")) { + chartType = Chart::CT_DoughnutChart; + } else if (name == QLatin1String("barChart")) { + chartType = Chart::CT_BarChart; + } else if (name == QLatin1String("bar3DChart")) { + chartType = Chart::CT_Bar3DChart; + } else if (name == QLatin1String("ofPieChart")) { + chartType = Chart::CT_OfPieChart; + } else if (name == QLatin1String("surfaceChart")) { + chartType = Chart::CT_SurfaceChart; + } else if (name == QLatin1String("surface3DChart")) { + chartType = Chart::CT_Surface3DChart; + } else if (name == QLatin1String("bubbleChart")) { + chartType = Chart::CT_BubbleChart; + } else { + qDebug() << "[undefined chart type] " << name; + chartType = Chart::CT_NoStatementChart; + return false; + } + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + // dev57 + + if (reader.name() == QLatin1String("ser")) { + loadXmlSer(reader); + } else if (reader.name() == QLatin1String("varyColors")) { + } else if (reader.name() == QLatin1String("barDir")) { + } else if (reader.name() == QLatin1String("axId")) { + // + + } else if (reader.name() == QLatin1String("scatterStyle")) { + } else if (reader.name() == QLatin1String("holeSize")) { + } else { + } + + } else if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == name) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlSer(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("ser")); + + auto series = std::make_shared(); + seriesList.append(series); + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("ser"))) { + if (reader.readNextStartElement()) { + // TODO beide Header noch auswerten RTR 2019.11 + const auto &name = reader.name(); + if (name == QLatin1String("tx")) { + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == name)) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("strRef")) + series->headerV_numRef = loadXmlStrRef(reader); + } + } + } else if (name == QLatin1String("cat") || name == QLatin1String("xVal")) { + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == name)) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("numRef")) + series->axDataSource_numRef = loadXmlNumRef(reader); + else if (reader.name() == QLatin1String("strRef")) + series->headerH_numRef = loadXmlStrRef(reader); + } + } + } else if (name == QLatin1String("val") || name == QLatin1String("yVal")) { + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == name)) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("numRef")) + series->numberDataSource_numRef = loadXmlNumRef(reader); + } + } + } else if (name == QLatin1String("extLst")) { + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == name)) { + reader.readNextStartElement(); + } + } + } + } + + return true; +} + +QString ChartPrivate::loadXmlNumRef(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("numRef")); + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("numRef"))) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("f")) + return reader.readElementText(); + } + } + + return QString(); +} + +QString ChartPrivate::loadXmlStrRef(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("strRef")); + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("strRef"))) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("f")) + return reader.readElementText(); + } + } + + return QString(); +} + +void ChartPrivate::saveXmlChart(QXmlStreamWriter &writer) const +{ + //---------------------------------------------------- + // c:chart + writer.writeStartElement(QStringLiteral("c:chart")); + + //---------------------------------------------------- + // c:title + + saveXmlChartTitle(writer); // write 'chart title' + + //---------------------------------------------------- + // c:plotArea + + writer.writeStartElement(QStringLiteral("c:plotArea")); + + // a little workaround for Start- and EndElement with starting ">" and ending without ">" + writer.device()->write(">"); // layout + writer.device()->write(layout.toUtf8()); + writer.device()->write("chartTitle = textValue; + return true; + } + } + } + + return false; +} + +// write 'chart title' +void ChartPrivate::saveXmlChartTitle(QXmlStreamWriter &writer) const +{ + if (chartTitle.isEmpty()) + return; + + writer.writeStartElement(QStringLiteral("c:title")); + /* + + + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:tx")); + /* + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:rich")); + /* + + + + + + */ + + writer.writeEmptyElement(QStringLiteral("a:bodyPr")); // + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + + writer.writeEmptyElement(QStringLiteral("a:lstStyle")); // + + writer.writeStartElement(QStringLiteral("a:p")); + /* + + + + + + + */ + + // + writer.writeStartElement(QStringLiteral("a:pPr")); + + writer.writeAttribute(QStringLiteral("lvl"), QStringLiteral("0")); + + // + writer.writeStartElement(QStringLiteral("a:defRPr")); + + writer.writeAttribute(QStringLiteral("b"), QStringLiteral("0")); + + writer.writeEndElement(); // a:defRPr + + writer.writeEndElement(); // a:pPr + + /* + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("a:r")); + /* + + + + + + + */ + + // chart name + writer.writeTextElement(QStringLiteral("a:t"), chartTitle); + + writer.writeEndElement(); // a:r + + writer.writeEndElement(); // a:p + + writer.writeEndElement(); // c:rich + + writer.writeEndElement(); // c:tx + + // + writer.writeStartElement(QStringLiteral("c:overlay")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("0")); + writer.writeEndElement(); // c:overlay + + writer.writeEndElement(); // c:title +} +// }} + +// write 'chart legend' +void ChartPrivate::saveXmlChartLegend(QXmlStreamWriter &writer) const +{ + if (legendPos == Chart::None) + return; + + // + // + // + // + + writer.writeStartElement(QStringLiteral("c:legend")); + + writer.writeStartElement(QStringLiteral("c:legendPos")); + + QString pos; + switch (legendPos) { + // case Chart::ChartAxisPos::Right: + case Chart::Right: + pos = QStringLiteral("r"); + break; + + // case Chart::ChartAxisPos::Left: + case Chart::Left: + pos = QStringLiteral("l"); + break; + + // case Chart::ChartAxisPos::Top: + case Chart::Top: + pos = QStringLiteral("t"); + break; + + // case Chart::ChartAxisPos::Bottom: + case Chart::Bottom: + pos = QStringLiteral("b"); + break; + + default: + pos = QStringLiteral("r"); + break; + } + + writer.writeAttribute(QStringLiteral("val"), pos); + + writer.writeEndElement(); // c:legendPos + + writer.writeStartElement(QStringLiteral("c:overlay")); + + if (legendOverlay) { + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("1")); + } else { + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("0")); + } + + writer.writeEndElement(); // c:overlay + + writer.writeEndElement(); // c:legend +} + +void ChartPrivate::saveXmlPieChart(QXmlStreamWriter &writer) const +{ + QString name = chartType == Chart::CT_PieChart ? QStringLiteral("c:pieChart") + : QStringLiteral("c:pie3DChart"); + + writer.writeStartElement(name); + + // Do the same behavior as Excel, Pie prefer varyColors + writer.writeEmptyElement(QStringLiteral("c:varyColors")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("1")); + + for (int i = 0; i < seriesList.size(); ++i) + saveXmlSer(writer, seriesList[i].get(), i); + + writer.writeEndElement(); // pieChart, pie3DChart +} + +void ChartPrivate::saveXmlBarChart(QXmlStreamWriter &writer) const +{ + QString name = chartType == Chart::CT_BarChart ? QStringLiteral("c:barChart") + : QStringLiteral("c:bar3DChart"); + + writer.writeStartElement(name); + + writer.writeEmptyElement(QStringLiteral("c:barDir")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("col")); + + for (int i = 0; i < seriesList.size(); ++i) { + saveXmlSer(writer, seriesList[i].get(), i); + } + + if (axisList.isEmpty()) { + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Cat, XlsxAxis::Bottom, 0, 1, axisNames[XlsxAxis::Bottom])); + + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Val, XlsxAxis::Left, 1, 0, axisNames[XlsxAxis::Left])); + } + + // Note: Bar3D have 2~3 axes + // int axisListSize = axisList.size(); + // [dev62] + // Q_ASSERT( axisListSize == 2 || + // ( axisListSize == 3 && chartType == Chart::CT_Bar3DChart ) ); + + for (int i = 0; i < axisList.size(); ++i) { + writer.writeEmptyElement(QStringLiteral("c:axId")); + writer.writeAttribute(QStringLiteral("val"), QString::number(axisList[i]->axisId)); + } + + writer.writeEndElement(); // barChart, bar3DChart +} + +void ChartPrivate::saveXmlLineChart(QXmlStreamWriter &writer) const +{ + QString name = chartType == Chart::CT_LineChart ? QStringLiteral("c:lineChart") + : QStringLiteral("c:line3DChart"); + + writer.writeStartElement(name); + + // writer.writeEmptyElement(QStringLiteral("grouping")); // dev22 + + for (int i = 0; i < seriesList.size(); ++i) + saveXmlSer(writer, seriesList[i].get(), i); + + if (axisList.isEmpty()) { + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Cat, XlsxAxis::Bottom, 0, 1, axisNames[XlsxAxis::Bottom])); + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Val, XlsxAxis::Left, 1, 0, axisNames[XlsxAxis::Left])); + if (chartType == Chart::CT_Line3DChart) + const_cast(this)->axisList.append( + std::make_shared(XlsxAxis::T_Ser, XlsxAxis::Bottom, 2, 0)); + } + + Q_ASSERT((axisList.size() == 2 || chartType == Chart::CT_LineChart) || + (axisList.size() == 3 && chartType == Chart::CT_Line3DChart)); + + for (int i = 0; i < axisList.size(); ++i) { + writer.writeEmptyElement(QStringLiteral("c:axId")); + writer.writeAttribute(QStringLiteral("val"), QString::number(axisList[i]->axisId)); + } + + writer.writeEndElement(); // lineChart, line3DChart +} + +void ChartPrivate::saveXmlScatterChart(QXmlStreamWriter &writer) const +{ + const QString name = QStringLiteral("c:scatterChart"); + + writer.writeStartElement(name); + + writer.writeEmptyElement(QStringLiteral("c:scatterStyle")); + + for (int i = 0; i < seriesList.size(); ++i) + saveXmlSer(writer, seriesList[i].get(), i); + + if (axisList.isEmpty()) { + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Val, XlsxAxis::Bottom, 0, 1, axisNames[XlsxAxis::Bottom])); + const_cast(this)->axisList.append(std::make_shared( + XlsxAxis::T_Val, XlsxAxis::Left, 1, 0, axisNames[XlsxAxis::Left])); + } + + int axisListSize = axisList.size(); + Q_ASSERT(axisListSize == 2); + + for (int i = 0; i < axisList.size(); ++i) { + writer.writeEmptyElement(QStringLiteral("c:axId")); + writer.writeAttribute(QStringLiteral("val"), QString::number(axisList[i]->axisId)); + } + + writer.writeEndElement(); // c:scatterChart +} + +void ChartPrivate::saveXmlAreaChart(QXmlStreamWriter &writer) const +{ + QString name = chartType == Chart::CT_AreaChart ? QStringLiteral("c:areaChart") + : QStringLiteral("c:area3DChart"); + + writer.writeStartElement(name); + + // writer.writeEmptyElement(QStringLiteral("grouping")); // dev22 + + for (int i = 0; i < seriesList.size(); ++i) + saveXmlSer(writer, seriesList[i].get(), i); + + if (axisList.isEmpty()) { + const_cast(this)->axisList.append( + std::make_shared(XlsxAxis::T_Cat, XlsxAxis::Bottom, 0, 1)); + const_cast(this)->axisList.append( + std::make_shared(XlsxAxis::T_Val, XlsxAxis::Left, 1, 0)); + } + + // Note: Area3D have 2~3 axes + Q_ASSERT(axisList.size() == 2 || (axisList.size() == 3 && chartType == Chart::CT_Area3DChart)); + + for (int i = 0; i < axisList.size(); ++i) { + writer.writeEmptyElement(QStringLiteral("c:axId")); + writer.writeAttribute(QStringLiteral("val"), QString::number(axisList[i]->axisId)); + } + + writer.writeEndElement(); // lineChart, line3DChart +} + +void ChartPrivate::saveXmlDoughnutChart(QXmlStreamWriter &writer) const +{ + QString name = QStringLiteral("c:doughnutChart"); + + writer.writeStartElement(name); + + writer.writeEmptyElement(QStringLiteral("c:varyColors")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("1")); + + for (int i = 0; i < seriesList.size(); ++i) + saveXmlSer(writer, seriesList[i].get(), i); + + writer.writeStartElement(QStringLiteral("c:holeSize")); + writer.writeAttribute(QStringLiteral("val"), QString::number(50)); + + writer.writeEndElement(); +} + +void ChartPrivate::saveXmlSer(QXmlStreamWriter &writer, XlsxSeries *ser, int id) const +{ + + writer.writeStartElement(QStringLiteral("c:ser")); + writer.writeEmptyElement(QStringLiteral("c:idx")); + writer.writeAttribute(QStringLiteral("val"), QString::number(id)); + writer.writeEmptyElement(QStringLiteral("c:order")); + writer.writeAttribute(QStringLiteral("val"), QString::number(id)); + + QString header1; + QString header2; + if (ser->swapHeader) { + header1 = ser->headerH_numRef; + header2 = ser->headerV_numRef; + } else { + header1 = ser->headerV_numRef; + header2 = ser->headerH_numRef; + } + + if (!header1.isEmpty()) { + writer.writeStartElement(QStringLiteral("c:tx")); + writer.writeStartElement(QStringLiteral("c:strRef")); + writer.writeTextElement(QStringLiteral("c:f"), header1); + writer.writeEndElement(); + writer.writeEndElement(); + } + if (!header2.isEmpty()) { + writer.writeStartElement(QStringLiteral("c:cat")); + writer.writeStartElement(QStringLiteral("c:strRef")); + writer.writeTextElement(QStringLiteral("c:f"), header2); + writer.writeEndElement(); + writer.writeEndElement(); + } + +#if 0 + if (!ser->axDataSource_numRef.isEmpty()) + { + if (chartType == Chart::CT_ScatterChart || chartType == Chart::CT_BubbleChart) + { + writer.writeStartElement(QStringLiteral("c:xVal")); + } + else + { + writer.writeStartElement(QStringLiteral("c:cat")); + } + + writer.writeStartElement(QStringLiteral("c:numRef")); + writer.writeTextElement(QStringLiteral("c:f"), ser->axDataSource_numRef); + writer.writeEndElement();//c:numRef + writer.writeEndElement();//c:cat or c:xVal + } +#endif + + if (!ser->numberDataSource_numRef.isEmpty()) { + if (chartType == Chart::CT_ScatterChart || chartType == Chart::CT_BubbleChart) + writer.writeStartElement(QStringLiteral("c:yVal")); + else + writer.writeStartElement(QStringLiteral("c:val")); + writer.writeStartElement(QStringLiteral("c:numRef")); + writer.writeTextElement(QStringLiteral("c:f"), ser->numberDataSource_numRef); + writer.writeEndElement(); // c:numRef + writer.writeEndElement(); // c:val or c:yVal + } + + writer.writeEndElement(); // c:ser +} + +bool ChartPrivate::loadXmlAxisCatAx(QXmlStreamReader &reader) +{ + + auto axis = std::make_shared(); + axis->type = XlsxAxis::T_Cat; + axisList.append(axis); + + // load EG_AxShared + if (!loadXmlAxisEG_AxShared(reader, axis.get())) { + qDebug() << "failed to load EG_AxShared"; + return false; + } + + //! TODO: load element + // auto + // lblAlgn + // lblOffset + // tickLblSkip + // tickMarkSkip + // noMultiLvlLbl + // extLst + + return true; +} + +bool ChartPrivate::loadXmlAxisDateAx(QXmlStreamReader &reader) +{ + + auto axis = std::make_shared(); + axis->type = XlsxAxis::T_Date; + axisList.append(axis); + + // load EG_AxShared + if (!loadXmlAxisEG_AxShared(reader, axis.get())) { + qDebug() << "failed to load EG_AxShared"; + return false; + } + + //! TODO: load element + // auto + // lblOffset + // baseTimeUnit + // majorUnit + // majorTimeUnit + // minorUnit + // minorTimeUnit + // extLst + + return true; +} + +bool ChartPrivate::loadXmlAxisSerAx(QXmlStreamReader &reader) +{ + + auto axis = std::make_shared(); + axis->type = XlsxAxis::T_Ser; + axisList.append(axis); + + // load EG_AxShared + if (!loadXmlAxisEG_AxShared(reader, axis.get())) { + qDebug() << "failed to load EG_AxShared"; + return false; + } + + //! TODO: load element + // tickLblSkip + // tickMarkSkip + // extLst + + return true; +} + +bool ChartPrivate::loadXmlAxisValAx(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("valAx")); + + auto axis = std::make_shared(); + axis->type = XlsxAxis::T_Val; + axisList.append(axis); + + if (!loadXmlAxisEG_AxShared(reader, axis.get())) { + qDebug() << "failed to load EG_AxShared"; + return false; + } + + //! TODO: load element + // crossBetween + // majorUnit + // minorUnit + // dispUnits + // extLst + + return true; +} + +/* + + + (*)(M) + (*)(M) + + (*)(M) + + + (*) + + + + + + + (*)(M) + + + + + + +*/ +bool ChartPrivate::loadXmlAxisEG_AxShared(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_ASSERT(nullptr != axis); + Q_ASSERT(reader.name().endsWith(QLatin1String("Ax"))); + QString name = reader.name().toString(); // + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + // qDebug() << "[debug]" << QTime::currentTime() << reader.name().toString(); + + if (reader.name() == QLatin1String("axId")) // mandatory element + { + // dev57 + uint axId = reader.attributes().value(QStringLiteral("val")).toUInt(); // for Qt5.1 + axis->axisId = axId; + } else if (reader.name() == QLatin1String("scaling")) { + // mandatory element + + loadXmlAxisEG_AxShared_Scaling(reader, axis); + } else if (reader.name() == QLatin1String("delete")) { + //! TODO + } else if (reader.name() == QLatin1String("axPos")) { + // mandatory element + + QString axPosVal = reader.attributes().value(QLatin1String("val")).toString(); + + if (axPosVal == QLatin1String("l")) { + axis->axisPos = XlsxAxis::Left; + } else if (axPosVal == QLatin1String("r")) { + axis->axisPos = XlsxAxis::Right; + } else if (axPosVal == QLatin1String("t")) { + axis->axisPos = XlsxAxis::Top; + } else if (axPosVal == QLatin1String("b")) { + axis->axisPos = XlsxAxis::Bottom; + } + } else if (reader.name() == QLatin1String("majorGridlines")) { + //! TODO anything else? + majorGridlinesEnabled = true; + } else if (reader.name() == QLatin1String("minorGridlines")) { + //! TODO anything else? + minorGridlinesEnabled = true; + } else if (reader.name() == QLatin1String("title")) { + // title + if (!loadXmlAxisEG_AxShared_Title(reader, axis)) { + qDebug() << "failed to load EG_AxShared title."; + Q_ASSERT(false); + return false; + } + } else if (reader.name() == QLatin1String("numFmt")) { + //! TODO + } else if (reader.name() == QLatin1String("majorTickMark")) { + //! TODO + } else if (reader.name() == QLatin1String("minorTickMark")) { + //! TODO + } else if (reader.name() == QLatin1String("tickLblPos")) { + //! TODO + } else if (reader.name() == QLatin1String("spPr")) { + //! TODO + } else if (reader.name() == QLatin1String("txPr")) { + //! TODO + } else if (reader.name() == QLatin1String("crossAx")) // mandatory element + { + // dev57 + uint crossAx = + reader.attributes().value(QLatin1String("val")).toUInt(); // for Qt5.1 + axis->crossAx = crossAx; + } else if (reader.name() == QLatin1String("crosses")) { + //! TODO + } else if (reader.name() == QLatin1String("crossesAt")) { + //! TODO + } + + // reader.readNext(); + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name().toString() == name) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Scaling(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_UNUSED(axis); + Q_ASSERT(reader.name() == QLatin1String("scaling")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("orientation")) { + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("scaling")) { + break; + } + } + + return true; +} + +/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ +bool ChartPrivate::loadXmlAxisEG_AxShared_Title(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_ASSERT(reader.name() == QLatin1String("title")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("tx")) { + loadXmlAxisEG_AxShared_Title_Tx(reader, axis); + } else if (reader.name() == QLatin1String("overlay")) { + //! TODO: load overlay + loadXmlAxisEG_AxShared_Title_Overlay(reader, axis); + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("title")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Overlay(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_UNUSED(axis); + Q_ASSERT(reader.name() == QLatin1String("overlay")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("overlay")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Tx(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_ASSERT(reader.name() == QLatin1String("tx")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("rich")) { + loadXmlAxisEG_AxShared_Title_Tx_Rich(reader, axis); + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("tx")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Tx_Rich(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_ASSERT(reader.name() == QLatin1String("rich")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("p")) { + loadXmlAxisEG_AxShared_Title_Tx_Rich_P(reader, axis); + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("rich")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Tx_Rich_P(QXmlStreamReader &reader, XlsxAxis *axis) +{ + Q_ASSERT(reader.name() == QLatin1String("p")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("r")) { + loadXmlAxisEG_AxShared_Title_Tx_Rich_P_R(reader, axis); + } else if (reader.name() == QLatin1String("pPr")) { + loadXmlAxisEG_AxShared_Title_Tx_Rich_P_pPr(reader, axis); + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("p")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Tx_Rich_P_pPr(QXmlStreamReader &reader, + XlsxAxis *axis) +{ + Q_UNUSED(axis); + Q_ASSERT(reader.name() == QLatin1String("pPr")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("defRPr")) { + reader.readElementText(); + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("pPr")) { + break; + } + } + + return true; +} + +bool ChartPrivate::loadXmlAxisEG_AxShared_Title_Tx_Rich_P_R(QXmlStreamReader &reader, + XlsxAxis *axis) +{ + Q_ASSERT(reader.name() == QLatin1String("r")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("t")) { + QString strAxisName = reader.readElementText(); + XlsxAxis::AxisPos axisPos = axis->axisPos; + axis->axisNames[axisPos] = strAxisName; + } else { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("r")) { + break; + } + } + + return true; +} + +/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +void ChartPrivate::saveXmlAxis(QXmlStreamWriter &writer) const +{ + for (int i = 0; i < axisList.size(); ++i) { + XlsxAxis *axis = axisList[i].get(); + if (nullptr == axis) + continue; + + if (axis->type == XlsxAxis::T_Cat) { + saveXmlAxisCatAx(writer, axis); + } + if (axis->type == XlsxAxis::T_Val) { + saveXmlAxisValAx(writer, axis); + } + if (axis->type == XlsxAxis::T_Ser) { + saveXmlAxisSerAx(writer, axis); + } + if (axis->type == XlsxAxis::T_Date) { + saveXmlAxisDateAx(writer, axis); + } + } +} + +void ChartPrivate::saveXmlAxisCatAx(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + /* + + + + + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:catAx")); + + saveXmlAxisEG_AxShared(writer, axis); // EG_AxShared + + //! TODO: write element + // auto + // lblAlgn + // lblOffset + // tickLblSkip + // tickMarkSkip + // noMultiLvlLbl + // extLst + + writer.writeEndElement(); // c:catAx +} + +void ChartPrivate::saveXmlAxisDateAx(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + /* + + + + + + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:dateAx")); + + saveXmlAxisEG_AxShared(writer, axis); // EG_AxShared + + //! TODO: write element + // auto + // lblOffset + // baseTimeUnit + // majorUnit + // majorTimeUnit + // minorUnit + // minorTimeUnit + // extLst + + writer.writeEndElement(); // c:dateAx +} + +void ChartPrivate::saveXmlAxisSerAx(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + /* + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:serAx")); + + saveXmlAxisEG_AxShared(writer, axis); // EG_AxShared + + //! TODO: write element + // tickLblSkip + // tickMarkSkip + // extLst + + writer.writeEndElement(); // c:serAx +} + +void ChartPrivate::saveXmlAxisValAx(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + /* + + + + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:valAx")); + + saveXmlAxisEG_AxShared(writer, axis); // EG_AxShared + + //! TODO: write element + // crossBetween + // majorUnit + // minorUnit + // dispUnits + // extLst + + writer.writeEndElement(); // c:valAx +} + +void ChartPrivate::saveXmlAxisEG_AxShared(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + /* + + + (*) + (*) + + (*) + + + + (***********************) + (*) + + + + + + + */ + + writer.writeEmptyElement( + QStringLiteral("c:axId")); // 21.2.2.9. axId (Axis ID) (mandatory value) + writer.writeAttribute(QStringLiteral("val"), QString::number(axis->axisId)); + + writer.writeStartElement(QStringLiteral("c:scaling")); // CT_Scaling (mandatory value) + writer.writeEmptyElement(QStringLiteral("c:orientation")); // CT_Orientation + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("minMax")); // ST_Orientation + writer.writeEndElement(); // c:scaling + + writer.writeEmptyElement(QStringLiteral("c:axPos")); // axPos CT_AxPos (mandatory value) + QString pos = GetAxisPosString(axis->axisPos); + if (!pos.isEmpty()) { + writer.writeAttribute(QStringLiteral("val"), pos); // ST_AxPos + } + + if (majorGridlinesEnabled) { + writer.writeEmptyElement(QStringLiteral("c:majorGridlines")); + } + if (minorGridlinesEnabled) { + writer.writeEmptyElement(QStringLiteral("c:minorGridlines")); + } + + saveXmlAxisEG_AxShared_Title(writer, axis); // "c:title" CT_Title + + writer.writeEmptyElement(QStringLiteral("c:crossAx")); // crossAx (mandatory value) + writer.writeAttribute(QStringLiteral("val"), QString::number(axis->crossAx)); +} + +void ChartPrivate::saveXmlAxisEG_AxShared_Title(QXmlStreamWriter &writer, XlsxAxis *axis) const +{ + // CT_Title + + /* + + + + + + + + + + + */ + /* + + + + + + + + + */ + /* + + + + + + + + */ + /* + + + + + + + + */ + + writer.writeStartElement(QStringLiteral("c:title")); + + // CT_Tx {{ + writer.writeStartElement(QStringLiteral("c:tx")); + + writer.writeStartElement(QStringLiteral("c:rich")); // CT_TextBody + + writer.writeEmptyElement(QStringLiteral("a:bodyPr")); // CT_TextBodyProperties + + writer.writeEmptyElement(QStringLiteral("a:lstStyle")); // CT_TextListStyle + + writer.writeStartElement(QStringLiteral("a:p")); + + writer.writeStartElement(QStringLiteral("a:pPr")); + writer.writeAttribute(QStringLiteral("lvl"), QString::number(0)); + + writer.writeStartElement(QStringLiteral("a:defRPr")); + writer.writeAttribute(QStringLiteral("b"), QString::number(0)); + writer.writeEndElement(); // a:defRPr + writer.writeEndElement(); // a:pPr + + writer.writeStartElement(QStringLiteral("a:r")); + QString strAxisName = GetAxisName(axis); + writer.writeTextElement(QStringLiteral("a:t"), strAxisName); + writer.writeEndElement(); // a:r + + writer.writeEndElement(); // a:p + + writer.writeEndElement(); // c:rich + + writer.writeEndElement(); // c:tx + // CT_Tx }} + + writer.writeStartElement(QStringLiteral("c:overlay")); + writer.writeAttribute(QStringLiteral("val"), QString::number(0)); // CT_Boolean + writer.writeEndElement(); // c:overlay + + writer.writeEndElement(); // c:title +} + +QString ChartPrivate::GetAxisPosString(XlsxAxis::AxisPos axisPos) const +{ + QString pos; + switch (axisPos) { + case XlsxAxis::Top: + pos = QStringLiteral("t"); + break; + case XlsxAxis::Bottom: + pos = QStringLiteral("b"); + break; + case XlsxAxis::Left: + pos = QStringLiteral("l"); + break; + case XlsxAxis::Right: + pos = QStringLiteral("r"); + break; + default: + break; // ?? + } + + return pos; +} + +QString ChartPrivate::GetAxisName(XlsxAxis *axis) const +{ + QString strAxisName; + if (nullptr == axis) + return strAxisName; + + QString pos = GetAxisPosString(axis->axisPos); // l, t, r, b + if (pos.isEmpty()) + return strAxisName; + + strAxisName = axis->axisNames[axis->axisPos]; + return strAxisName; +} + +/// +/// \brief ChartPrivate::readSubTree +/// \param reader +/// \return +/// +QString ChartPrivate::readSubTree(QXmlStreamReader &reader) +{ + QString treeString; + QString prefix; + const auto &treeName = reader.name(); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + prefix = reader.prefix().toString(); + + treeString += QLatin1String("<") + reader.qualifiedName().toString(); + + const QXmlStreamAttributes attributes = reader.attributes(); + for (const QXmlStreamAttribute &attr : attributes) { + treeString += QLatin1String(" ") + attr.name().toString() + QLatin1String("=\"") + + attr.value().toString() + QLatin1String("\""); + } + treeString += QStringLiteral(">"); + } else if (reader.tokenType() == QXmlStreamReader::EndElement) { + if (reader.name() == treeName) { + break; + } + treeString += + QLatin1String(""); + } + } + + return treeString; +} + +/// +/// \brief ChartPrivate::loadXmlChartLegend +/// \param reader +/// \return +/// +bool ChartPrivate::loadXmlChartLegend(QXmlStreamReader &reader) +{ + + Q_ASSERT(reader.name() == QLatin1String("legend")); + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("legend"))) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("legendPos")) // c:legendPos + { + QString pos = reader.attributes().value(QLatin1String("val")).toString(); + if (pos.compare(QLatin1String("r"), Qt::CaseInsensitive) == 0) { + // legendPos = Chart::ChartAxisPos::Right; + legendPos = Chart::Right; + } else if (pos.compare(QLatin1String("l"), Qt::CaseInsensitive) == 0) { + // legendPos = Chart::ChartAxisPos::Left; + legendPos = Chart::Left; + } else if (pos.compare(QLatin1String("t"), Qt::CaseInsensitive) == 0) { + // legendPos = Chart::ChartAxisPos::Top; + legendPos = Chart::Top; + } else if (pos.compare(QLatin1String("b"), Qt::CaseInsensitive) == 0) { + // legendPos = Chart::ChartAxisPos::Bottom; + legendPos = Chart::Bottom; + } else { + // legendPos = Chart::ChartAxisPos::None; + legendPos = Chart::None; + } + } else if (reader.name() == QLatin1String("overlay")) // c:legendPos + { + QString pos = reader.attributes().value(QLatin1String("val")).toString(); + if (pos.compare(QLatin1String("1"), Qt::CaseInsensitive) == 0) { + legendOverlay = true; + } else { + legendOverlay = false; + } + } + } + } + + return false; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxchartsheet.cpp b/QXlsx/source/xlsxchartsheet.cpp new file mode 100644 index 0000000..c344f07 --- /dev/null +++ b/QXlsx/source/xlsxchartsheet.cpp @@ -0,0 +1,148 @@ +// xlsxchartsheet.cpp + +#include "xlsxchartsheet.h" + +#include "xlsxchart.h" +#include "xlsxchartsheet_p.h" +#include "xlsxdrawing_p.h" +#include "xlsxdrawinganchor_p.h" +#include "xlsxutility_p.h" +#include "xlsxworkbook.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ChartsheetPrivate::ChartsheetPrivate(Chartsheet *p, Chartsheet::CreateFlag flag) + : AbstractSheetPrivate(p, flag) + , chart(nullptr) +{ +} + +ChartsheetPrivate::~ChartsheetPrivate() +{ +} + +/*! + \class Chartsheet + \inmodule QtXlsx + \brief Represent one chartsheet in the workbook. +*/ + +/*! + * \internal + */ +Chartsheet::Chartsheet(const QString &name, int id, Workbook *workbook, CreateFlag flag) + : AbstractSheet(name, id, workbook, new ChartsheetPrivate(this, flag)) +{ + setSheetType(ST_ChartSheet); + + if (flag == Chartsheet::F_NewFromScratch) { + d_func()->drawing = std::make_shared(this, flag); + + DrawingAbsoluteAnchor *anchor = + new DrawingAbsoluteAnchor(drawing(), DrawingAnchor::Picture); + + anchor->pos = QPoint(0, 0); + anchor->ext = QSize(9293679, 6068786); + + QSharedPointer chart = QSharedPointer(new Chart(this, flag)); + chart->setChartType(Chart::CT_BarChart); + anchor->setObjectGraphicFrame(chart); + + d_func()->chart = chart.data(); + } +} + +/*! + * \internal + * + * Make a copy of this sheet. + */ + +Chartsheet *Chartsheet::copy(const QString &distName, int distId) const +{ + //: Todo + Q_UNUSED(distName) + Q_UNUSED(distId) + return nullptr; +} + +/*! + * Destroys this workssheet. + */ +Chartsheet::~Chartsheet() +{ +} + +/*! + * Returns the chart object of the sheet. + */ +Chart *Chartsheet::chart() +{ + Q_D(Chartsheet); + + return d->chart; +} + +void Chartsheet::saveToXmlFile(QIODevice *device) const +{ + Q_D(const Chartsheet); + d->relationships->clear(); + + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeDefaultNamespace( + QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); + writer.writeNamespace( + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"), + QStringLiteral("r")); + writer.writeStartElement(QStringLiteral("chartsheet")); + + writer.writeStartElement(QStringLiteral("sheetViews")); + writer.writeEmptyElement(QStringLiteral("sheetView")); + writer.writeAttribute(QStringLiteral("workbookViewId"), QString::number(0)); + writer.writeAttribute(QStringLiteral("zoomToFit"), QStringLiteral("1")); + writer.writeEndElement(); // sheetViews + + int idx = d->workbook->drawings().indexOf(d->drawing.get()); + d->relationships->addWorksheetRelationship( + QStringLiteral("/drawing"), QStringLiteral("../drawings/drawing%1.xml").arg(idx + 1)); + + writer.writeEmptyElement(QStringLiteral("drawing")); + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(d->relationships->count())); + + writer.writeEndElement(); // chartsheet + writer.writeEndDocument(); +} + +bool Chartsheet::loadFromXmlFile(QIODevice *device) +{ + Q_D(Chartsheet); + + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("drawing")) { + QString rId = reader.attributes().value(QStringLiteral("r:id")).toString(); + QString name = d->relationships->getRelationshipById(rId).target; + + const auto parts = splitPath(filePath()); + QString path = QDir::cleanPath(parts.first() + QLatin1String("/") + name); + + d->drawing = std::make_shared(this, F_LoadFromExists); + d->drawing->setFilePath(path); + } + } + } + + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcolor.cpp b/QXlsx/source/xlsxcolor.cpp new file mode 100644 index 0000000..1fee05d --- /dev/null +++ b/QXlsx/source/xlsxcolor.cpp @@ -0,0 +1,192 @@ +// xlsxcolor.cpp + +#include "xlsxcolor_p.h" +#include "xlsxstyles_p.h" +#include "xlsxutility_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +XlsxColor::XlsxColor(const QColor &color) +{ + if (color.isValid()) + val.setValue(color); +} + +XlsxColor::XlsxColor(const QString &theme, const QString &tint) + : val(QStringList() << theme << tint) +{ +} + +XlsxColor::XlsxColor(int index) + : val(index) +{ +} + +bool XlsxColor::isRgbColor() const +{ + return val.userType() == qMetaTypeId() && val.value().isValid(); +} + +bool XlsxColor::isIndexedColor() const +{ + return val.userType() == QMetaType::Int; +} + +bool XlsxColor::isThemeColor() const +{ + return val.userType() == QMetaType::QStringList; +} + +bool XlsxColor::isInvalid() const +{ + return !val.isValid(); +} + +QColor XlsxColor::rgbColor() const +{ + return isRgbColor() ? val.value() : QColor(); +} + +int XlsxColor::indexedColor() const +{ + return isIndexedColor() ? val.toInt() : -1; +} + +QStringList XlsxColor::themeColor() const +{ + return isThemeColor() ? val.toStringList() : QStringList(); +} + +bool XlsxColor::saveToXml(QXmlStreamWriter &writer, const QString &node) const +{ + if (!node.isEmpty()) + writer.writeEmptyElement(node); // color, bgColor, fgColor + else + writer.writeEmptyElement(QStringLiteral("color")); + + if (val.userType() == qMetaTypeId()) { + writer.writeAttribute(QStringLiteral("rgb"), XlsxColor::toARGBString(val.value())); + } else if (val.userType() == QMetaType::QStringList) { + QStringList themes = val.toStringList(); + writer.writeAttribute(QStringLiteral("theme"), themes[0]); + if (!themes[1].isEmpty()) + writer.writeAttribute(QStringLiteral("tint"), themes[1]); + } else if (val.userType() == QMetaType::Int) { + writer.writeAttribute(QStringLiteral("indexed"), val.toString()); + } else { + writer.writeAttribute(QStringLiteral("auto"), QStringLiteral("1")); + } + + return true; +} + +bool XlsxColor::loadFromXml(QXmlStreamReader &reader) +{ + const auto &attributes = reader.attributes(); + + if (attributes.hasAttribute(QLatin1String("rgb"))) { + const auto &colorString = attributes.value(QLatin1String("rgb")).toString(); + val.setValue(fromARGBString(colorString)); + } else if (attributes.hasAttribute(QLatin1String("indexed"))) { + int index = attributes.value(QLatin1String("indexed")).toInt(); + val.setValue(index); + } else if (attributes.hasAttribute(QLatin1String("theme"))) { + const auto &theme = attributes.value(QLatin1String("theme")).toString(); + const auto &tint = attributes.value(QLatin1String("tint")).toString(); + val.setValue(QStringList() << theme << tint); + } + return true; +} + +XlsxColor::operator QVariant() const +{ + const auto &cref +#if QT_VERSION >= 0x060000 // Qt 6.0 or over + = QMetaType::fromType(); +#else + = qMetaTypeId(); +#endif + return QVariant(cref, this); +} + +QColor XlsxColor::fromARGBString(const QString &c) +{ + QColor color; + if (c.startsWith(u'#')) { + color.setNamedColor(c); + } else { + color.setNamedColor(QLatin1Char('#') + c); + } + return color; +} + +QString XlsxColor::toARGBString(const QColor &c) +{ + return QString::asprintf("%02X%02X%02X%02X", c.alpha(), c.red(), c.green(), c.blue()); +} + +#if !defined(QT_NO_DATASTREAM) +QDataStream &operator<<(QDataStream &s, const XlsxColor &color) +{ + if (color.isInvalid()) + s << 0; + else if (color.isRgbColor()) + s << 1 << color.rgbColor(); + else if (color.isIndexedColor()) + s << 2 << color.indexedColor(); + else if (color.isThemeColor()) + s << 3 << color.themeColor(); + else + s << 4; + + return s; +} + +QDataStream &operator>>(QDataStream &s, XlsxColor &color) +{ + int marker(4); + s >> marker; + if (marker == 0) { + color = XlsxColor(); + } else if (marker == 1) { + QColor c; + s >> c; + color = XlsxColor(c); + } else if (marker == 2) { + int indexed; + s >> indexed; + color = XlsxColor(indexed); + } else if (marker == 3) { + QStringList list; + s >> list; + color = XlsxColor(list[0], list[1]); + } + + return s; +} + +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const XlsxColor &c) +{ + if (c.isInvalid()) + dbg.nospace() << "XlsxColor(invalid)"; + else if (c.isRgbColor()) + dbg.nospace() << c.rgbColor(); + else if (c.isIndexedColor()) + dbg.nospace() << "XlsxColor(indexed," << c.indexedColor() << ")"; + else if (c.isThemeColor()) + dbg.nospace() << "XlsxColor(theme," << c.themeColor().join(QLatin1Char(':')) << ')'; + + return dbg.space(); +} + +#endif + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxconditionalformatting.cpp b/QXlsx/source/xlsxconditionalformatting.cpp new file mode 100644 index 0000000..932fce8 --- /dev/null +++ b/QXlsx/source/xlsxconditionalformatting.cpp @@ -0,0 +1,810 @@ +// xlsxconditionalformatting.cpp + +#include "xlsxconditionalformatting.h" + +#include "xlsxcellrange.h" +#include "xlsxconditionalformatting_p.h" +#include "xlsxstyles_p.h" +#include "xlsxworksheet.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ConditionalFormattingPrivate::ConditionalFormattingPrivate() +{ +} + +ConditionalFormattingPrivate::ConditionalFormattingPrivate( + const ConditionalFormattingPrivate &other) + : QSharedData(other) +{ +} + +ConditionalFormattingPrivate::~ConditionalFormattingPrivate() +{ +} + +void ConditionalFormattingPrivate::writeCfVo(QXmlStreamWriter &writer, + const XlsxCfVoData &cfvo) const +{ + writer.writeEmptyElement(QStringLiteral("cfvo")); + QString type; + switch (cfvo.type) { + case ConditionalFormatting::VOT_Formula: + type = QStringLiteral("formula"); + break; + case ConditionalFormatting::VOT_Max: + type = QStringLiteral("max"); + break; + case ConditionalFormatting::VOT_Min: + type = QStringLiteral("min"); + break; + case ConditionalFormatting::VOT_Num: + type = QStringLiteral("num"); + break; + case ConditionalFormatting::VOT_Percent: + type = QStringLiteral("percent"); + break; + case ConditionalFormatting::VOT_Percentile: + type = QStringLiteral("percentile"); + break; + default: + break; + } + writer.writeAttribute(QStringLiteral("type"), type); + writer.writeAttribute(QStringLiteral("val"), cfvo.value); + if (!cfvo.gte) + writer.writeAttribute(QStringLiteral("gte"), QStringLiteral("0")); +} + +/*! + * \class ConditionalFormatting + * \brief Conditional formatting for single cell or ranges + * \inmodule QtXlsx + * + * The conditional formatting can be applied to a single cell or ranges of cells. + */ + +/*! + \enum ConditionalFormatting::HighlightRuleType + + \value Highlight_LessThan + \value Highlight_LessThanOrEqual + \value Highlight_Equal + \value Highlight_NotEqual + \value Highlight_GreaterThanOrEqual + \value Highlight_GreaterThan + \value Highlight_Between + \value Highlight_NotBetween + + \value Highlight_ContainsText + \value Highlight_NotContainsText + \value Highlight_BeginsWith + \value Highlight_EndsWith + + \value Highlight_TimePeriod + + \value Highlight_Duplicate + \value Highlight_Unique + + \value Highlight_Blanks + \value Highlight_NoBlanks + \value Highlight_Errors + \value Highlight_NoErrors + + \value Highlight_Top + \value Highlight_TopPercent + \value Highlight_Bottom + \value Highlight_BottomPercent + + \value Highlight_AboveAverage + \value Highlight_AboveOrEqualAverage + \value Highlight_BelowAverage + \value Highlight_BelowOrEqualAverage + \value Highlight_AboveStdDev1 + \value Highlight_AboveStdDev2 + \value Highlight_AboveStdDev3 + \value Highlight_BelowStdDev1 + \value Highlight_BelowStdDev2 + \value Highlight_BelowStdDev3 + + \value Highlight_Expression +*/ + +/*! + \enum ConditionalFormatting::ValueObjectType + + \value VOT_Formula + \value VOT_Max + \value VOT_Min + \value VOT_Num + \value VOT_Percent + \value VOT_Percentile +*/ + +/*! + Construct a conditional formatting object +*/ +ConditionalFormatting::ConditionalFormatting() + : d(new ConditionalFormattingPrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +ConditionalFormatting::ConditionalFormatting(const ConditionalFormatting &other) + : d(other.d) +{ +} + +/*! + Assigns \a other to this conditional formatting and returns a reference to + this conditional formatting. + */ +ConditionalFormatting &ConditionalFormatting::operator=(const ConditionalFormatting &other) +{ + this->d = other.d; + return *this; +} + +/*! + * Destroy the object. + */ +ConditionalFormatting::~ConditionalFormatting() +{ +} + +/*! + * Add a highlight rule with the given \a type, \a formula1, \a formula2, + * \a format and \a stopIfTrue. + * Return false if failed. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, + const QString &formula1, + const QString &formula2, + const Format &format, + bool stopIfTrue) +{ + if (format.isEmpty()) + return false; + + bool skipFormula = false; + + auto cfRule = std::make_shared(); + if (type >= Highlight_LessThan && type <= Highlight_NotBetween) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("cellIs"); + QString op; + switch (type) { + case Highlight_Between: + op = QStringLiteral("between"); + break; + case Highlight_Equal: + op = QStringLiteral("equal"); + break; + case Highlight_GreaterThan: + op = QStringLiteral("greaterThan"); + break; + case Highlight_GreaterThanOrEqual: + op = QStringLiteral("greaterThanOrEqual"); + break; + case Highlight_LessThan: + op = QStringLiteral("lessThan"); + break; + case Highlight_LessThanOrEqual: + op = QStringLiteral("lessThanOrEqual"); + break; + case Highlight_NotBetween: + op = QStringLiteral("notBetween"); + break; + case Highlight_NotEqual: + op = QStringLiteral("notEqual"); + break; + default: + break; + } + cfRule->attrs[XlsxCfRuleData::A_operator] = op; + } else if (type >= Highlight_ContainsText && type <= Highlight_EndsWith) { + if (type == Highlight_ContainsText) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsText"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("containsText"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = + QStringLiteral("NOT(ISERROR(SEARCH(\"%1\",%2)))").arg(formula1); + } else if (type == Highlight_NotContainsText) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsText"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("notContains"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = + QStringLiteral("ISERROR(SEARCH(\"%2\",%1))").arg(formula1); + } else if (type == Highlight_BeginsWith) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("beginsWith"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("beginsWith"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = + QStringLiteral("LEFT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1); + } else { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("endsWith"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("endsWith"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = + QStringLiteral("RIGHT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1); + } + cfRule->attrs[XlsxCfRuleData::A_text] = formula1; + skipFormula = true; + } else if (type == Highlight_TimePeriod) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("timePeriod"); + //: Todo + return false; + } else if (type == Highlight_Duplicate) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("duplicateValues"); + } else if (type == Highlight_Unique) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("uniqueValues"); + } else if (type == Highlight_Errors) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsErrors"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("ISERROR(%1)"); + skipFormula = true; + } else if (type == Highlight_NoErrors) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsErrors"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("NOT(ISERROR(%1))"); + skipFormula = true; + } else if (type == Highlight_Blanks) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsBlanks"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))=0"); + skipFormula = true; + } else if (type == Highlight_NoBlanks) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsBlanks"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))>0"); + skipFormula = true; + } else if (type >= Highlight_Top && type <= Highlight_BottomPercent) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("top10"); + if (type == Highlight_Bottom || type == Highlight_BottomPercent) + cfRule->attrs[XlsxCfRuleData::A_bottom] = QStringLiteral("1"); + if (type == Highlight_TopPercent || type == Highlight_BottomPercent) + cfRule->attrs[XlsxCfRuleData::A_percent] = QStringLiteral("1"); + cfRule->attrs[XlsxCfRuleData::A_rank] = + !formula1.isEmpty() ? formula1 : QStringLiteral("10"); + skipFormula = true; + } else if (type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("aboveAverage"); + if (type >= Highlight_BelowAverage && type <= Highlight_BelowStdDev3) + cfRule->attrs[XlsxCfRuleData::A_aboveAverage] = QStringLiteral("0"); + if (type == Highlight_AboveOrEqualAverage || type == Highlight_BelowOrEqualAverage) + cfRule->attrs[XlsxCfRuleData::A_equalAverage] = QStringLiteral("1"); + if (type == Highlight_AboveStdDev1 || type == Highlight_BelowStdDev1) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("1"); + else if (type == Highlight_AboveStdDev2 || type == Highlight_BelowStdDev2) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("2"); + else if (type == Highlight_AboveStdDev3 || type == Highlight_BelowStdDev3) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("3"); + } else if (type == Highlight_Expression) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("expression"); + } else { + return false; + } + + cfRule->dxfFormat = format; + if (stopIfTrue) + cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true; + if (!skipFormula) { + if (!formula1.isEmpty()) + cfRule->attrs[XlsxCfRuleData::A_formula1] = + formula1.startsWith(QLatin1String("=")) ? formula1.mid(1) : formula1; + if (!formula2.isEmpty()) + cfRule->attrs[XlsxCfRuleData::A_formula2] = + formula2.startsWith(QLatin1String("=")) ? formula2.mid(1) : formula2; + } + d->cfRules.append(cfRule); + return true; +} + +/*! + * \overload + * + * Add a highlight rule with the given \a type \a format and \a stopIfTrue. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, + const Format &format, + bool stopIfTrue) +{ + if ((type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) || + (type >= Highlight_Duplicate && type <= Highlight_NoErrors)) { + return addHighlightCellsRule(type, QString(), QString(), format, stopIfTrue); + } + + return false; +} + +/*! + * \overload + * + * Add a highlight rule with the given \a type, \a formula, \a format and \a stopIfTrue. + * Return false if failed. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, + const QString &formula, + const Format &format, + bool stopIfTrue) +{ + if (type == Highlight_Between || type == Highlight_NotBetween) + return false; + + return addHighlightCellsRule(type, formula, QString(), format, stopIfTrue); +} + +/*! + * Add a dataBar rule with the given \a color, \a type1, \a val1 + * , \a type2, \a val2, \a showData and \a stopIfTrue. + * Return false if failed. + */ +bool ConditionalFormatting::addDataBarRule(const QColor &color, + ValueObjectType type1, + const QString &val1, + ValueObjectType type2, + const QString &val2, + bool showData, + bool stopIfTrue) +{ + auto cfRule = std::make_shared(); + + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("dataBar"); + cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(color); + if (stopIfTrue) + cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true; + if (!showData) + cfRule->attrs[XlsxCfRuleData::A_hideData] = true; + + XlsxCfVoData cfvo1(type1, val1); + XlsxCfVoData cfvo2(type2, val2); + cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1); + cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2); + + d->cfRules.append(cfRule); + return true; +} + +/*! + * \overload + * Add a dataBar rule with the given \a color, \a showData and \a stopIfTrue. + */ +bool ConditionalFormatting::addDataBarRule(const QColor &color, bool showData, bool stopIfTrue) +{ + return addDataBarRule( + color, VOT_Min, QStringLiteral("0"), VOT_Max, QStringLiteral("0"), showData, stopIfTrue); +} + +/*! + * Add a colorScale rule with the given \a minColor, \a maxColor and \a stopIfTrue. + * Return false if failed. + */ +bool ConditionalFormatting::add2ColorScaleRule(const QColor &minColor, + const QColor &maxColor, + bool stopIfTrue) +{ + ValueObjectType type1 = VOT_Min; + ValueObjectType type2 = VOT_Max; + QString val1 = QStringLiteral("0"); + QString val2 = QStringLiteral("0"); + + auto cfRule = std::make_shared(); + + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("colorScale"); + cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(minColor); + cfRule->attrs[XlsxCfRuleData::A_color2] = XlsxColor(maxColor); + if (stopIfTrue) + cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true; + + XlsxCfVoData cfvo1(type1, val1); + XlsxCfVoData cfvo2(type2, val2); + cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1); + cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2); + + d->cfRules.append(cfRule); + return true; +} + +/*! + * Add a colorScale rule with the given \a minColor, \a midColor, \a maxColor and \a stopIfTrue. + * Return false if failed. + */ +bool ConditionalFormatting::add3ColorScaleRule(const QColor &minColor, + const QColor &midColor, + const QColor &maxColor, + bool stopIfTrue) +{ + ValueObjectType type1 = VOT_Min; + ValueObjectType type2 = VOT_Percent; + ValueObjectType type3 = VOT_Max; + QString val1 = QStringLiteral("0"); + QString val2 = QStringLiteral("50"); + QString val3 = QStringLiteral("0"); + + auto cfRule = std::make_shared(); + + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("colorScale"); + cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(minColor); + cfRule->attrs[XlsxCfRuleData::A_color2] = XlsxColor(midColor); + cfRule->attrs[XlsxCfRuleData::A_color3] = XlsxColor(maxColor); + + if (stopIfTrue) + cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true; + + XlsxCfVoData cfvo1(type1, val1); + XlsxCfVoData cfvo2(type2, val2); + XlsxCfVoData cfvo3(type3, val3); + cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1); + cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2); + cfRule->attrs[XlsxCfRuleData::A_cfvo3] = QVariant::fromValue(cfvo3); + + d->cfRules.append(cfRule); + return true; +} + +/*! + Returns the ranges on which the validation will be applied. + */ +QList ConditionalFormatting::ranges() const +{ + return d->ranges; +} + +/*! + Add the \a cell on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addCell(const CellReference &cell) +{ + d->ranges.append(CellRange(cell, cell)); +} + +/*! + \overload + Add the cell(\a row, \a col) on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addCell(int row, int col) +{ + d->ranges.append(CellRange(row, col, row, col)); +} + +/*! + \overload + Add the range(\a firstRow, \a firstCol, \a lastRow, \a lastCol) on + which the conditional formatting will apply to. + */ +void ConditionalFormatting::addRange(int firstRow, int firstCol, int lastRow, int lastCol) +{ + d->ranges.append(CellRange(firstRow, firstCol, lastRow, lastCol)); +} + +/*! + Add the \a range on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addRange(const CellRange &range) +{ + d->ranges.append(range); +} + +bool ConditionalFormattingPrivate::readCfRule(QXmlStreamReader &reader, + XlsxCfRuleData *rule, + Styles *styles) +{ + Q_ASSERT(reader.name() == QLatin1String("cfRule")); + QXmlStreamAttributes attrs = reader.attributes(); + if (attrs.hasAttribute(QLatin1String("type"))) + rule->attrs[XlsxCfRuleData::A_type] = attrs.value(QLatin1String("type")).toString(); + if (attrs.hasAttribute(QLatin1String("dxfId"))) { + int id = attrs.value(QLatin1String("dxfId")).toInt(); + if (styles) + rule->dxfFormat = styles->dxfFormat(id); + else + rule->dxfFormat.setDxfIndex(id); + } + rule->priority = attrs.value(QLatin1String("priority")).toInt(); + if (attrs.value(QLatin1String("stopIfTrue")) == QLatin1String("1")) { + // default is false + rule->attrs[XlsxCfRuleData::A_stopIfTrue] = QLatin1String("1"); + } + if (attrs.value(QLatin1String("aboveAverage")) == QLatin1String("0")) { + // default is true + rule->attrs[XlsxCfRuleData::A_aboveAverage] = QLatin1String("0"); + } + if (attrs.value(QLatin1String("percent")) == QLatin1String("1")) { + // default is false + rule->attrs[XlsxCfRuleData::A_percent] = QLatin1String("1"); + } + if (attrs.value(QLatin1String("bottom")) == QLatin1String("1")) { + // default is false + rule->attrs[XlsxCfRuleData::A_bottom] = QLatin1String("1"); + } + if (attrs.hasAttribute(QLatin1String("operator"))) + rule->attrs[XlsxCfRuleData::A_operator] = attrs.value(QLatin1String("operator")).toString(); + + if (attrs.hasAttribute(QLatin1String("text"))) + rule->attrs[XlsxCfRuleData::A_text] = attrs.value(QLatin1String("text")).toString(); + + if (attrs.hasAttribute(QLatin1String("timePeriod"))) + rule->attrs[XlsxCfRuleData::A_timePeriod] = + attrs.value(QLatin1String("timePeriod")).toString(); + + if (attrs.hasAttribute(QLatin1String("rank"))) + rule->attrs[XlsxCfRuleData::A_rank] = attrs.value(QLatin1String("rank")).toString(); + + if (attrs.hasAttribute(QLatin1String("stdDev"))) + rule->attrs[XlsxCfRuleData::A_stdDev] = attrs.value(QLatin1String("stdDev")).toString(); + + if (attrs.value(QLatin1String("equalAverage")) == QLatin1String("1")) { + // default is false + rule->attrs[XlsxCfRuleData::A_equalAverage] = QLatin1String("1"); + } + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("formula")) { + const QString f = reader.readElementText(); + if (!rule->attrs.contains(XlsxCfRuleData::A_formula1)) + rule->attrs[XlsxCfRuleData::A_formula1] = f; + else if (!rule->attrs.contains(XlsxCfRuleData::A_formula2)) + rule->attrs[XlsxCfRuleData::A_formula2] = f; + else if (!rule->attrs.contains(XlsxCfRuleData::A_formula3)) + rule->attrs[XlsxCfRuleData::A_formula3] = f; + } else if (reader.name() == QLatin1String("dataBar")) { + readCfDataBar(reader, rule); + } else if (reader.name() == QLatin1String("colorScale")) { + readCfColorScale(reader, rule); + } + } + if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QStringLiteral("conditionalFormatting")) { + break; + } + } + return true; +} + +bool ConditionalFormattingPrivate::readCfDataBar(QXmlStreamReader &reader, XlsxCfRuleData *rule) +{ + Q_ASSERT(reader.name() == QLatin1String("dataBar")); + QXmlStreamAttributes attrs = reader.attributes(); + if (attrs.value(QLatin1String("showValue")) == QLatin1String("0")) + rule->attrs[XlsxCfRuleData::A_hideData] = QStringLiteral("1"); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("cfvo")) { + XlsxCfVoData data; + readCfVo(reader, data); + if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo1)) + rule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(data); + else + rule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(data); + } else if (reader.name() == QLatin1String("color")) { + XlsxColor color; + color.loadFromXml(reader); + rule->attrs[XlsxCfRuleData::A_color1] = color; + } + } + if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QStringLiteral("dataBar")) { + break; + } + } + + return true; +} + +bool ConditionalFormattingPrivate::readCfColorScale(QXmlStreamReader &reader, XlsxCfRuleData *rule) +{ + Q_ASSERT(reader.name() == QLatin1String("colorScale")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("cfvo")) { + XlsxCfVoData data; + readCfVo(reader, data); + if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo1)) + rule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(data); + else if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo2)) + rule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(data); + else + rule->attrs[XlsxCfRuleData::A_cfvo3] = QVariant::fromValue(data); + } else if (reader.name() == QLatin1String("color")) { + XlsxColor color; + color.loadFromXml(reader); + if (!rule->attrs.contains(XlsxCfRuleData::A_color1)) + rule->attrs[XlsxCfRuleData::A_color1] = color; + else if (!rule->attrs.contains(XlsxCfRuleData::A_color2)) + rule->attrs[XlsxCfRuleData::A_color2] = color; + else + rule->attrs[XlsxCfRuleData::A_color3] = color; + } + } + if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QStringLiteral("colorScale")) { + break; + } + } + + return true; +} + +bool ConditionalFormattingPrivate::readCfVo(QXmlStreamReader &reader, XlsxCfVoData &cfvo) +{ + Q_ASSERT(reader.name() == QStringLiteral("cfvo")); + + QXmlStreamAttributes attrs = reader.attributes(); + + QString type = attrs.value(QLatin1String("type")).toString(); + ConditionalFormatting::ValueObjectType t; + if (type == QLatin1String("formula")) + t = ConditionalFormatting::VOT_Formula; + else if (type == QLatin1String("max")) + t = ConditionalFormatting::VOT_Max; + else if (type == QLatin1String("min")) + t = ConditionalFormatting::VOT_Min; + else if (type == QLatin1String("num")) + t = ConditionalFormatting::VOT_Num; + else if (type == QLatin1String("percent")) + t = ConditionalFormatting::VOT_Percent; + else // if (type == QLatin1String("percentile")) + t = ConditionalFormatting::VOT_Percentile; + + cfvo.type = t; + cfvo.value = attrs.value(QLatin1String("val")).toString(); + if (attrs.value(QLatin1String("gte")) == QLatin1String("0")) { + // default is true + cfvo.gte = false; + } + return true; +} + +bool ConditionalFormatting::loadFromXml(QXmlStreamReader &reader, Styles *styles) +{ + Q_ASSERT(reader.name() == QStringLiteral("conditionalFormatting")); + + d->ranges.clear(); + d->cfRules.clear(); + QXmlStreamAttributes attrs = reader.attributes(); + const QString sqref = attrs.value(QLatin1String("sqref")).toString(); + const auto sqrefParts = sqref.split(QLatin1Char(' ')); + for (const QString &range : sqrefParts) { + this->addRange(range); + } + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("cfRule")) { + auto cfRule = std::make_shared(); + d->readCfRule(reader, cfRule.get(), styles); + d->cfRules.append(cfRule); + } + } + if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QStringLiteral("conditionalFormatting")) { + break; + } + } + + return true; +} + +bool ConditionalFormatting::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("conditionalFormatting")); + QStringList sqref; + const auto rangeList = ranges(); + for (const CellRange &range : rangeList) { + sqref.append(range.toString()); + } + writer.writeAttribute(QStringLiteral("sqref"), sqref.join(QLatin1String(" "))); + + for (int i = 0; i < d->cfRules.size(); ++i) { + const std::shared_ptr &rule = d->cfRules[i]; + writer.writeStartElement(QStringLiteral("cfRule")); + writer.writeAttribute(QStringLiteral("type"), + rule->attrs[XlsxCfRuleData::A_type].toString()); + if (rule->dxfFormat.dxfIndexValid()) + writer.writeAttribute(QStringLiteral("dxfId"), + QString::number(rule->dxfFormat.dxfIndex())); + writer.writeAttribute(QStringLiteral("priority"), QString::number(rule->priority)); + + auto it = rule->attrs.constFind(XlsxCfRuleData::A_stopIfTrue); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("stopIfTrue"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_aboveAverage); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("aboveAverage"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_percent); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("percent"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_bottom); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("bottom"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_operator); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("operator"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_text); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("text"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_timePeriod); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("timePeriod"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_rank); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("rank"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_stdDev); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("stdDev"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_equalAverage); + if (it != rule->attrs.constEnd()) + writer.writeAttribute(QStringLiteral("equalAverage"), it.value().toString()); + + if (rule->attrs[XlsxCfRuleData::A_type] == QLatin1String("dataBar")) { + writer.writeStartElement(QStringLiteral("dataBar")); + if (rule->attrs.contains(XlsxCfRuleData::A_hideData)) + writer.writeAttribute(QStringLiteral("showValue"), QStringLiteral("0")); + d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo1].value()); + d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo2].value()); + rule->attrs[XlsxCfRuleData::A_color1].value().saveToXml(writer); + writer.writeEndElement(); // dataBar + } else if (rule->attrs[XlsxCfRuleData::A_type] == QLatin1String("colorScale")) { + writer.writeStartElement(QStringLiteral("colorScale")); + d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo1].value()); + d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo2].value()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_cfvo3); + if (it != rule->attrs.constEnd()) + d->writeCfVo(writer, it.value().value()); + + rule->attrs[XlsxCfRuleData::A_color1].value().saveToXml(writer); + rule->attrs[XlsxCfRuleData::A_color2].value().saveToXml(writer); + + it = rule->attrs.constFind(XlsxCfRuleData::A_color3); + if (it != rule->attrs.constEnd()) + it.value().value().saveToXml(writer); + + writer.writeEndElement(); // colorScale + } + + it = rule->attrs.constFind(XlsxCfRuleData::A_formula1_temp); + if (it != rule->attrs.constEnd()) { + const auto _ranges = ranges(); + const auto begin = _ranges.begin(); + if (begin != _ranges.end()) { + QString str = begin->toString(); + QString startCell = str.mid(0, str.indexOf(u':')); + writer.writeTextElement(QStringLiteral("formula"), + it.value().toString().arg(startCell)); + } + } else if ((it = rule->attrs.constFind(XlsxCfRuleData::A_formula1)) != + rule->attrs.constEnd()) { + writer.writeTextElement(QStringLiteral("formula"), it.value().toString()); + } + + it = rule->attrs.constFind(XlsxCfRuleData::A_formula2); + if (it != rule->attrs.constEnd()) + writer.writeTextElement(QStringLiteral("formula"), it.value().toString()); + + it = rule->attrs.constFind(XlsxCfRuleData::A_formula3); + if (it != rule->attrs.constEnd()) + writer.writeTextElement(QStringLiteral("formula"), it.value().toString()); + + writer.writeEndElement(); // cfRule + } + + writer.writeEndElement(); // conditionalFormatting + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxcontenttypes.cpp b/QXlsx/source/xlsxcontenttypes.cpp new file mode 100644 index 0000000..c6233dd --- /dev/null +++ b/QXlsx/source/xlsxcontenttypes.cpp @@ -0,0 +1,200 @@ +// xlsxcontenttypes.cpp + +#include "xlsxcontenttypes_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ContentTypes::ContentTypes(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ + m_package_prefix = QStringLiteral("application/vnd.openxmlformats-package."); + m_document_prefix = QStringLiteral("application/vnd.openxmlformats-officedocument."); + + m_defaults.insert(QStringLiteral("rels"), + m_package_prefix + QLatin1String("relationships+xml")); + m_defaults.insert(QStringLiteral("xml"), QStringLiteral("application/xml")); +} + +void ContentTypes::addDefault(const QString &key, const QString &value) +{ + m_defaults.insert(key, value); +} + +void ContentTypes::addOverride(const QString &key, const QString &value) +{ + m_overrides.insert(key, value); +} + +void ContentTypes::addDocPropApp() +{ + addOverride(QStringLiteral("/docProps/app.xml"), + m_document_prefix + QLatin1String("extended-properties+xml")); +} + +void ContentTypes::addDocPropCore() +{ + addOverride(QStringLiteral("/docProps/core.xml"), + m_package_prefix + QLatin1String("core-properties+xml")); +} + +void ContentTypes::addStyles() +{ + addOverride(QStringLiteral("/xl/styles.xml"), + m_document_prefix + QLatin1String("spreadsheetml.styles+xml")); +} + +void ContentTypes::addTheme() +{ + addOverride(QStringLiteral("/xl/theme/theme1.xml"), + m_document_prefix + QLatin1String("theme+xml")); +} + +void ContentTypes::addWorkbook() +{ + addOverride(QStringLiteral("/xl/workbook.xml"), + m_document_prefix + QLatin1String("spreadsheetml.sheet.main+xml")); +} + +void ContentTypes::addWorksheetName(const QString &name) +{ + addOverride(QStringLiteral("/xl/worksheets/%1.xml").arg(name), + m_document_prefix + QLatin1String("spreadsheetml.worksheet+xml")); +} + +void ContentTypes::addChartsheetName(const QString &name) +{ + addOverride(QStringLiteral("/xl/chartsheets/%1.xml").arg(name), + m_document_prefix + QLatin1String("spreadsheetml.chartsheet+xml")); +} + +void ContentTypes::addDrawingName(const QString &name) +{ + addOverride(QStringLiteral("/xl/drawings/%1.xml").arg(name), + m_document_prefix + QLatin1String("drawing+xml")); +} + +void ContentTypes::addChartName(const QString &name) +{ + addOverride(QStringLiteral("/xl/charts/%1.xml").arg(name), + m_document_prefix + QLatin1String("drawingml.chart+xml")); +} + +void ContentTypes::addCommentName(const QString &name) +{ + addOverride(QStringLiteral("/xl/%1.xml").arg(name), + m_document_prefix + QLatin1String("spreadsheetml.comments+xml")); +} + +void ContentTypes::addTableName(const QString &name) +{ + addOverride(QStringLiteral("/xl/tables/%1.xml").arg(name), + m_document_prefix + QLatin1String("spreadsheetml.table+xml")); +} + +void ContentTypes::addExternalLinkName(const QString &name) +{ + addOverride(QStringLiteral("/xl/externalLinks/%1.xml").arg(name), + m_document_prefix + QLatin1String("spreadsheetml.externalLink+xml")); +} + +void ContentTypes::addSharedString() +{ + addOverride(QStringLiteral("/xl/sharedStrings.xml"), + m_document_prefix + QLatin1String("spreadsheetml.sharedStrings+xml")); +} + +void ContentTypes::addVmlName() +{ + addOverride(QStringLiteral("vml"), m_document_prefix + QLatin1String("vmlDrawing")); +} + +void ContentTypes::addCalcChain() +{ + addOverride(QStringLiteral("/xl/calcChain.xml"), + m_document_prefix + QLatin1String("spreadsheetml.calcChain+xml")); +} + +void ContentTypes::addVbaProject() +{ + //: TODO + addOverride(QStringLiteral("bin"), QStringLiteral("application/vnd.ms-office.vbaProject")); +} + +void ContentTypes::clearOverrides() +{ + m_overrides.clear(); +} + +void ContentTypes::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("Types")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/package/2006/content-types")); + + { + QMapIterator it(m_defaults); + while (it.hasNext()) { + it.next(); + writer.writeStartElement(QStringLiteral("Default")); + writer.writeAttribute(QStringLiteral("Extension"), it.key()); + writer.writeAttribute(QStringLiteral("ContentType"), it.value()); + writer.writeEndElement(); // Default + } + } + + { + QMapIterator it(m_overrides); + while (it.hasNext()) { + it.next(); + writer.writeStartElement(QStringLiteral("Override")); + writer.writeAttribute(QStringLiteral("PartName"), it.key()); + writer.writeAttribute(QStringLiteral("ContentType"), it.value()); + writer.writeEndElement(); // Override + } + } + + writer.writeEndElement(); // Types + writer.writeEndDocument(); +} + +bool ContentTypes::loadFromXmlFile(QIODevice *device) +{ + m_defaults.clear(); + m_overrides.clear(); + + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("Default")) { + QXmlStreamAttributes attrs = reader.attributes(); + QString extension = attrs.value(QLatin1String("Extension")).toString(); + QString type = attrs.value(QLatin1String("ContentType")).toString(); + m_defaults.insert(extension, type); + } else if (reader.name() == QLatin1String("Override")) { + QXmlStreamAttributes attrs = reader.attributes(); + QString partName = attrs.value(QLatin1String("PartName")).toString(); + QString type = attrs.value(QLatin1String("ContentType")).toString(); + m_overrides.insert(partName, type); + } + } + + if (reader.hasError()) { + qDebug() << reader.errorString(); + } + } + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdatavalidation.cpp b/QXlsx/source/xlsxdatavalidation.cpp new file mode 100644 index 0000000..a7d4127 --- /dev/null +++ b/QXlsx/source/xlsxdatavalidation.cpp @@ -0,0 +1,546 @@ +// xlsxdatavalidation.cpp + +#include "xlsxdatavalidation.h" + +#include "xlsxcellrange.h" +#include "xlsxdatavalidation_p.h" +#include "xlsxworksheet.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +DataValidationPrivate::DataValidationPrivate() + : validationType(DataValidation::None) + , validationOperator(DataValidation::Between) + , errorStyle(DataValidation::Stop) + , allowBlank(false) + , isPromptMessageVisible(true) + , isErrorMessageVisible(true) +{ +} + +DataValidationPrivate::DataValidationPrivate(DataValidation::ValidationType type, + DataValidation::ValidationOperator op, + const QString &formula1, + const QString &formula2, + bool allowBlank) + : validationType(type) + , validationOperator(op) + , errorStyle(DataValidation::Stop) + , allowBlank(allowBlank) + , isPromptMessageVisible(true) + , isErrorMessageVisible(true) + , formula1(formula1) + , formula2(formula2) +{ +} + +DataValidationPrivate::DataValidationPrivate(const DataValidationPrivate &other) + : QSharedData(other) + , validationType(DataValidation::None) + , validationOperator(DataValidation::Between) + , errorStyle(DataValidation::Stop) + , allowBlank(false) + , isPromptMessageVisible(true) + , isErrorMessageVisible(true) +{ +} + +DataValidationPrivate::~DataValidationPrivate() +{ +} + +/*! + * \class DataValidation + * \brief Data validation for single cell or a range + * \inmodule QtXlsx + * + * The data validation can be applied to a single cell or a range of cells. + */ + +/*! + * \enum DataValidation::ValidationType + * + * The enum type defines the type of data that you wish to validate. + * + * \value None the type of data is unrestricted. This is the same as not applying a data validation. + * \value Whole restricts the cell to integer values. Means "Whole number"? + * \value Decimal restricts the cell to decimal values. + * \value List restricts the cell to a set of user specified values. + * \value Date restricts the cell to date values. + * \value Time restricts the cell to time values. + * \value TextLength restricts the cell data based on an integer string length. + * \value Custom restricts the cell based on an external Excel formula that returns a true/false + * value. + */ + +/*! + * \enum DataValidation::ValidationOperator + * + * The enum type defines the criteria by which the data in the + * cell is validated + * + * \value Between + * \value NotBetween + * \value Equal + * \value NotEqual + * \value LessThan + * \value LessThanOrEqual + * \value GreaterThan + * \value GreaterThanOrEqual + */ + +/*! + * \enum DataValidation::ErrorStyle + * + * The enum type defines the type of error dialog that + * is displayed. + * + * \value Stop + * \value Warning + * \value Information + */ + +/*! + * Construct a data validation object with the given \a type, \a op, \a formula1 + * \a formula2, and \a allowBlank. + */ +DataValidation::DataValidation(ValidationType type, + ValidationOperator op, + const QString &formula1, + const QString &formula2, + bool allowBlank) + : d(new DataValidationPrivate(type, op, formula1, formula2, allowBlank)) +{ +} + +/*! + Construct a data validation object +*/ +DataValidation::DataValidation() + : d(new DataValidationPrivate()) +{ +} + +/*! + Constructs a copy of \a other. +*/ +DataValidation::DataValidation(const DataValidation &other) + : d(other.d) +{ +} + +/*! + Assigns \a other to this validation and returns a reference to this validation. + */ +DataValidation &DataValidation::operator=(const DataValidation &other) +{ + this->d = other.d; + return *this; +} + +/*! + * Destroy the object. + */ +DataValidation::~DataValidation() +{ +} + +/*! + Returns the validation type. + */ +DataValidation::ValidationType DataValidation::validationType() const +{ + return d->validationType; +} + +/*! + Returns the validation operator. + */ +DataValidation::ValidationOperator DataValidation::validationOperator() const +{ + return d->validationOperator; +} + +/*! + Returns the validation error style. + */ +DataValidation::ErrorStyle DataValidation::errorStyle() const +{ + return d->errorStyle; +} + +/*! + Returns the formula1. + */ +QString DataValidation::formula1() const +{ + return d->formula1; +} + +/*! + Returns the formula2. + */ +QString DataValidation::formula2() const +{ + return d->formula2; +} + +/*! + Returns whether blank is allowed. + */ +bool DataValidation::allowBlank() const +{ + return d->allowBlank; +} + +/*! + Returns the error message. + */ +QString DataValidation::errorMessage() const +{ + return d->errorMessage; +} + +/*! + Returns the error message title. + */ +QString DataValidation::errorMessageTitle() const +{ + return d->errorMessageTitle; +} + +/*! + Returns the prompt message. + */ +QString DataValidation::promptMessage() const +{ + return d->promptMessage; +} + +/*! + Returns the prompt message title. + */ +QString DataValidation::promptMessageTitle() const +{ + return d->promptMessageTitle; +} + +/*! + Returns the whether prompt message is shown. + */ +bool DataValidation::isPromptMessageVisible() const +{ + return d->isPromptMessageVisible; +} + +/*! + Returns the whether error message is shown. + */ +bool DataValidation::isErrorMessageVisible() const +{ + return d->isErrorMessageVisible; +} + +/*! + Returns the ranges on which the validation will be applied. + */ +QList DataValidation::ranges() const +{ + return d->ranges; +} + +/*! + Sets the validation type to \a type. + */ +void DataValidation::setValidationType(DataValidation::ValidationType type) +{ + d->validationType = type; +} + +/*! + Sets the validation operator to \a op. + */ +void DataValidation::setValidationOperator(DataValidation::ValidationOperator op) +{ + d->validationOperator = op; +} + +/*! + Sets the error style to \a es. + */ +void DataValidation::setErrorStyle(DataValidation::ErrorStyle es) +{ + d->errorStyle = es; +} + +/*! + Sets the formula1 to \a formula. + */ +void DataValidation::setFormula1(const QString &formula) +{ + if (formula.startsWith(QLatin1Char('='))) + d->formula1 = formula.mid(1); + else + d->formula1 = formula; +} + +/*! + Sets the formulas to \a formula. + */ +void DataValidation::setFormula2(const QString &formula) +{ + if (formula.startsWith(QLatin1Char('='))) + d->formula2 = formula.mid(1); + else + d->formula2 = formula; +} + +/*! + Sets the error message to \a error with title \a title. + */ +void DataValidation::setErrorMessage(const QString &error, const QString &title) +{ + d->errorMessage = error; + d->errorMessageTitle = title; +} + +/*! + Sets the prompt message to \a prompt with title \a title. + */ +void DataValidation::setPromptMessage(const QString &prompt, const QString &title) +{ + d->promptMessage = prompt; + d->promptMessageTitle = title; +} + +/*! + Enable/disable blank allow based on \a enable. + */ +void DataValidation::setAllowBlank(bool enable) +{ + d->allowBlank = enable; +} + +/*! + Enable/disable prompt message visible based on \a visible. + */ +void DataValidation::setPromptMessageVisible(bool visible) +{ + d->isPromptMessageVisible = visible; +} + +/*! + Enable/disable error message visible based on \a visible. + */ +void DataValidation::setErrorMessageVisible(bool visible) +{ + d->isErrorMessageVisible = visible; +} + +/*! + Add the \a cell on which the DataValidation will apply to. + */ +void DataValidation::addCell(const CellReference &cell) +{ + d->ranges.append(CellRange(cell, cell)); +} + +/*! + \overload + Add the cell(\a row, \a col) on which the DataValidation will apply to. + */ +void DataValidation::addCell(int row, int col) +{ + d->ranges.append(CellRange(row, col, row, col)); +} + +/*! + \overload + Add the range(\a firstRow, \a firstCol, \a lastRow, \a lastCol) on + which the DataValidation will apply to. + */ +void DataValidation::addRange(int firstRow, int firstCol, int lastRow, int lastCol) +{ + d->ranges.append(CellRange(firstRow, firstCol, lastRow, lastCol)); +} + +/*! + Add the \a range on which the DataValidation will apply to. + */ +void DataValidation::addRange(const CellRange &range) +{ + d->ranges.append(range); +} + +/*! + * \internal + */ +bool DataValidation::saveToXml(QXmlStreamWriter &writer) const +{ + static const QMap typeMap = { + {DataValidation::None, QStringLiteral("none")}, + {DataValidation::Whole, QStringLiteral("whole")}, + {DataValidation::Decimal, QStringLiteral("decimal")}, + {DataValidation::List, QStringLiteral("list")}, + {DataValidation::Date, QStringLiteral("date")}, + {DataValidation::Time, QStringLiteral("time")}, + {DataValidation::TextLength, QStringLiteral("textLength")}, + {DataValidation::Custom, QStringLiteral("custom")}}; + + static const QMap opMap = { + {DataValidation::Between, QStringLiteral("between")}, + {DataValidation::NotBetween, QStringLiteral("notBetween")}, + {DataValidation::Equal, QStringLiteral("equal")}, + {DataValidation::NotEqual, QStringLiteral("notEqual")}, + {DataValidation::LessThan, QStringLiteral("lessThan")}, + {DataValidation::LessThanOrEqual, QStringLiteral("lessThanOrEqual")}, + {DataValidation::GreaterThan, QStringLiteral("greaterThan")}, + {DataValidation::GreaterThanOrEqual, QStringLiteral("greaterThanOrEqual")}}; + + static const QMap esMap = { + {DataValidation::Stop, QStringLiteral("stop")}, + {DataValidation::Warning, QStringLiteral("warning")}, + {DataValidation::Information, QStringLiteral("information")}}; + + writer.writeStartElement(QStringLiteral("dataValidation")); + if (validationType() != DataValidation::None) + writer.writeAttribute(QStringLiteral("type"), typeMap[validationType()]); + if (errorStyle() != DataValidation::Stop) + writer.writeAttribute(QStringLiteral("errorStyle"), esMap[errorStyle()]); + if (validationOperator() != DataValidation::Between) + writer.writeAttribute(QStringLiteral("operator"), opMap[validationOperator()]); + if (allowBlank()) + writer.writeAttribute(QStringLiteral("allowBlank"), QStringLiteral("1")); + // if (dropDownVisible()) + // writer.writeAttribute(QStringLiteral("showDropDown"), QStringLiteral("1")); + if (isPromptMessageVisible()) + writer.writeAttribute(QStringLiteral("showInputMessage"), QStringLiteral("1")); + if (isErrorMessageVisible()) + writer.writeAttribute(QStringLiteral("showErrorMessage"), QStringLiteral("1")); + if (!errorMessageTitle().isEmpty()) + writer.writeAttribute(QStringLiteral("errorTitle"), errorMessageTitle()); + if (!errorMessage().isEmpty()) + writer.writeAttribute(QStringLiteral("error"), errorMessage()); + if (!promptMessageTitle().isEmpty()) + writer.writeAttribute(QStringLiteral("promptTitle"), promptMessageTitle()); + if (!promptMessage().isEmpty()) + writer.writeAttribute(QStringLiteral("prompt"), promptMessage()); + + QStringList sqref; + const auto rangeList = ranges(); + for (const CellRange &range : rangeList) + sqref.append(range.toString()); + writer.writeAttribute(QStringLiteral("sqref"), sqref.join(QLatin1String(" "))); + + if (!formula1().isEmpty()) + writer.writeTextElement(QStringLiteral("formula1"), formula1()); + if (!formula2().isEmpty()) + writer.writeTextElement(QStringLiteral("formula2"), formula2()); + + writer.writeEndElement(); // dataValidation + + return true; +} + +/*! + * \internal + */ +DataValidation DataValidation::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("dataValidation")); + + static const QMap typeMap = { + {QStringLiteral("none"), DataValidation::None}, + {QStringLiteral("whole"), DataValidation::Whole}, + {QStringLiteral("decimal"), DataValidation::Decimal}, + {QStringLiteral("list"), DataValidation::List}, + {QStringLiteral("date"), DataValidation::Date}, + {QStringLiteral("time"), DataValidation::Time}, + {QStringLiteral("textLength"), DataValidation::TextLength}, + {QStringLiteral("custom"), DataValidation::Custom}}; + + static const QMap opMap = { + {QStringLiteral("between"), DataValidation::Between}, + {QStringLiteral("notBetween"), DataValidation::NotBetween}, + {QStringLiteral("equal"), DataValidation::Equal}, + {QStringLiteral("notEqual"), DataValidation::NotEqual}, + {QStringLiteral("lessThan"), DataValidation::LessThan}, + {QStringLiteral("lessThanOrEqual"), DataValidation::LessThanOrEqual}, + {QStringLiteral("greaterThan"), DataValidation::GreaterThan}, + {QStringLiteral("greaterThanOrEqual"), DataValidation::GreaterThanOrEqual}}; + + static const QMap esMap = { + {QStringLiteral("stop"), DataValidation::Stop}, + {QStringLiteral("warning"), DataValidation::Warning}, + {QStringLiteral("information"), DataValidation::Information}}; + + DataValidation validation; + QXmlStreamAttributes attrs = reader.attributes(); + + QString sqref = attrs.value(QLatin1String("sqref")).toString(); + const auto sqrefParts = sqref.split(QLatin1Char(' ')); + for (const QString &range : sqrefParts) + validation.addRange(range); + + if (attrs.hasAttribute(QLatin1String("type"))) { + QString t = attrs.value(QLatin1String("type")).toString(); + auto it = typeMap.constFind(t); + validation.setValidationType(it != typeMap.constEnd() ? it.value() : DataValidation::None); + } + if (attrs.hasAttribute(QLatin1String("errorStyle"))) { + QString es = attrs.value(QLatin1String("errorStyle")).toString(); + auto it = esMap.constFind(es); + validation.setErrorStyle(it != esMap.constEnd() ? it.value() : DataValidation::Stop); + } + if (attrs.hasAttribute(QLatin1String("operator"))) { + QString op = attrs.value(QLatin1String("operator")).toString(); + auto it = opMap.constFind(op); + validation.setValidationOperator(it != opMap.constEnd() ? it.value() + : DataValidation::Between); + } + if (attrs.hasAttribute(QLatin1String("allowBlank"))) { + validation.setAllowBlank(true); + } else { + validation.setAllowBlank(false); + } + if (attrs.hasAttribute(QLatin1String("showInputMessage"))) { + validation.setPromptMessageVisible(true); + } else { + validation.setPromptMessageVisible(false); + } + if (attrs.hasAttribute(QLatin1String("showErrorMessage"))) { + validation.setErrorMessageVisible(true); + } else { + validation.setErrorMessageVisible(false); + } + + QString et = attrs.value(QLatin1String("errorTitle")).toString(); + QString e = attrs.value(QLatin1String("error")).toString(); + if (!e.isEmpty() || !et.isEmpty()) + validation.setErrorMessage(e, et); + + QString pt = attrs.value(QLatin1String("promptTitle")).toString(); + QString p = attrs.value(QLatin1String("prompt")).toString(); + if (!p.isEmpty() || !pt.isEmpty()) + validation.setPromptMessage(p, pt); + + // find the end + while (!(reader.name() == QLatin1String("dataValidation") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("formula1")) { + validation.setFormula1(reader.readElementText()); + } else if (reader.name() == QLatin1String("formula2")) { + validation.setFormula2(reader.readElementText()); + } + } + } + return validation; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdatetype.cpp b/QXlsx/source/xlsxdatetype.cpp new file mode 100644 index 0000000..f757c65 --- /dev/null +++ b/QXlsx/source/xlsxdatetype.cpp @@ -0,0 +1,86 @@ +// xlsxdatetype.cpp + +#include "xlsxdatetype.h" + +#include "xlsxglobal.h" +#include "xlsxutility_p.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +DateType::DateType() +{ +} + +/* +DateType::DateType(bool is1904) +{ + isSet = false; +} + +DateType::DateType(double d, bool is1904) +{ + // TODO: check date + + // int iVaue = (int) d; + // double surplus = d - double(iVaue); + + dValue = d; + is1904Type = is1904; + isSet = true; +} + +DateType::DateType(QDateTime qdt, bool is1904) +{ + double ret = datetimeToNumber( qdt, is1904 ); + dValue = ret; + is1904Type = is1904; + isSet = true; +} + +DateType::DateType(QDate qd, bool is1904) +{ + + is1904Type = is1904; + isSet = true; +} + +DateType::DateType(QTime qt, bool is1904) +{ + double ret = timeToNumber( qt ); + dValue = ret; + is1904Type = is1904; + isSet = true; +} + +// enum currentDateType { DateAndTimeType, OnlyDateType, OnlyTimeType }; + +DateType::currentDateType DateType::getType() +{ + +} + +bool DateType::getValue(QDateTime* pQdt) +{ + +} + + +bool DateType::getValue(QDate* pQd) +{ + +} + +bool DateType::getValue(QTime* pQt) +{ + +} + +bool DateType::getValue(double* pD) +{ + +} +*/ + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdocpropsapp.cpp b/QXlsx/source/xlsxdocpropsapp.cpp new file mode 100644 index 0000000..9b82860 --- /dev/null +++ b/QXlsx/source/xlsxdocpropsapp.cpp @@ -0,0 +1,139 @@ +// xlsxdocpropsapp.cpp + +#include "xlsxdocpropsapp_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +DocPropsApp::DocPropsApp(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ +} + +void DocPropsApp::addPartTitle(const QString &title) +{ + m_titlesOfPartsList.append(title); +} + +void DocPropsApp::addHeadingPair(const QString &name, int value) +{ + m_headingPairsList.append({name, value}); +} + +bool DocPropsApp::setProperty(const QString &name, const QString &value) +{ + static const QStringList validKeys = {QStringLiteral("manager"), QStringLiteral("company")}; + + if (!validKeys.contains(name)) + return false; + + if (value.isEmpty()) + m_properties.remove(name); + else + m_properties[name] = value; + + return true; +} + +QString DocPropsApp::property(const QString &name) const +{ + auto it = m_properties.constFind(name); + if (it != m_properties.constEnd()) + return it.value(); + + return QString(); +} + +QStringList DocPropsApp::propertyNames() const +{ + return m_properties.keys(); +} + +void DocPropsApp::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + QString vt = + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("Properties")); + writer.writeDefaultNamespace(QStringLiteral( + "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties")); + writer.writeNamespace(vt, QStringLiteral("vt")); + writer.writeTextElement(QStringLiteral("Application"), QStringLiteral("Microsoft Excel")); + writer.writeTextElement(QStringLiteral("DocSecurity"), QStringLiteral("0")); + writer.writeTextElement(QStringLiteral("ScaleCrop"), QStringLiteral("false")); + + writer.writeStartElement(QStringLiteral("HeadingPairs")); + writer.writeStartElement(vt, QStringLiteral("vector")); + writer.writeAttribute(QStringLiteral("size"), QString::number(m_headingPairsList.size() * 2)); + writer.writeAttribute(QStringLiteral("baseType"), QStringLiteral("variant")); + + for (const auto &pair : m_headingPairsList) { + writer.writeStartElement(vt, QStringLiteral("variant")); + writer.writeTextElement(vt, QStringLiteral("lpstr"), pair.first); + writer.writeEndElement(); // vt:variant + writer.writeStartElement(vt, QStringLiteral("variant")); + writer.writeTextElement(vt, QStringLiteral("i4"), QString::number(pair.second)); + writer.writeEndElement(); // vt:variant + } + writer.writeEndElement(); // vt:vector + writer.writeEndElement(); // HeadingPairs + + writer.writeStartElement(QStringLiteral("TitlesOfParts")); + writer.writeStartElement(vt, QStringLiteral("vector")); + writer.writeAttribute(QStringLiteral("size"), QString::number(m_titlesOfPartsList.size())); + writer.writeAttribute(QStringLiteral("baseType"), QStringLiteral("lpstr")); + for (const QString &title : m_titlesOfPartsList) + writer.writeTextElement(vt, QStringLiteral("lpstr"), title); + writer.writeEndElement(); // vt:vector + writer.writeEndElement(); // TitlesOfParts + + auto it = m_properties.constFind(QStringLiteral("manager")); + if (it != m_properties.constEnd()) + writer.writeTextElement(QStringLiteral("Manager"), it.value()); + // Not like "manager", "company" always exists for Excel generated file. + + it = m_properties.constFind(QStringLiteral("company")); + writer.writeTextElement(QStringLiteral("Company"), + it != m_properties.constEnd() ? it.value() : QString()); + writer.writeTextElement(QStringLiteral("LinksUpToDate"), QStringLiteral("false")); + writer.writeTextElement(QStringLiteral("SharedDoc"), QStringLiteral("false")); + writer.writeTextElement(QStringLiteral("HyperlinksChanged"), QStringLiteral("false")); + writer.writeTextElement(QStringLiteral("AppVersion"), QStringLiteral("12.0000")); + + writer.writeEndElement(); // Properties + writer.writeEndDocument(); +} + +bool DocPropsApp::loadFromXmlFile(QIODevice *device) +{ + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("Properties")) + continue; + + if (reader.name() == QStringLiteral("Manager")) { + setProperty(QStringLiteral("manager"), reader.readElementText()); + } else if (reader.name() == QStringLiteral("Company")) { + setProperty(QStringLiteral("company"), reader.readElementText()); + } + } + + if (reader.hasError()) { + qDebug("Error when read doc props app file."); + } + } + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdocpropscore.cpp b/QXlsx/source/xlsxdocpropscore.cpp new file mode 100644 index 0000000..450b812 --- /dev/null +++ b/QXlsx/source/xlsxdocpropscore.cpp @@ -0,0 +1,170 @@ +// xlsxdocpropscore.cpp + +#include "xlsxdocpropscore_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +DocPropsCore::DocPropsCore(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ +} + +bool DocPropsCore::setProperty(const QString &name, const QString &value) +{ + static const QStringList validKeys = {QStringLiteral("title"), + QStringLiteral("subject"), + QStringLiteral("keywords"), + QStringLiteral("description"), + QStringLiteral("category"), + QStringLiteral("status"), + QStringLiteral("created"), + QStringLiteral("creator")}; + + if (!validKeys.contains(name)) + return false; + + if (value.isEmpty()) + m_properties.remove(name); + else + m_properties[name] = value; + + return true; +} + +QString DocPropsCore::property(const QString &name) const +{ + auto it = m_properties.constFind(name); + if (it != m_properties.constEnd()) + return it.value(); + + return QString(); +} + +QStringList DocPropsCore::propertyNames() const +{ + return m_properties.keys(); +} + +void DocPropsCore::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + const QString cp = + QStringLiteral("http://schemas.openxmlformats.org/package/2006/metadata/core-properties"); + const QString dc = QStringLiteral("http://purl.org/dc/elements/1.1/"); + const QString dcterms = QStringLiteral("http://purl.org/dc/terms/"); + const QString dcmitype = QStringLiteral("http://purl.org/dc/dcmitype/"); + const QString xsi = QStringLiteral("http://www.w3.org/2001/XMLSchema-instance"); + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("cp:coreProperties")); + writer.writeNamespace(cp, QStringLiteral("cp")); + writer.writeNamespace(dc, QStringLiteral("dc")); + writer.writeNamespace(dcterms, QStringLiteral("dcterms")); + writer.writeNamespace(dcmitype, QStringLiteral("dcmitype")); + writer.writeNamespace(xsi, QStringLiteral("xsi")); + + auto it = m_properties.constFind(QStringLiteral("title")); + if (it != m_properties.constEnd()) + writer.writeTextElement(dc, QStringLiteral("title"), it.value()); + + it = m_properties.constFind(QStringLiteral("subject")); + if (it != m_properties.constEnd()) + writer.writeTextElement(dc, QStringLiteral("subject"), it.value()); + + it = m_properties.constFind(QStringLiteral("creator")); + writer.writeTextElement(dc, + QStringLiteral("creator"), + it != m_properties.constEnd() ? it.value() + : QStringLiteral("Qt Xlsx Library")); + + it = m_properties.constFind(QStringLiteral("keywords")); + if (it != m_properties.constEnd()) + writer.writeTextElement(cp, QStringLiteral("keywords"), it.value()); + + it = m_properties.constFind(QStringLiteral("description")); + if (it != m_properties.constEnd()) + writer.writeTextElement(dc, QStringLiteral("description"), it.value()); + + it = m_properties.constFind(QStringLiteral("creator")); + writer.writeTextElement(cp, + QStringLiteral("lastModifiedBy"), + it != m_properties.constEnd() ? it.value() + : QStringLiteral("Qt Xlsx Library")); + + writer.writeStartElement(dcterms, QStringLiteral("created")); + writer.writeAttribute(xsi, QStringLiteral("type"), QStringLiteral("dcterms:W3CDTF")); + it = m_properties.constFind(QStringLiteral("created")); + writer.writeCharacters(it != m_properties.constEnd() + ? it.value() + : QDateTime::currentDateTime().toString(Qt::ISODate)); + writer.writeEndElement(); // dcterms:created + + writer.writeStartElement(dcterms, QStringLiteral("modified")); + writer.writeAttribute(xsi, QStringLiteral("type"), QStringLiteral("dcterms:W3CDTF")); + writer.writeCharacters(QDateTime::currentDateTime().toString(Qt::ISODate)); + writer.writeEndElement(); // dcterms:created + + it = m_properties.constFind(QStringLiteral("category")); + if (it != m_properties.constEnd()) + writer.writeTextElement(cp, QStringLiteral("category"), it.value()); + + it = m_properties.constFind(QStringLiteral("status")); + if (it != m_properties.constEnd()) + writer.writeTextElement(cp, QStringLiteral("contentStatus"), it.value()); + + writer.writeEndElement(); // cp:coreProperties + writer.writeEndDocument(); +} + +bool DocPropsCore::loadFromXmlFile(QIODevice *device) +{ + QXmlStreamReader reader(device); + + const QString cp = + QStringLiteral("http://schemas.openxmlformats.org/package/2006/metadata/core-properties"); + const QString dc = QStringLiteral("http://purl.org/dc/elements/1.1/"); + const QString dcterms = QStringLiteral("http://purl.org/dc/terms/"); + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + + if (token == QXmlStreamReader::StartElement) { + + const auto &nsUri = reader.namespaceUri(); + const auto &name = reader.name(); + + if (name == QStringLiteral("subject") && nsUri == dc) { + setProperty(QStringLiteral("subject"), reader.readElementText()); + } else if (name == QStringLiteral("title") && nsUri == dc) { + setProperty(QStringLiteral("title"), reader.readElementText()); + } else if (name == QStringLiteral("creator") && nsUri == dc) { + setProperty(QStringLiteral("creator"), reader.readElementText()); + } else if (name == QStringLiteral("description") && nsUri == dc) { + setProperty(QStringLiteral("description"), reader.readElementText()); + } else if (name == QStringLiteral("keywords") && nsUri == cp) { + setProperty(QStringLiteral("keywords"), reader.readElementText()); + } else if (name == QStringLiteral("created") && nsUri == dcterms) { + setProperty(QStringLiteral("created"), reader.readElementText()); + } else if (name == QStringLiteral("category") && nsUri == cp) { + setProperty(QStringLiteral("category"), reader.readElementText()); + } else if (name == QStringLiteral("contentStatus") && nsUri == cp) { + setProperty(QStringLiteral("status"), reader.readElementText()); + } + } + + if (reader.hasError()) { + qDebug() << "Error when read doc props core file." << reader.errorString(); + } + } + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdocument.cpp b/QXlsx/source/xlsxdocument.cpp new file mode 100644 index 0000000..9b58675 --- /dev/null +++ b/QXlsx/source/xlsxdocument.cpp @@ -0,0 +1,1460 @@ +// xlsxdocument.cpp + +#include "xlsxdocument.h" + +#include "xlsxchart.h" +#include "xlsxcontenttypes_p.h" +#include "xlsxdocpropsapp_p.h" +#include "xlsxdocpropscore_p.h" +#include "xlsxdocument_p.h" +#include "xlsxdrawing_p.h" +#include "xlsxmediafile_p.h" +#include "xlsxrelationships_p.h" +#include "xlsxsharedstrings_p.h" +#include "xlsxstyles_p.h" +#include "xlsxtheme_p.h" +#include "xlsxutility_p.h" +#include "xlsxworkbook.h" +#include "xlsxworkbook_p.h" +#include "xlsxworksheet.h" +#include "xlsxzipreader_p.h" +#include "xlsxzipwriter_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + From Wikipedia: The Open Packaging Conventions (OPC) is a + container-file technology initially created by Microsoft to store + a combination of XML and non-XML files that together form a single + entity such as an Open XML Paper Specification (OpenXPS) + document. http://en.wikipedia.org/wiki/Open_Packaging_Conventions. + + At its simplest an Excel XLSX file contains the following elements: + + ____ [Content_Types].xml + | + |____ docProps + | |____ app.xml + | |____ core.xml + | + |____ xl + | |____ workbook.xml + | |____ worksheets + | | |____ sheet1.xml + | | + | |____ styles.xml + | | + | |____ theme + | | |____ theme1.xml + | | + | |_____rels + | |____ workbook.xml.rels + | + |_____rels + |____ .rels + + The Packager class coordinates the classes that represent the + elements of the package and writes them into the XLSX file. +*/ + +QT_BEGIN_NAMESPACE_XLSX + +namespace xlsxDocumentCpp { +std::string copyTag(const std::string &sFrom, const std::string &sTo, const std::string &tag) +{ + const std::string tagToFindStart = "<" + tag; + const std::string tagToFindEnd = ""; + + // search all occurrences of tag in 'sFrom' + std::string sFromData = ""; + size_t startIndex = 0; + while (true) { + std::size_t startPos = sFrom.find(tagToFindStart, startIndex); + if (startPos != std::string::npos) { + std::size_t endPos = sFrom.find(tagToFindEnd, startPos); + std::string tagEndTmp = tagEnd; + if (endPos == std::string::npos) { // second try to find the ending, maybe it is "/>" + endPos = sFrom.find("/>", startPos); + tagEndTmp = "/>"; + } + if (endPos != std::string::npos) { + sFromData += sFrom.substr(startPos, endPos - startPos) + tagEndTmp; + startIndex = endPos + strlen(tagEndTmp.c_str()); + } else { + break; + } + } else { + break; + } + } + + std::string sOut = sTo; // copy 'sTo' in the output string + + if (!sFromData.empty()) { // tag found in 'from'? + // search all occurrences of tag in 'sOut' and delete them + int firstPosTag = -1; + while (true) { + std::size_t startPos = sOut.find(tagToFindStart); + if (startPos != std::string::npos) { + std::size_t endPos = sOut.find(tagToFindEnd); + std::string tagEndTmp = tagEnd; + if (endPos == + std::string::npos) { // second try to find the ending, maybe it is "/>" + endPos = sOut.find("/>", startPos); + tagEndTmp = "/>"; + } + if (endPos != std::string::npos) { + if (firstPosTag < 0) + firstPosTag = startPos; + std::string stringBefore = sOut.substr(0, startPos); + endPos += strlen(tagEndTmp.c_str()); + std::string stringAfter = sOut.substr(endPos, strlen(sOut.c_str()) - endPos); + sOut = stringBefore + stringAfter; + } else { + break; + } + } else { + break; + } + } + + if (firstPosTag == -1) { + // tag not found in 'sTo' file + // try to find a default pos using standard tags + std::vector defaultPos{"", ""}; + for (unsigned int i = 0; i < defaultPos.size(); ++i) { + std::size_t iDefaultPos = sOut.find(defaultPos[i]); + if (iDefaultPos != std::string::npos) { + firstPosTag = iDefaultPos; + break; + } + } + } + + // add the tag extracted from 'sFrom' in 'sOut' + // add in the position of the first tag found in 'sOut' ('firstPosTag') + if (firstPosTag >= 0) { + std::string stringBefore = sOut.substr(0, firstPosTag); + std::string stringAfter = sOut.substr(firstPosTag, strlen(sOut.c_str()) - firstPosTag); + sOut = stringBefore + sFromData + stringAfter; + } + } + + return sOut; +} +} // namespace xlsxDocumentCpp + +DocumentPrivate::DocumentPrivate(Document *p) + : q_ptr(p) + , defaultPackageName(QStringLiteral("Book1.xlsx")) + , isLoad(false) +{ +} + +void DocumentPrivate::init() +{ + if (!contentTypes) + contentTypes = std::make_shared(ContentTypes::F_NewFromScratch); + + if (workbook.isNull()) + workbook = QSharedPointer(new Workbook(Workbook::F_NewFromScratch)); +} + +bool DocumentPrivate::loadPackage(QIODevice *device) +{ + Q_Q(Document); + ZipReader zipReader(device); + QStringList filePaths = zipReader.filePaths(); + + // Load the Content_Types file + if (!filePaths.contains(QLatin1String("[Content_Types].xml"))) + return false; + contentTypes = std::make_shared(ContentTypes::F_LoadFromExists); + contentTypes->loadFromXmlData(zipReader.fileData(QStringLiteral("[Content_Types].xml"))); + + // Load root rels file + if (!filePaths.contains(QLatin1String("_rels/.rels"))) + return false; + Relationships rootRels; + rootRels.loadFromXmlData(zipReader.fileData(QStringLiteral("_rels/.rels"))); + + // load core property + QList rels_core = + rootRels.packageRelationships(QStringLiteral("/metadata/core-properties")); + if (!rels_core.isEmpty()) { + // Get the core property file name if it exists. + // In normal case, this should be "docProps/core.xml" + QString docPropsCore_Name = rels_core[0].target; + + DocPropsCore props(DocPropsCore::F_LoadFromExists); + props.loadFromXmlData(zipReader.fileData(docPropsCore_Name)); + const auto propNames = props.propertyNames(); + for (const QString &name : propNames) + q->setDocumentProperty(name, props.property(name)); + } + + // load app property + QList rels_app = + rootRels.documentRelationships(QStringLiteral("/extended-properties")); + if (!rels_app.isEmpty()) { + // Get the app property file name if it exists. + // In normal case, this should be "docProps/app.xml" + QString docPropsApp_Name = rels_app[0].target; + + DocPropsApp props(DocPropsApp::F_LoadFromExists); + props.loadFromXmlData(zipReader.fileData(docPropsApp_Name)); + const auto propNames = props.propertyNames(); + for (const QString &name : propNames) + q->setDocumentProperty(name, props.property(name)); + } + + // load workbook now, Get the workbook file path from the root rels file + // In normal case, this should be "xl/workbook.xml" + workbook = QSharedPointer(new Workbook(Workbook::F_LoadFromExists)); + QList rels_xl = + rootRels.documentRelationships(QStringLiteral("/officeDocument")); + if (rels_xl.isEmpty()) + return false; + const QString xlworkbook_Path = rels_xl[0].target; + const auto parts = splitPath(xlworkbook_Path); + const QString xlworkbook_Dir = parts.first(); + const QString relFilePath = getRelFilePath(xlworkbook_Path); + + workbook->relationships()->loadFromXmlData(zipReader.fileData(relFilePath)); + workbook->setFilePath(xlworkbook_Path); + workbook->loadFromXmlData(zipReader.fileData(xlworkbook_Path)); + + // load styles + QList rels_styles = + workbook->relationships()->documentRelationships(QStringLiteral("/styles")); + if (!rels_styles.isEmpty()) { + // In normal case this should be styles.xml which in xl + QString name = rels_styles[0].target; + + // dev34 + QString path; + if (xlworkbook_Dir == QLatin1String(".")) // root + { + path = name; + } else { + path = xlworkbook_Dir + QLatin1String("/") + name; + } + + QSharedPointer styles(new Styles(Styles::F_LoadFromExists)); + styles->loadFromXmlData(zipReader.fileData(path)); + workbook->d_func()->styles = styles; + } + + // load sharedStrings + QList rels_sharedStrings = + workbook->relationships()->documentRelationships(QStringLiteral("/sharedStrings")); + if (!rels_sharedStrings.isEmpty()) { + // In normal case this should be sharedStrings.xml which in xl + QString name = rels_sharedStrings[0].target; + QString path = xlworkbook_Dir + QLatin1String("/") + name; + workbook->d_func()->sharedStrings->loadFromXmlData(zipReader.fileData(path)); + } + + // load theme + QList rels_theme = + workbook->relationships()->documentRelationships(QStringLiteral("/theme")); + if (!rels_theme.isEmpty()) { + // In normal case this should be theme/theme1.xml which in xl + QString name = rels_theme[0].target; + QString path = xlworkbook_Dir + QLatin1String("/") + name; + workbook->theme()->loadFromXmlData(zipReader.fileData(path)); + } + + // load sheets + for (int i = 0; i < workbook->sheetCount(); ++i) { + AbstractSheet *sheet = workbook->sheet(i); + QString strFilePath = sheet->filePath(); + QString rel_path = getRelFilePath(strFilePath); + // If the .rel file exists, load it. + if (zipReader.filePaths().contains(rel_path)) + sheet->relationships()->loadFromXmlData(zipReader.fileData(rel_path)); + sheet->loadFromXmlData(zipReader.fileData(sheet->filePath())); + } + + // load external links + for (int i = 0; i < workbook->d_func()->externalLinks.count(); ++i) { + SimpleOOXmlFile *link = workbook->d_func()->externalLinks[i].data(); + QString rel_path = getRelFilePath(link->filePath()); + // If the .rel file exists, load it. + if (zipReader.filePaths().contains(rel_path)) + link->relationships()->loadFromXmlData(zipReader.fileData(rel_path)); + link->loadFromXmlData(zipReader.fileData(link->filePath())); + } + + // load drawings + for (int i = 0; i < workbook->drawings().size(); ++i) { + Drawing *drawing = workbook->drawings()[i]; + QString rel_path = getRelFilePath(drawing->filePath()); + if (zipReader.filePaths().contains(rel_path)) + drawing->relationships()->loadFromXmlData(zipReader.fileData(rel_path)); + drawing->loadFromXmlData(zipReader.fileData(drawing->filePath())); + } + + // load charts + QList> chartFileToLoad = workbook->chartFiles(); + for (int i = 0; i < chartFileToLoad.size(); ++i) { + QSharedPointer cf = chartFileToLoad[i]; + cf->loadFromXmlData(zipReader.fileData(cf->filePath())); + } + + // load media files + const auto mediaFileToLoad = workbook->mediaFiles(); + for (const auto &mf : mediaFileToLoad) { + const QString path = mf->fileName(); + const QString suffix = path.mid(path.lastIndexOf(QLatin1Char('.')) + 1); + mf->set(zipReader.fileData(path), suffix); + } + + isLoad = true; + return true; +} + +bool DocumentPrivate::savePackage(QIODevice *device) const +{ + Q_Q(const Document); + + ZipWriter zipWriter(device); + if (zipWriter.error()) + return false; + + contentTypes->clearOverrides(); + + DocPropsApp docPropsApp(DocPropsApp::F_NewFromScratch); + DocPropsCore docPropsCore(DocPropsCore::F_NewFromScratch); + + // save worksheet xml files + QList> worksheets = + workbook->getSheetsByTypes(AbstractSheet::ST_WorkSheet); + if (!worksheets.isEmpty()) + docPropsApp.addHeadingPair(QStringLiteral("Worksheets"), worksheets.size()); + + for (int i = 0; i < worksheets.size(); ++i) { + QSharedPointer sheet = worksheets[i]; + contentTypes->addWorksheetName(QStringLiteral("sheet%1").arg(i + 1)); + docPropsApp.addPartTitle(sheet->sheetName()); + + zipWriter.addFile(QStringLiteral("xl/worksheets/sheet%1.xml").arg(i + 1), + sheet->saveToXmlData()); + + Relationships *rel = sheet->relationships(); + if (!rel->isEmpty()) + zipWriter.addFile(QStringLiteral("xl/worksheets/_rels/sheet%1.xml.rels").arg(i + 1), + rel->saveToXmlData()); + } + + // save chartsheet xml files + QList> chartsheets = + workbook->getSheetsByTypes(AbstractSheet::ST_ChartSheet); + if (!chartsheets.isEmpty()) + docPropsApp.addHeadingPair(QStringLiteral("Chartsheets"), chartsheets.size()); + for (int i = 0; i < chartsheets.size(); ++i) { + QSharedPointer sheet = chartsheets[i]; + contentTypes->addWorksheetName(QStringLiteral("sheet%1").arg(i + 1)); + docPropsApp.addPartTitle(sheet->sheetName()); + + zipWriter.addFile(QStringLiteral("xl/chartsheets/sheet%1.xml").arg(i + 1), + sheet->saveToXmlData()); + Relationships *rel = sheet->relationships(); + if (!rel->isEmpty()) + zipWriter.addFile(QStringLiteral("xl/chartsheets/_rels/sheet%1.xml.rels").arg(i + 1), + rel->saveToXmlData()); + } + + // save external links xml files + for (int i = 0; i < workbook->d_func()->externalLinks.count(); ++i) { + SimpleOOXmlFile *link = workbook->d_func()->externalLinks[i].data(); + contentTypes->addExternalLinkName(QStringLiteral("externalLink%1").arg(i + 1)); + + zipWriter.addFile(QStringLiteral("xl/externalLinks/externalLink%1.xml").arg(i + 1), + link->saveToXmlData()); + Relationships *rel = link->relationships(); + if (!rel->isEmpty()) + zipWriter.addFile( + QStringLiteral("xl/externalLinks/_rels/externalLink%1.xml.rels").arg(i + 1), + rel->saveToXmlData()); + } + + // save workbook xml file + contentTypes->addWorkbook(); + zipWriter.addFile(QStringLiteral("xl/workbook.xml"), workbook->saveToXmlData()); + zipWriter.addFile(QStringLiteral("xl/_rels/workbook.xml.rels"), + workbook->relationships()->saveToXmlData()); + + // save drawing xml files + for (int i = 0; i < workbook->drawings().size(); ++i) { + contentTypes->addDrawingName(QStringLiteral("drawing%1").arg(i + 1)); + + Drawing *drawing = workbook->drawings()[i]; + zipWriter.addFile(QStringLiteral("xl/drawings/drawing%1.xml").arg(i + 1), + drawing->saveToXmlData()); + if (!drawing->relationships()->isEmpty()) + zipWriter.addFile(QStringLiteral("xl/drawings/_rels/drawing%1.xml.rels").arg(i + 1), + drawing->relationships()->saveToXmlData()); + } + + // save docProps app/core xml file + const auto docPropNames = q->documentPropertyNames(); + for (const QString &name : docPropNames) { + docPropsApp.setProperty(name, q->documentProperty(name)); + docPropsCore.setProperty(name, q->documentProperty(name)); + } + contentTypes->addDocPropApp(); + contentTypes->addDocPropCore(); + zipWriter.addFile(QStringLiteral("docProps/app.xml"), docPropsApp.saveToXmlData()); + zipWriter.addFile(QStringLiteral("docProps/core.xml"), docPropsCore.saveToXmlData()); + + // save sharedStrings xml file + if (!workbook->sharedStrings()->isEmpty()) { + contentTypes->addSharedString(); + zipWriter.addFile(QStringLiteral("xl/sharedStrings.xml"), + workbook->sharedStrings()->saveToXmlData()); + } + + // save calc chain [dev16] + contentTypes->addCalcChain(); + zipWriter.addFile(QStringLiteral("xl/calcChain.xml"), workbook->styles()->saveToXmlData()); + + // save styles xml file + contentTypes->addStyles(); + zipWriter.addFile(QStringLiteral("xl/styles.xml"), workbook->styles()->saveToXmlData()); + + // save theme xml file + contentTypes->addTheme(); + zipWriter.addFile(QStringLiteral("xl/theme/theme1.xml"), workbook->theme()->saveToXmlData()); + + // save chart xml files + for (int i = 0; i < workbook->chartFiles().size(); ++i) { + contentTypes->addChartName(QStringLiteral("chart%1").arg(i + 1)); + QSharedPointer cf = workbook->chartFiles()[i]; + zipWriter.addFile(QStringLiteral("xl/charts/chart%1.xml").arg(i + 1), cf->saveToXmlData()); + } + + // save image files + const auto mfs = workbook->mediaFiles(); + for (int i = 0; i < mfs.size(); ++i) { + auto mf = mfs[i]; + if (!mf->mimeType().isEmpty()) + contentTypes->addDefault(mf->suffix(), mf->mimeType()); + + zipWriter.addFile(QStringLiteral("xl/media/image%1.%2").arg(i + 1).arg(mf->suffix()), + mf->contents()); + } + + // save root .rels xml file + Relationships rootrels; + rootrels.addDocumentRelationship(QStringLiteral("/officeDocument"), + QStringLiteral("xl/workbook.xml")); + rootrels.addPackageRelationship(QStringLiteral("/metadata/core-properties"), + QStringLiteral("docProps/core.xml")); + rootrels.addDocumentRelationship(QStringLiteral("/extended-properties"), + QStringLiteral("docProps/app.xml")); + zipWriter.addFile(QStringLiteral("_rels/.rels"), rootrels.saveToXmlData()); + + // save content types xml file + zipWriter.addFile(QStringLiteral("[Content_Types].xml"), contentTypes->saveToXmlData()); + + zipWriter.close(); + return true; +} + +bool DocumentPrivate::copyStyle(const QString &from, const QString &to) +{ + // create a temp file because the zip writer cannot modify already existing zips + QTemporaryFile tempFile; + tempFile.open(); + tempFile.close(); + QString temFilePath = QFileInfo(tempFile).absoluteFilePath(); + + ZipWriter temporalZip(temFilePath); + + ZipReader zipReader(from); + QStringList filePaths = zipReader.filePaths(); + + QSharedPointer toReader = QSharedPointer(new ZipReader(to)); + + QStringList toFilePaths = toReader->filePaths(); + + // copy all files from "to" zip except those related to style + for (int i = 0; i < toFilePaths.size(); i++) { + if (toFilePaths[i].contains(QLatin1String("xl/styles"))) { + if (filePaths.contains(toFilePaths[i])) { // style file exist in 'from' as well + // modify style file + std::string fromData = + QString::fromUtf8(zipReader.fileData(toFilePaths[i])).toStdString(); + std::string toData = + QString::fromUtf8(toReader->fileData(toFilePaths[i])).toStdString(); + // copy default theme style from 'from' to 'to' + toData = xlsxDocumentCpp::copyTag(fromData, toData, "dxfs"); + temporalZip.addFile(toFilePaths.at(i), QString::fromUtf8(toData.c_str()).toUtf8()); + + continue; + } + } + + if (toFilePaths[i].contains(QLatin1String("xl/workbook"))) { + if (filePaths.contains(toFilePaths[i])) { // workbook file exist in 'from' as well + // modify workbook file + std::string fromData = + QString::fromUtf8(zipReader.fileData(toFilePaths[i])).toStdString(); + std::string toData = + QString::fromUtf8(toReader->fileData(toFilePaths[i])).toStdString(); + // copy default theme style from 'from' to 'to' + toData = xlsxDocumentCpp::copyTag(fromData, toData, "workbookPr"); + temporalZip.addFile(toFilePaths.at(i), QString::fromUtf8(toData.c_str()).toUtf8()); + continue; + } + } + + if (toFilePaths[i].contains(QLatin1String("xl/worksheets/sheet"))) { + if (filePaths.contains(toFilePaths[i])) { // sheet file exist in 'from' as well + // modify sheet file + std::string fromData = + QString::fromUtf8(zipReader.fileData(toFilePaths[i])).toStdString(); + std::string toData = + QString::fromUtf8(toReader->fileData(toFilePaths[i])).toStdString(); + // copy "conditionalFormatting" from 'from' to 'to' + toData = xlsxDocumentCpp::copyTag(fromData, toData, "conditionalFormatting"); + temporalZip.addFile(toFilePaths.at(i), QString::fromUtf8(toData.c_str()).toUtf8()); + continue; + } + } + + QByteArray data = toReader->fileData(toFilePaths.at(i)); + temporalZip.addFile(toFilePaths.at(i), data); + } + + temporalZip.close(); + + toReader.clear(); + + tempFile.close(); + + QFile::remove(to); + tempFile.copy(to); + + return true; +} + +/*! + \class Document + \inmodule QtXlsx + \brief The Document class provides a API that is used to handle the contents of .xlsx files. + +*/ + +/*! + * Creates a new empty xlsx document. + * The \a parent argument is passed to QObject's constructor. + */ +Document::Document(QObject *parent) + : QObject(parent) + , d_ptr(new DocumentPrivate(this)) +{ + d_ptr->init(); +} + +/*! + * \overload + * Try to open an existing xlsx document named \a name. + * The \a parent argument is passed to QObject's constructor. + */ +Document::Document(const QString &name, QObject *parent) + : QObject(parent) + , d_ptr(new DocumentPrivate(this)) +{ + d_ptr->packageName = name; + + if (QFile::exists(name)) { + QFile xlsx(name); + if (xlsx.open(QFile::ReadOnly)) { + if (!d_ptr->loadPackage(&xlsx)) { + // NOTICE: failed to load package + } + } + } + + d_ptr->init(); +} + +/*! + * \overload + * Try to open an existing xlsx document from \a device. + * The \a parent argument is passed to QObject's constructor. + */ +Document::Document(QIODevice *device, QObject *parent) + : QObject(parent) + , d_ptr(new DocumentPrivate(this)) +{ + if (device && device->isReadable()) { + if (!d_ptr->loadPackage(device)) { + // NOTICE: failed to load package + } + } + d_ptr->init(); +} + +/*! + \overload + + Write \a value to cell \a row_column with the given \a format. + */ +bool Document::write(const CellReference &row_column, const QVariant &value, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->write(row_column, value, format); + return false; +} + +/*! + * Write \a value to cell (\a row, \a col) with the \a format. + * Returns true on success. + */ +bool Document::write(int row, int col, const QVariant &value, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->write(row, col, value, format); + return false; +} + +/*! + \overload + Returns the contents of the cell \a cell. + + \sa cellAt() +*/ +QVariant Document::read(const CellReference &cell) const +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->read(cell); + return QVariant(); +} + +/*! + Returns the contents of the cell (\a row, \a col). + + \sa cellAt() + */ +QVariant Document::read(int row, int col) const +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->read(row, col); + return QVariant(); +} + +/*! + * Insert an \a image to current active worksheet at the position \a row, \a column + * Returns true if success. + */ +int Document::insertImage(int row, int column, const QImage &image) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->insertImage(row, column, image); + + return 0; +} + +bool Document::getImage(int imageIndex, QImage &img) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->getImage(imageIndex, img); + + return false; +} + +bool Document::getImage(int row, int col, QImage &img) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->getImage(row, col, img); + + return false; +} + +uint Document::getImageCount() +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->getImageCount(); + + return 0; +} + +/*! + * Creates an chart with the given \a size and insert it to the current + * active worksheet at the position \a row, \a col. + * The chart will be returned. + */ +Chart *Document::insertChart(int row, int col, const QSize &size) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->insertChart(row, col, size); + return nullptr; +} + +/*! + Merge a \a range of cells. The first cell should contain the data and the others should + be blank. All cells will be applied the same style if a valid \a format is given. + Returns true on success. + + \note All cells except the top-left one will be cleared. + */ +bool Document::mergeCells(const CellRange &range, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->mergeCells(range, format); + return false; +} + +/*! + Unmerge the cells in the \a range. + Returns true on success. +*/ +bool Document::unmergeCells(const CellRange &range) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->unmergeCells(range); + return false; +} + +/*! + Sets width in characters of columns with the given \a range and \a width. + Returns true on success. + */ +bool Document::setColumnWidth(const CellRange &range, double width) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnWidth(range, width); + return false; +} + +/*! + Sets format property of columns with the given \a range and \a format. + Returns true on success. + */ +bool Document::setColumnFormat(const CellRange &range, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnFormat(range, format); + return false; +} + +/*! + Sets hidden property of columns \a range to \a hidden. Columns are 1-indexed. + Hidden columns are not visible. + Returns true on success. + */ +bool Document::setColumnHidden(const CellRange &range, bool hidden) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnWidth(range, hidden); + return false; +} + +/*! + Sets width in characters \a column to \a width. Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnWidth(int column, double width) +{ + return setColumnWidth(column, column, width); +} + +/*! + Sets format property \a column to \a format. Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnFormat(int column, const Format &format) +{ + return setColumnFormat(column, column, format); +} + +/*! + Sets hidden property of a \a column. Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnHidden(int column, bool hidden) +{ + return setColumnHidden(column, column, hidden); +} + +/*! + Sets width in characters for columns [\a colFirst, \a colLast]. Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnWidth(int colFirst, int colLast, double width) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnWidth(colFirst, colLast, width); + return false; +} + +/*! + Sets format property of columns [\a colFirst, \a colLast] to \a format. + Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnFormat(int colFirst, int colLast, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnFormat(colFirst, colLast, format); + return false; +} + +/*! + Sets hidden property of columns [\a colFirst, \a colLast] to \a hidden. + Columns are 1-indexed. + Returns true on success. + */ +bool Document::setColumnHidden(int colFirst, int colLast, bool hidden) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setColumnHidden(colFirst, colLast, hidden); + return false; +} + +/*! + Returns width of the \a column in characters of the normal font. + Columns are 1-indexed. + Returns true on success. + */ +double Document::columnWidth(int column) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->columnWidth(column); + return 0.0; +} + +/*! + Returns formatting of the \a column. Columns are 1-indexed. + */ +Format Document::columnFormat(int column) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->columnFormat(column); + return Format(); +} + +/*! + Returns true if \a column is hidden. Columns are 1-indexed. + */ +bool Document::isColumnHidden(int column) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->isColumnHidden(column); + return false; +} + +/*! + Sets the \a format of the \a row. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Document::setRowFormat(int row, const Format &format) +{ + return setRowFormat(row, row, format); +} + +/*! + Sets the \a format of the rows including and between \a rowFirst and \a rowLast. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Document::setRowFormat(int rowFirst, int rowLast, const Format &format) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setRowFormat(rowFirst, rowLast, format); + return false; +} + +/*! + Sets the \a hidden property of the row \a row. + Rows are 1-indexed. If hidden is true rows will not be visible. + + Returns true if success. +*/ +bool Document::setRowHidden(int row, bool hidden) +{ + return setRowHidden(row, row, hidden); +} + +/*! + Sets the \a hidden property of the rows including and between \a rowFirst and \a rowLast. + Rows are 1-indexed. If hidden is true rows will not be visible. + + Returns true if success. +*/ +bool Document::setRowHidden(int rowFirst, int rowLast, bool hidden) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setRowHidden(rowFirst, rowLast, hidden); + return false; +} + +/*! + Sets the \a height of the row \a row. + Row height measured in point size. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Document::setRowHeight(int row, double height) +{ + return setRowHeight(row, row, height); +} + +/*! + Sets the \a height of the rows including and between \a rowFirst and \a rowLast. + Row height measured in point size. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Document::setRowHeight(int rowFirst, int rowLast, double height) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->setRowHeight(rowFirst, rowLast, height); + return false; +} + +/*! + Returns height of \a row in points. +*/ +double Document::rowHeight(int row) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->rowHeight(row); + return 0.0; +} + +/*! + Returns format of \a row. +*/ +Format Document::rowFormat(int row) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->rowFormat(row); + return Format(); +} + +/*! + Returns true if \a row is hidden. +*/ +bool Document::isRowHidden(int row) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->isRowHidden(row); + return false; +} + +/*! + Groups rows from \a rowFirst to \a rowLast with the given \a collapsed. + Returns false if error occurs. + */ +bool Document::groupRows(int rowFirst, int rowLast, bool collapsed) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->groupRows(rowFirst, rowLast, collapsed); + return false; +} + +/*! + Groups columns from \a colFirst to \a colLast with the given \a collapsed. + Returns false if error occurs. + */ +bool Document::groupColumns(int colFirst, int colLast, bool collapsed) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->groupColumns(colFirst, colLast, collapsed); + return false; +} + +/*! + * Add a data \a validation rule for current worksheet. Returns true if successful. + */ +bool Document::addDataValidation(const DataValidation &validation) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->addDataValidation(validation); + return false; +} + +/*! + * Add a conditional formatting \a cf for current worksheet. Returns true if successful. + */ +bool Document::addConditionalFormatting(const ConditionalFormatting &cf) +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->addConditionalFormatting(cf); + return false; +} + +/*! + * \overload + * Returns the cell at the position \a pos. If there is no cell at + * the specified position, the function returns 0. + * + * \sa read() + */ +Cell *Document::cellAt(const CellReference &pos) const +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->cellAt(pos); + return nullptr; +} + +/*! + * Returns the cell at the given \a row and \a col. If there + * is no cell at the specified position, the function returns 0. + * + * \sa read() + */ +Cell *Document::cellAt(int row, int col) const +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->cellAt(row, col); + return nullptr; +} + +/*! + * \brief Create a defined name in the workbook with the given \a name, \a formula, \a comment + * and \a scope. + * + * \param name The defined name. + * \param formula The cell or range that the defined name refers to. + * \param scope The name of one worksheet, or empty which means global scope. + * \return Return false if the name invalid. + */ +bool Document::defineName(const QString &name, + const QString &formula, + const QString &comment, + const QString &scope) +{ + Q_D(Document); + + return d->workbook->defineName(name, formula, comment, scope); +} + +/*! + Return the range that contains cell data. + */ +CellRange Document::dimension() const +{ + if (Worksheet *sheet = currentWorksheet()) + return sheet->dimension(); + return CellRange(); +} + +/*! + * Returns the value of the document's \a key property. + */ +QString Document::documentProperty(const QString &key) const +{ + Q_D(const Document); + auto it = d->documentProperties.constFind(key); + if (it != d->documentProperties.constEnd()) + return it.value(); + else + return QString(); +} + +/*! + Set the document properties such as Title, Author etc. + + The method can be used to set the document properties of the Excel + file created by Qt Xlsx. These properties are visible when you use the + Office Button -> Prepare -> Properties option in Excel and are also + available to external applications that read or index windows files. + + The \a property \a key that can be set are: + + \list + \li title + \li subject + \li creator + \li manager + \li company + \li category + \li keywords + \li description + \li status + \endlist +*/ +void Document::setDocumentProperty(const QString &key, const QString &property) +{ + Q_D(Document); + d->documentProperties[key] = property; +} + +/*! + * Returns the names of all properties that were addedusing setDocumentProperty(). + */ +QStringList Document::documentPropertyNames() const +{ + Q_D(const Document); + return d->documentProperties.keys(); +} + +/*! + * Return the internal Workbook object. + */ +Workbook *Document::workbook() const +{ + Q_D(const Document); + return d->workbook.data(); +} + +/*! + * Returns the sheet object named \a sheetName. + */ +AbstractSheet *Document::sheet(const QString &sheetName) const +{ + Q_D(const Document); + return d->workbook->sheet(sheetNames().indexOf(sheetName)); +} + +/*! + * Creates and append an sheet with the given \a name and \a type. + * Return true if success. + */ +bool Document::addSheet(const QString &name, AbstractSheet::SheetType type) +{ + Q_D(Document); + return d->workbook->addSheet(name, type); +} + +/*! + * Creates and inserts an document with the given \a name and \a type at the \a index. + * Returns false if the \a name already used. + */ +bool Document::insertSheet(int index, const QString &name, AbstractSheet::SheetType type) +{ + Q_D(Document); + return d->workbook->insertSheet(index, name, type); +} + +/*! + Rename the worksheet from \a oldName to \a newName. + Returns true if the success. + */ +bool Document::renameSheet(const QString &oldName, const QString &newName) +{ + Q_D(Document); + if (oldName == newName) + return false; + return d->workbook->renameSheet(sheetNames().indexOf(oldName), newName); +} + +/*! + Make a copy of the worksheet \a srcName with the new name \a distName. + Returns true if the success. + */ +bool Document::copySheet(const QString &srcName, const QString &distName) +{ + Q_D(Document); + if (srcName == distName) + return false; + return d->workbook->copySheet(sheetNames().indexOf(srcName), distName); +} + +/*! + Move the worksheet \a srcName to the new pos \a distIndex. + Returns true if the success. + */ +bool Document::moveSheet(const QString &srcName, int distIndex) +{ + Q_D(Document); + return d->workbook->moveSheet(sheetNames().indexOf(srcName), distIndex); +} + +/*! + Delete the worksheet \a name. + Returns true if current sheet was deleted successfully. + */ +bool Document::deleteSheet(const QString &name) +{ + Q_D(Document); + return d->workbook->deleteSheet(sheetNames().indexOf(name)); +} + +/*! + * \brief Return pointer of current sheet. + */ +AbstractSheet *Document::currentSheet() const +{ + Q_D(const Document); + + return d->workbook->activeSheet(); +} + +/*! + * \brief Return pointer of current worksheet. + * If the type of sheet is not AbstractSheet::ST_WorkSheet, then 0 will be returned. + */ +Worksheet *Document::currentWorksheet() const +{ + AbstractSheet *st = currentSheet(); + if (st && st->sheetType() == AbstractSheet::ST_WorkSheet) + return static_cast(st); + else + return nullptr; +} + +/*! + * \brief Set worksheet named \a name to be active sheet. + * Returns true if success. + */ +bool Document::selectSheet(const QString &name) +{ + Q_D(Document); + return d->workbook->setActiveSheet(sheetNames().indexOf(name)); +} + +/*! + * \brief Set worksheet whose index is \a index to be active sheet. + * Returns true if success. + */ +bool Document::selectSheet(int index) +{ + Q_D(Document); + return d->workbook->setActiveSheet(index); +} + +/*! + * Returns the names of worksheets contained in current document. + */ +QStringList Document::sheetNames() const +{ + Q_D(const Document); + return d->workbook->worksheetNames(); +} + +/*! + * Save current document to the filesystem. If no name specified when + * the document constructed, a default name "book1.xlsx" will be used. + * Returns true if saved successfully. + */ +bool Document::save() const +{ + Q_D(const Document); + QString name = d->packageName.isEmpty() ? d->defaultPackageName : d->packageName; + + return saveAs(name); +} + +/*! + * Saves the document to the file with the given \a name. + * Returns true if saved successfully. + */ +bool Document::saveAs(const QString &name) const +{ + QFile file(name); + if (file.open(QIODevice::WriteOnly)) + return saveAs(&file); + return false; +} + +/*! + * \overload + * This function writes a document to the given \a device. + * + * \warning The \a device will be closed when this function returned. + */ +bool Document::saveAs(QIODevice *device) const +{ + Q_D(const Document); + return d->savePackage(device); +} + +bool Document::isLoadPackage() const +{ + Q_D(const Document); + return d->isLoad; +} + +bool Document::load() const +{ + return isLoadPackage(); +} + +bool Document::copyStyle(const QString &from, const QString &to) +{ + return DocumentPrivate::copyStyle(from, to); +} + +/*! + * Destroys the document and cleans up. + */ +Document::~Document() +{ + delete d_ptr; +} + +// add by liufeijin 20181025 {{ +bool Document::changeimage(int filenoinmidea, QString newfile) +{ + Q_D(const Document); + + QImage newpic(newfile); + + auto mediaFileToLoad = d->workbook->mediaFiles(); + const auto mf = mediaFileToLoad[filenoinmidea]; + + const QString suffix = newfile.mid(newfile.lastIndexOf(QLatin1Char('.')) + 1); + QString mimetypemy; + if (QString::compare(QLatin1String("jpg"), suffix, Qt::CaseInsensitive) == 0) + mimetypemy = QStringLiteral("image/jpeg"); + if (QString::compare(QLatin1String("bmp"), suffix, Qt::CaseInsensitive) == 0) + mimetypemy = QStringLiteral("image/bmp"); + if (QString::compare(QLatin1String("gif"), suffix, Qt::CaseInsensitive) == 0) + mimetypemy = QStringLiteral("image/gif"); + if (QString::compare(QLatin1String("png"), suffix, Qt::CaseInsensitive) == 0) + mimetypemy = QStringLiteral("image/png"); + + QByteArray ba; + QBuffer buffer(&ba); + buffer.setBuffer(&ba); + buffer.open(QIODevice::WriteOnly); + newpic.save(&buffer, suffix.toLocal8Bit().data()); + + mf->set(ba, suffix, mimetypemy); + mediaFileToLoad[filenoinmidea] = mf; + + return true; +} +// liufeijin }} + +/*! + Returns map of columns with there maximal width + */ +QMap Document::getMaximalColumnWidth(int firstRow, int lastRow) +{ + const int defaultPixelSize = 11; // Default font pixel size of excel? + int maxRows = -1; + int maxCols = -1; + QMap colWidth; + if (!currentWorksheet()) + return colWidth; + QVector cellLocation = currentWorksheet()->getFullCells(&maxRows, &maxCols); + + for (int i = 0; i < cellLocation.size(); i++) { + int col = cellLocation.at(i).col; + int row = cellLocation.at(i).row; + int fs = cellLocation.at(i).cell->format().fontSize(); + if (fs <= 0) { + fs = defaultPixelSize; + } + + // QString str = cellLocation.at(i).cell.data()->value().toString(); + QString str = read(row, col).toString(); + + double w = str.length() * double(fs) / defaultPixelSize + + 1; // width not perfect, but works reasonably well + + if ((row >= firstRow) && (row <= lastRow)) { + if (w > colWidth.value(col)) { + colWidth.insert(col, int(w)); + } + } + } + + return colWidth; +} + +/*! + Auto ets width in characters of columns with the given \a range. + Returns true on success. + */ +bool Document::autosizeColumnWidth(const CellRange &range) +{ + bool erg = false; + + if (!range.isValid()) { + return false; + } + + const QMap colWidth = getMaximalColumnWidth(range.firstRow(), range.lastRow()); + auto it = colWidth.constBegin(); + while (it != colWidth.constEnd()) { + if ((it.key() >= range.firstColumn()) && (it.key() <= range.lastColumn())) { + erg |= setColumnWidth(it.key(), it.value()); + } + ++it; + } + + return erg; +} + +/*! + Auto sets width in characters \a column . Columns are 1-indexed. + Returns true on success. + */ +bool Document::autosizeColumnWidth(int column) +{ + bool erg = false; + + const QMap colWidth = getMaximalColumnWidth(); + auto it = colWidth.constBegin(); + while (it != colWidth.constEnd()) { + if (it.key() == column) { + erg |= setColumnWidth(it.key(), it.value()); + } + ++it; + } + + return erg; +} + +/*! + Auto sets width in characters for columns [\a colFirst, \a colLast]. Columns are 1-indexed. + Returns true on success. + */ +bool Document::autosizeColumnWidth(int colFirst, int colLast) +{ + Q_UNUSED(colFirst) + Q_UNUSED(colLast) + bool erg = false; + + const QMap colWidth = getMaximalColumnWidth(); + auto it = colWidth.constBegin(); + while (it != colWidth.constEnd()) { + if ((it.key() >= colFirst) && (it.key() <= colLast)) { + erg |= setColumnWidth(it.key(), it.value()); + } + ++it; + } + + return erg; +} + +/*! + Auto sets width in characters for all columns. + Returns true on success. + */ +bool Document::autosizeColumnWidth(void) +{ + bool erg = false; + + const QMap colWidth = getMaximalColumnWidth(); + auto it = colWidth.constBegin(); + while (it != colWidth.constEnd()) { + erg |= setColumnWidth(it.key(), it.value()); + ++it; + } + + return erg; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdrawing.cpp b/QXlsx/source/xlsxdrawing.cpp new file mode 100644 index 0000000..fc37502 --- /dev/null +++ b/QXlsx/source/xlsxdrawing.cpp @@ -0,0 +1,84 @@ +// xlsxdrawing.cpp + +#include "xlsxabstractsheet.h" +#include "xlsxdrawing_p.h" +#include "xlsxdrawinganchor_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +Drawing::Drawing(AbstractSheet *sheet, CreateFlag flag) + : AbstractOOXmlFile(flag) + , sheet(sheet) +{ + workbook = sheet->workbook(); +} + +Drawing::~Drawing() +{ + qDeleteAll(anchors); +} + +void Drawing::saveToXmlFile(QIODevice *device) const +{ + relationships()->clear(); + + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("xdr:wsDr")); + writer.writeAttribute( + QStringLiteral("xmlns:xdr"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing")); + writer.writeAttribute(QStringLiteral("xmlns:a"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/main")); + + for (DrawingAnchor *anchor : anchors) + anchor->saveToXml(writer); + + writer.writeEndElement(); // xdr:wsDr + writer.writeEndDocument(); +} + +// check point +bool Drawing::loadFromXmlFile(QIODevice *device) +{ + /* + + + + + + + + */ + + QXmlStreamReader reader(device); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("absoluteAnchor")) // CT_AbsoluteAnchor + { + DrawingAbsoluteAnchor *anchor = new DrawingAbsoluteAnchor(this); + anchor->loadFromXml(reader); + } else if (reader.name() == QLatin1String("oneCellAnchor")) // CT_OneCellAnchor + { + DrawingOneCellAnchor *anchor = new DrawingOneCellAnchor(this); + anchor->loadFromXml(reader); + } else if (reader.name() == QLatin1String("twoCellAnchor")) // CT_TwoCellAnchor + { + DrawingTwoCellAnchor *anchor = new DrawingTwoCellAnchor(this); + anchor->loadFromXml(reader); + } + } + } + + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxdrawinganchor.cpp b/QXlsx/source/xlsxdrawinganchor.cpp new file mode 100644 index 0000000..9922f30 --- /dev/null +++ b/QXlsx/source/xlsxdrawinganchor.cpp @@ -0,0 +1,1194 @@ +// xlsxdrawinganchor.cpp + +#include "xlsxchart.h" +#include "xlsxdrawing_p.h" +#include "xlsxdrawinganchor_p.h" +#include "xlsxmediafile_p.h" +#include "xlsxutility_p.h" +#include "xlsxworkbook.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +/* + The vertices that define the position of a graphical object + within the worksheet in pixels. + + +------------+------------+ + | A | B | + +-----+------------+------------+ + | |(x1,y1) | | + | 1 |(A1)._______|______ | + | | | | | + | | | | | + +-----+----| OBJECT |-----+ + | | | | | + | 2 | |______________. | + | | | (B2)| + | | | (x2,y2)| + +---- +------------+------------+ + + Example of an object that covers some of the area from cell A1 to B2. + + Based on the width and height of the object we need to calculate 8 vars: + + col_start, row_start, col_end, row_end, x1, y1, x2, y2. + + We also calculate the absolute x and y position of the top left vertex of + the object. This is required for images. + + The width and height of the cells that the object occupies can be + variable and have to be taken into account. +*/ + +// anchor + +DrawingAnchor::DrawingAnchor(Drawing *drawing, ObjectType objectType) + : m_drawing(drawing) + , m_objectType(objectType) +{ + m_drawing->anchors.append(this); + m_id = m_drawing->anchors.size(); // must be unique in one drawing{x}.xml file. +} + +DrawingAnchor::~DrawingAnchor() +{ +} + +void DrawingAnchor::setObjectPicture(const QImage &img) +{ + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + + m_pictureFile = + std::make_shared(ba, QStringLiteral("png"), QStringLiteral("image/png")); + m_drawing->workbook->addMediaFile(m_pictureFile); + + m_objectType = Picture; +} + +bool DrawingAnchor::getObjectPicture(QImage &img) +{ + if (m_pictureFile == nullptr) + return false; + + bool ret = img.loadFromData(m_pictureFile->contents()); + return ret; +} + +//{{ liufeijin +void DrawingAnchor::setObjectShape(const QImage &img) +{ + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + + m_pictureFile = + std::make_shared(ba, QStringLiteral("png"), QStringLiteral("image/png")); + m_drawing->workbook->addMediaFile(m_pictureFile); + + m_objectType = Shape; +} +//}} + +void DrawingAnchor::setObjectGraphicFrame(QSharedPointer chart) +{ + m_chartFile = chart; + m_drawing->workbook->addChartFile(chart); + + m_objectType = GraphicFrame; +} + +int DrawingAnchor::row() const +{ + return -1; +} + +int DrawingAnchor::col() const +{ + return -1; +} + +QPoint DrawingAnchor::loadXmlPos(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("pos")); + + QPoint pos; + QXmlStreamAttributes attrs = reader.attributes(); + pos.setX(attrs.value(QLatin1String("x")).toInt()); + pos.setY(attrs.value(QLatin1String("y")).toInt()); + return pos; +} + +QSize DrawingAnchor::loadXmlExt(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("ext")); + + QSize size; + QXmlStreamAttributes attrs = reader.attributes(); + size.setWidth(attrs.value(QLatin1String("cx")).toInt()); + size.setHeight(attrs.value(QLatin1String("cy")).toInt()); + return size; +} + +XlsxMarker DrawingAnchor::loadXmlMarker(QXmlStreamReader &reader, const QString &node) +{ + Q_ASSERT(reader.name() == node); + + int col = 0; + int colOffset = 0; + int row = 0; + int rowOffset = 0; + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("col")) { + col = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("colOff")) { + colOffset = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("row")) { + row = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("rowOff")) { + rowOffset = reader.readElementText().toInt(); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == node) { + break; + } + } + + return XlsxMarker(row, col, rowOffset, colOffset); +} + +void DrawingAnchor::loadXmlObject(QXmlStreamReader &reader) +{ + /* + + + + + + + + + + + + + */ + + if (reader.name() == QLatin1String("sp")) // + { + // Shape + m_objectType = Shape; + + //{{ liufeijin + sp_textlink = reader.attributes().value(QLatin1String("textlink")).toString(); + sp_macro = reader.attributes().value(QLatin1String("macro")).toString(); + //}} + + // + // + // + // + + loadXmlObjectShape(reader); // CT_Shape + } else if (reader.name() == + QLatin1String("grpSp")) // + { + // Group Shape + m_objectType = GroupShape; + loadXmlObjectGroupShape(reader); + } else if (reader.name() == QLatin1String("graphicFrame")) // + { + // Graphic Frame + m_objectType = GraphicFrame; + loadXmlObjectGraphicFrame(reader); + } else if (reader.name() == + QLatin1String("cxnSp")) // + { + // Connection Shape + m_objectType = ConnectionShape; + + // {{ liufeijin + cxnSp_macro = reader.attributes().value(QLatin1String("macro")).toString(); + // }} + + loadXmlObjectConnectionShape(reader); + } else if (reader.name() == QLatin1String("pic")) // + { + // Picture + m_objectType = Picture; + loadXmlObjectPicture(reader); + } else if (reader.name() == + QLatin1String("contentPart")) // + { + // contentPart + /// TODO: + } +} + +void DrawingAnchor::loadXmlObjectConnectionShape(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("cxnSp")); + bool hasoffext = false; + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("cNvPr")) { + xsp_cNvPR_name = reader.attributes().value(QLatin1String("name")).toString(); + xsp_cNvPR_id = reader.attributes().value(QLatin1String("id")).toString(); + } else if (reader.name() == QLatin1String("spPr")) { + xbwMode = reader.attributes().value(QLatin1String("bwMode")).toString(); + } else if (reader.name() == QLatin1String("xfrm")) { + cxnSp_filpV = reader.attributes().value(QLatin1String("flipV")).toString(); + } else if (reader.name() == QLatin1String("off")) { + posTA = loadXmlPos(reader); + hasoffext = true; + } else if (reader.name() == QLatin1String("ext") && hasoffext) { + extTA = loadXmlExt(reader); + hasoffext = false; + } else if (reader.name() == QLatin1String("prstGeom")) { + xprstGeom_prst = + reader.attributes().value(QLatin1String("prst")).toString().trimmed(); + } else if (reader.name() == QLatin1String("ln")) { + xIn_algn = reader.attributes().value(QLatin1String("algn")).toString().trimmed(); + xIn_cmpd = reader.attributes().value(QLatin1String("cmpd")).toString().trimmed(); + xIn_cap = reader.attributes().value(QLatin1String("cap")).toString().trimmed(); + xIn_w = reader.attributes().value(QLatin1String("w")).toString().trimmed(); + } else if (reader.name() == QLatin1String("headEnd")) { + x_headEnd_w = reader.attributes().value(QLatin1String("w")).toString().trimmed(); + x_headEnd_len = + reader.attributes().value(QLatin1String("len")).toString().trimmed(); + x_headEnd_tyep = + reader.attributes().value(QLatin1String("type")).toString().trimmed(); + } else if (reader.name() == QLatin1String("tailEnd")) { + x_tailEnd_w = reader.attributes().value(QLatin1String("w")).toString().trimmed(); + x_tailEnd_len = + reader.attributes().value(QLatin1String("len")).toString().trimmed(); + x_tailEnd_tyep = + reader.attributes().value(QLatin1String("type")).toString().trimmed(); + } else if (reader.name() == QLatin1String("lnRef")) { + Style_inref_idx = + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("schemeClr")) { + Style_inref_val = + reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } else if (reader.name() == QLatin1String("fillRef")) { + style_fillref_idx = + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("schemeClr")) { + style_fillref_val = + reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } else if (reader.name() == QLatin1String("effectRef")) { + style_effectref_idx = + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("schemeClr")) { + style_effectref_val = + reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } else if (reader.name() == QLatin1String("fontRef")) { + style_forntref_idx = + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("schemeClr")) { + style_forntref_val = + reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } + + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("cxnSp")) { + break; + } + } + + return; +} + +void DrawingAnchor::loadXmlObjectGraphicFrame(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("graphicFrame")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("chart")) { + QString rId = reader.attributes().value(QLatin1String("r:id")).toString(); + QString name = m_drawing->relationships()->getRelationshipById(rId).target; + + const auto parts = splitPath(m_drawing->filePath()); + QString path = QDir::cleanPath(parts.first() + QLatin1String("/") + name); + + bool exist = false; + QList> cfs = m_drawing->workbook->chartFiles(); + for (int i = 0; i < cfs.size(); ++i) { + if (cfs[i]->filePath() == path) { + // already exist + exist = true; + m_chartFile = cfs[i]; + } + } + if (!exist) { + m_chartFile = + QSharedPointer(new Chart(m_drawing->sheet, Chart::F_LoadFromExists)); + m_chartFile->setFilePath(path); + m_drawing->workbook->addChartFile(m_chartFile); + } + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("graphicFrame")) { + break; + } + } + + return; +} + +void DrawingAnchor::loadXmlObjectGroupShape(QXmlStreamReader &reader) +{ + Q_UNUSED(reader) +} + +void DrawingAnchor::loadXmlObjectPicture(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("pic")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("blip")) { + QString rId = reader.attributes().value(QLatin1String("r:embed")).toString(); + QString name = m_drawing->relationships()->getRelationshipById(rId).target; + + const auto parts = splitPath(m_drawing->filePath()); + QString path = QDir::cleanPath(parts.first() + QLatin1String("/") + name); + + bool exist = false; + const auto mfs = m_drawing->workbook->mediaFiles(); + for (const auto &mf : mfs) { + if (mf->fileName() == path) { + // already exist + exist = true; + m_pictureFile = mf; + } + } + if (!exist) { + m_pictureFile = std::make_shared(path); + m_drawing->workbook->addMediaFile(m_pictureFile, true); + } + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("pic")) { + break; + } + } + + return; +} + +void DrawingAnchor::loadXmlObjectShape(QXmlStreamReader &reader) +{ + /* + + + + + + + + + + + + + */ + /* + + + + + + + */ + + Q_ASSERT(reader.name() == QLatin1String("sp")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + + // qDebug() << __FUNCTION__ << reader.name().toString(); + + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("nvSpPr")) { + + } else if (reader.name() == QLatin1String("spPr")) { + + } else if (reader.name() == QLatin1String("style")) { + + } else if (reader.name() == QLatin1String("txBody")) { + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("sp")) { + break; + } + } + + /* + + bool hasoffext = false; + while (!reader.atEnd()) + { + reader.readNextStartElement(); + + // qDebug() << __FUNCTION__ << reader.name().toString(); + + if (reader.tokenType() == QXmlStreamReader::StartElement) + { + if (reader.name() == QLatin1String("blip")) + { + QString rId; + sp_blip_rembed= reader.attributes().value(QLatin1String("r:embed")).toString(); + sp_blip_cstate=reader.attributes().value(QLatin1String("cstate")).toString(); + rId=sp_blip_rembed; + QString name = m_drawing->relationships()->getRelationshipById(rId).target; + QString path = QDir::cleanPath(splitPath(m_drawing->filePath())[0] + + QLatin1String("/") + name); bool exist = false; QList > mfs = + m_drawing->workbook->mediaFiles(); for (int i=0; ifileName() == path) + { + //already exist + exist = true; + m_pictureFile = mfs[i]; + } + } + if (!exist) { + m_pictureFile = QSharedPointer (new MediaFile(path)); + m_drawing->workbook->addMediaFile(m_pictureFile, true); + } + } + else if (reader.name() == QLatin1String("off")) + { + posTA = loadXmlPos(reader); + hasoffext=true; + } + else if (reader.name() == QLatin1String("ext")&&hasoffext) + { + extTA = loadXmlExt(reader); + hasoffext=false; + } + else if(reader.name() == QLatin1String("blipFill")) + { + // dev24 : fixed for old Qt 5 + + rotWithShapeTA = + reader.attributes().value(QLatin1String("rotWithShape")).toString().toInt(); dpiTA = + reader.attributes().value(QLatin1String("dpi")).toString().toInt(); + + // rotWithShapeTA = + reader.attributes().value(QLatin1String("rotWithShape")).toInt(); + // dpiTA = reader.attributes().value(QLatin1String("dpi")).toInt(); + + }else if(reader.name() == QLatin1String("cNvPr")) + { + xsp_cNvPR_name= reader.attributes().value(QLatin1String("name")).toString(); + xsp_cNvPR_id= reader.attributes().value(QLatin1String("id")).toString(); + } + else if(reader.name() == QLatin1String("spPr")) + { + xbwMode= reader.attributes().value(QLatin1String("bwMode")).toString(); + } + else if(reader.name() == QLatin1String("prstGeom")) + { + xprstGeom_prst= reader.attributes().value(QLatin1String("prst")).toString(); + } + else if(reader.name() == QLatin1String("ln")) + { + xIn_algn= reader.attributes().value(QLatin1String("algn")).toString(); + xIn_cmpd= reader.attributes().value(QLatin1String("cmpd")).toString(); + xIn_cap= reader.attributes().value(QLatin1String("cap")).toString(); + xIn_w= reader.attributes().value(QLatin1String("w")).toString(); + } + else if(reader.name() == QLatin1String("headEnd")) + { + x_headEnd_w= reader.attributes().value(QLatin1String("w")).toString(); + x_headEnd_len= reader.attributes().value(QLatin1String("len")).toString(); + x_headEnd_tyep= reader.attributes().value(QLatin1String("type")).toString(); + } + else if(reader.name() == QLatin1String("tailEnd")) + { + x_tailEnd_w= reader.attributes().value(QLatin1String("w")).toString(); + x_tailEnd_len= reader.attributes().value(QLatin1String("len")).toString(); + x_tailEnd_tyep= reader.attributes().value(QLatin1String("type")).toString(); + } + else if(reader.name() == QLatin1String("lnRef")) + { + Style_inref_idx= + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if(reader.name() == QLatin1String("schemeClr")){ + Style_inref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } + else if(reader.name() == QLatin1String("fillRef")) + { + style_fillref_idx= + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) + { + if(reader.name() == QLatin1String("schemeClr")) + { + style_fillref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } + else if(reader.name() == QLatin1String("effectRef")) + { + style_effectref_idx= + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if(reader.name() == QLatin1String("schemeClr")){ + style_effectref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } + else if(reader.name() == QLatin1String("fontRef")) + { + style_forntref_idx= + reader.attributes().value(QLatin1String("idx")).toString().trimmed(); + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if(reader.name() == QLatin1String("schemeClr")){ + style_forntref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed(); + } + } + } + + } + else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == QLatin1String("sp")) + { + break; + } + } + + //*/ + + return; +} + +void DrawingAnchor::saveXmlPos(QXmlStreamWriter &writer, const QPoint &pos) const +{ + writer.writeEmptyElement(QStringLiteral("xdr:pos")); + writer.writeAttribute(QStringLiteral("x"), QString::number(pos.x())); + writer.writeAttribute(QStringLiteral("y"), QString::number(pos.y())); +} + +void DrawingAnchor::saveXmlExt(QXmlStreamWriter &writer, const QSize &ext) const +{ + writer.writeStartElement(QStringLiteral("xdr:ext")); + writer.writeAttribute(QStringLiteral("cx"), QString::number(ext.width())); + writer.writeAttribute(QStringLiteral("cy"), QString::number(ext.height())); + writer.writeEndElement(); // xdr:ext +} + +void DrawingAnchor::saveXmlMarker(QXmlStreamWriter &writer, + const XlsxMarker &marker, + const QString &node) const +{ + writer.writeStartElement(node); // xdr:from or xdr:to + writer.writeTextElement(QStringLiteral("xdr:col"), QString::number(marker.col())); + writer.writeTextElement(QStringLiteral("xdr:colOff"), QString::number(marker.colOff())); + writer.writeTextElement(QStringLiteral("xdr:row"), QString::number(marker.row())); + writer.writeTextElement(QStringLiteral("xdr:rowOff"), QString::number(marker.rowOff())); + writer.writeEndElement(); +} + +void DrawingAnchor::saveXmlObject(QXmlStreamWriter &writer) const +{ + if (m_objectType == Picture) + saveXmlObjectPicture(writer); + else if (m_objectType == ConnectionShape) + saveXmlObjectConnectionShape(writer); + else if (m_objectType == GraphicFrame) + saveXmlObjectGraphicFrame(writer); + else if (m_objectType == GroupShape) + saveXmlObjectGroupShape(writer); + else if (m_objectType == Shape) + saveXmlObjectShape(writer); +} + +void DrawingAnchor::saveXmlObjectConnectionShape(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:cxnSp")); ///? + writer.writeAttribute(QStringLiteral("macro"), cxnSp_macro); + + writer.writeStartElement(QStringLiteral("xdr:nvCxnSpPr")); + writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); + writer.writeAttribute(QStringLiteral("id"), xsp_cNvPR_id); + writer.writeAttribute(QStringLiteral("name"), xsp_cNvPR_name); + writer.writeEmptyElement(QStringLiteral("xdr:cNvCxnSpPr")); + writer.writeEndElement(); // xdr:nvCxnSpPr + + writer.writeStartElement(QStringLiteral("xdr:spPr")); + if (!xbwMode.isNull()) { + writer.writeAttribute(QStringLiteral("bwMode"), xbwMode); + } + + writer.writeStartElement(QStringLiteral("a:xfrm")); + if (!cxnSp_filpV.isEmpty()) { + writer.writeAttribute(QStringLiteral("flipV"), cxnSp_filpV); + } + writer.writeEmptyElement(QStringLiteral("a:off")); + writer.writeAttribute(QStringLiteral("x"), QString::number(posTA.x())); + writer.writeAttribute(QStringLiteral("y"), QString::number(posTA.y())); + writer.writeEmptyElement(QStringLiteral("a:ext")); + writer.writeAttribute(QStringLiteral("cx"), QString::number(extTA.width())); + writer.writeAttribute(QStringLiteral("cy"), QString::number(extTA.height())); + writer.writeEndElement(); // a:xfrm + + writer.writeStartElement(QStringLiteral("a:prstGeom")); + writer.writeAttribute(QStringLiteral("prst"), xprstGeom_prst); + writer.writeEmptyElement(QStringLiteral("a:avLst")); + writer.writeEndElement(); // a:prstGeom + + writer.writeStartElement(QStringLiteral("a:ln")); + if (!xIn_w.isEmpty() && !xIn_cap.isEmpty()) { + if (!xIn_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), xIn_w); + } + if (!xIn_cap.isEmpty()) { + writer.writeAttribute(QStringLiteral("cap"), xIn_cap); + } + if (!xIn_cmpd.isEmpty()) { + writer.writeAttribute(QStringLiteral("cmpd"), xIn_cmpd); + } + if (!xIn_algn.isEmpty()) { + writer.writeAttribute(QStringLiteral("algn"), xIn_algn); + } + } + if ((!x_headEnd_tyep.isEmpty()) || (!x_headEnd_w.isEmpty()) || (!x_headEnd_len.isEmpty())) { + writer.writeEmptyElement(QStringLiteral("a:headEnd")); + if (!x_headEnd_tyep.isEmpty()) { + writer.writeAttribute(QStringLiteral("type"), x_headEnd_tyep); + } + if (!x_headEnd_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), x_headEnd_w); + } + if (!x_headEnd_len.isEmpty()) { + writer.writeAttribute(QStringLiteral("len"), x_headEnd_len); + } + } + if ((!x_tailEnd_tyep.isEmpty()) || (!x_tailEnd_w.isEmpty()) || (!x_tailEnd_len.isEmpty())) { + writer.writeEmptyElement(QStringLiteral("a:tailEnd")); + if (!x_tailEnd_tyep.isEmpty()) { + writer.writeAttribute(QStringLiteral("type"), x_tailEnd_tyep); + } + if (!x_tailEnd_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), x_tailEnd_w); + } + if (!x_tailEnd_len.isEmpty()) { + writer.writeAttribute(QStringLiteral("len"), x_tailEnd_len); + } + } + + writer.writeEndElement(); // a:ln + + writer.writeEndElement(); // xdr:spPr + // writer style + + writer.writeStartElement(QStringLiteral("xdr:style")); // style + writer.writeStartElement(QStringLiteral("a:lnRef")); // lnRef + writer.writeAttribute(QStringLiteral("idx"), Style_inref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), Style_inref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // lnRef + writer.writeStartElement(QStringLiteral("a:fillRef")); // fillRef + writer.writeAttribute(QStringLiteral("idx"), style_fillref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_fillref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // fillRef + writer.writeStartElement(QStringLiteral("a:effectRef")); // effectRef + writer.writeAttribute(QStringLiteral("idx"), style_effectref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_effectref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // effectRef + writer.writeStartElement(QStringLiteral("a:fontRef")); // fontRef + writer.writeAttribute(QStringLiteral("idx"), style_forntref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_forntref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // fontRef + writer.writeEndElement(); // style + + writer.writeEndElement(); // xdr:sp +} + +void DrawingAnchor::saveXmlObjectGraphicFrame(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:graphicFrame")); + writer.writeAttribute(QStringLiteral("macro"), QString()); + + writer.writeStartElement(QStringLiteral("xdr:nvGraphicFramePr")); + writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); + writer.writeAttribute(QStringLiteral("id"), QString::number(m_id)); + writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Chart %1").arg(m_id)); + writer.writeEmptyElement(QStringLiteral("xdr:cNvGraphicFramePr")); + writer.writeEndElement(); // xdr:nvGraphicFramePr + + writer.writeStartElement(QStringLiteral("xdr:xfrm")); + writer.writeEndElement(); // xdr:xfrm + + writer.writeStartElement(QStringLiteral("a:graphic")); + writer.writeStartElement(QStringLiteral("a:graphicData")); + writer.writeAttribute(QStringLiteral("uri"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/chart")); + + int idx = m_drawing->workbook->chartFiles().indexOf(m_chartFile); + m_drawing->relationships()->addDocumentRelationship( + QStringLiteral("/chart"), QStringLiteral("../charts/chart%1.xml").arg(idx + 1)); + + writer.writeEmptyElement(QStringLiteral("c:chart")); + writer.writeAttribute(QStringLiteral("xmlns:c"), + QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/chart")); + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(m_drawing->relationships()->count())); + + writer.writeEndElement(); // a:graphicData + writer.writeEndElement(); // a:graphic + writer.writeEndElement(); // xdr:graphicFrame +} + +void DrawingAnchor::saveXmlObjectGroupShape(QXmlStreamWriter &writer) const +{ + Q_UNUSED(writer) +} + +void DrawingAnchor::saveXmlObjectPicture(QXmlStreamWriter &writer) const +{ + Q_ASSERT(m_objectType == Picture && m_pictureFile); + + writer.writeStartElement(QStringLiteral("xdr:pic")); + + writer.writeStartElement(QStringLiteral("xdr:nvPicPr")); + writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); + writer.writeAttribute(QStringLiteral("id"), QString::number(m_id + 1)); // liufeijin + writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Picture %1").arg(m_id)); + + writer.writeStartElement(QStringLiteral("xdr:cNvPicPr")); + writer.writeEmptyElement(QStringLiteral("a:picLocks")); + writer.writeAttribute(QStringLiteral("noChangeAspect"), QStringLiteral("1")); + writer.writeEndElement(); // xdr:cNvPicPr + + writer.writeEndElement(); // xdr:nvPicPr + + m_drawing->relationships()->addDocumentRelationship(QStringLiteral("/image"), + QStringLiteral("../media/image%1.%2") + .arg(m_pictureFile->index() + 1) + .arg(m_pictureFile->suffix())); + + writer.writeStartElement(QStringLiteral("xdr:blipFill")); + writer.writeEmptyElement(QStringLiteral("a:blip")); + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + writer.writeAttribute(QStringLiteral("r:embed"), + QStringLiteral("rId%1").arg(m_drawing->relationships()->count())); + writer.writeStartElement(QStringLiteral("a:stretch")); + writer.writeEmptyElement(QStringLiteral("a:fillRect")); + writer.writeEndElement(); // a:stretch + writer.writeEndElement(); // xdr:blipFill + + writer.writeStartElement(QStringLiteral("xdr:spPr")); + + writer.writeStartElement(QStringLiteral("a:prstGeom")); + writer.writeAttribute(QStringLiteral("prst"), QStringLiteral("rect")); + writer.writeEmptyElement(QStringLiteral("a:avLst")); + writer.writeEndElement(); // a:prstGeom + + writer.writeEndElement(); // xdr:spPr + + writer.writeEndElement(); // xdr:pic +} + +int DrawingAnchor::getm_id() +{ + return (this->m_id); +} + +void DrawingAnchor::saveXmlObjectShape(QXmlStreamWriter &writer) const +{ + //{{ liufeijin + writer.writeStartElement(QStringLiteral("xdr:sp")); // xdr:sp + writer.writeAttribute(QStringLiteral("macro"), sp_macro); + writer.writeAttribute(QStringLiteral("textlink"), sp_textlink); + + writer.writeStartElement(QStringLiteral("xdr:nvSpPr")); // xdr:nvSpPr + + writer.writeStartElement(QStringLiteral("xdr:cNvPr")); + writer.writeAttribute(QStringLiteral("id"), xsp_cNvPR_id); + writer.writeAttribute(QStringLiteral("name"), xsp_cNvPR_name); + writer.writeStartElement(QStringLiteral("a:extLst")); + writer.writeEndElement(); + writer.writeEndElement(); // xdr:cNvPr + + writer.writeEmptyElement(QStringLiteral("xdr:cNvSpPr")); + + writer.writeEndElement(); // xdr:nvSpPr + + writer.writeStartElement(QStringLiteral("xdr:spPr")); + if (!xbwMode.isNull()) { + writer.writeAttribute(QStringLiteral("bwMode"), xbwMode); + } + writer.writeStartElement(QStringLiteral("a:xfrm")); + writer.writeEmptyElement(QStringLiteral("a:off")); + writer.writeAttribute(QStringLiteral("x"), QString::number(posTA.x())); + writer.writeAttribute(QStringLiteral("y"), QString::number(posTA.y())); + writer.writeEmptyElement(QStringLiteral("a:ext")); + writer.writeAttribute(QStringLiteral("cx"), QString::number(extTA.width())); + writer.writeAttribute(QStringLiteral("cy"), QString::number(extTA.height())); + writer.writeEndElement(); // a:xfrm + + writer.writeStartElement(QStringLiteral("a:prstGeom")); + writer.writeAttribute(QStringLiteral("prst"), xprstGeom_prst); + writer.writeEmptyElement(QStringLiteral("a:avLst")); + writer.writeEndElement(); // a:prstGeom + + if (m_pictureFile) { + m_drawing->relationships()->addDocumentRelationship(QStringLiteral("/image"), + QStringLiteral("../media/image%1.%2") + .arg(m_pictureFile->index() + 1) + .arg(m_pictureFile->suffix())); + writer.writeStartElement(QStringLiteral("a:blipFill")); + writer.writeAttribute(QStringLiteral("dpi"), QString::number(dpiTA)); + writer.writeAttribute(QStringLiteral("rotWithShape"), QString::number(rotWithShapeTA)); + + writer.writeStartElement(QStringLiteral("a:blip")); + writer.writeAttribute( + QStringLiteral("r:embed"), + QStringLiteral("rId%1").arg( + m_drawing->relationships() + ->count())); // sp_blip_rembed + // QStringLiteral("rId%1").arg(m_drawing->relationships()->count()) + // can't made new pic + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + if (!sp_blip_cstate.isNull()) { + writer.writeAttribute(QStringLiteral("cstate"), sp_blip_cstate); + } + writer.writeEndElement(); // a:blip + writer.writeEmptyElement(QStringLiteral("a:srcRect")); + writer.writeStartElement(QStringLiteral("a:stretch")); + writer.writeEmptyElement(QStringLiteral("a:fillRect")); + writer.writeEndElement(); // a:stretch + writer.writeEndElement(); // a:blipFill + } + writer.writeStartElement(QStringLiteral("a:ln")); + if (!xIn_w.isEmpty() && !xIn_cap.isEmpty()) { + if (!xIn_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), xIn_w); + } + if (!xIn_cap.isEmpty()) { + writer.writeAttribute(QStringLiteral("cap"), xIn_cap); + } + if (!xIn_cmpd.isEmpty()) { + writer.writeAttribute(QStringLiteral("cmpd"), xIn_cmpd); + } + if (!xIn_algn.isEmpty()) { + writer.writeAttribute(QStringLiteral("algn"), xIn_algn); + } + } + if ((!x_headEnd_tyep.isEmpty()) || (!x_headEnd_w.isEmpty()) || (!x_headEnd_len.isEmpty())) { + writer.writeEmptyElement(QStringLiteral("a:headEnd")); + if (!x_headEnd_tyep.isEmpty()) { + writer.writeAttribute(QStringLiteral("type"), x_headEnd_tyep); + } + if (!x_headEnd_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), x_headEnd_w); + } + if (!x_headEnd_len.isEmpty()) { + writer.writeAttribute(QStringLiteral("len"), x_headEnd_len); + } + } + if ((!x_tailEnd_tyep.isEmpty()) || (!x_tailEnd_w.isEmpty()) || (!x_tailEnd_len.isEmpty())) { + writer.writeEmptyElement(QStringLiteral("a:tailEnd")); + if (!x_tailEnd_tyep.isEmpty()) { + writer.writeAttribute(QStringLiteral("type"), x_tailEnd_tyep); + } + if (!x_tailEnd_w.isEmpty()) { + writer.writeAttribute(QStringLiteral("w"), x_tailEnd_w); + } + if (!x_tailEnd_len.isEmpty()) { + writer.writeAttribute(QStringLiteral("len"), x_tailEnd_len); + } + } + + writer.writeEndElement(); // a:ln + + writer.writeEndElement(); // xdr:spPr + // writer style + + writer.writeStartElement(QStringLiteral("xdr:style")); // style + writer.writeStartElement(QStringLiteral("a:lnRef")); // lnRef + writer.writeAttribute(QStringLiteral("idx"), Style_inref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), Style_inref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // lnRef + writer.writeStartElement(QStringLiteral("a:fillRef")); // fillRef + writer.writeAttribute(QStringLiteral("idx"), style_fillref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_fillref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // fillRef + writer.writeStartElement(QStringLiteral("a:effectRef")); // effectRef + writer.writeAttribute(QStringLiteral("idx"), style_effectref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_effectref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // effectRef + writer.writeStartElement(QStringLiteral("a:fontRef")); // fontRef + writer.writeAttribute(QStringLiteral("idx"), style_forntref_idx); + writer.writeStartElement(QStringLiteral("a:schemeClr")); // val + writer.writeAttribute(QStringLiteral("val"), style_forntref_val); + writer.writeEndElement(); // val + writer.writeEndElement(); // fontRef + writer.writeEndElement(); // style + + writer.writeEndElement(); // xdr:sp + + //}} liufeijin +} + +// absolute anchor + +DrawingAbsoluteAnchor::DrawingAbsoluteAnchor(Drawing *drawing, ObjectType objectType) + : DrawingAnchor(drawing, objectType) +{ +} + +// check point +bool DrawingAbsoluteAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("absoluteAnchor")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("pos")) { + pos = loadXmlPos(reader); + } else if (reader.name() == QLatin1String("ext")) { + ext = loadXmlExt(reader); + } else { + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("absoluteAnchor")) { + break; + } + } + return true; +} + +void DrawingAbsoluteAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:absoluteAnchor")); + saveXmlPos(writer, pos); + saveXmlExt(writer, ext); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); // xdr:absoluteAnchor +} + +// one cell anchor + +DrawingOneCellAnchor::DrawingOneCellAnchor(Drawing *drawing, ObjectType objectType) + : DrawingAnchor(drawing, objectType) +{ +} + +int DrawingOneCellAnchor::row() const +{ + return from.row(); +} + +int DrawingOneCellAnchor::col() const +{ + return from.col(); +} + +// check point +bool DrawingOneCellAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("oneCellAnchor")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("from")) { + from = loadXmlMarker(reader, QLatin1String("from")); + } else if (reader.name() == QLatin1String("ext")) { + ext = loadXmlExt(reader); + } else { + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("oneCellAnchor")) { + break; + } + } + return true; +} + +void DrawingOneCellAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:oneCellAnchor")); + + saveXmlMarker(writer, from, QStringLiteral("xdr:from")); + saveXmlExt(writer, ext); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); // xdr:oneCellAnchor +} + +/* + Two cell anchor + + This class specifies a two cell anchor placeholder for a group + , a shape, or a drawing element. It moves with + cells and its extents are in EMU units. +*/ +DrawingTwoCellAnchor::DrawingTwoCellAnchor(Drawing *drawing, ObjectType objectType) + : DrawingAnchor(drawing, objectType) +{ +} + +int DrawingTwoCellAnchor::row() const +{ + return from.row(); +} + +int DrawingTwoCellAnchor::col() const +{ + return from.col(); +} + +// check point +bool DrawingTwoCellAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("twoCellAnchor")); + + /* + + + + + + + + + + */ + + //{{ liufeijin + QXmlStreamAttributes attrs = + reader.attributes(); // for absolute twocell added by liufeijin 20181024 + editASName = attrs.value(QLatin1String("editAs")).toString(); + //}} + + while (!reader.atEnd()) { + reader.readNextStartElement(); + + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("from")) { + from = loadXmlMarker(reader, QLatin1String("from")); + } else if (reader.name() == QLatin1String("to")) { + to = loadXmlMarker(reader, QLatin1String("to")); + } else if (reader.name() == QLatin1String("clientData")) { + // clientData + } else { + /* + + + + + + + + + + + + + */ + + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("twoCellAnchor")) { + break; + } + } + return true; +} + +void DrawingTwoCellAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:twoCellAnchor")); + + //{{ liufeijin + // writer.writeAttribute(QStringLiteral("editAs"), QStringLiteral("oneCell")); + if (!editASName.isNull()) { + writer.writeAttribute(QStringLiteral("editAs"), editASName); // QStringLiteral("oneCell") + } + // }} + + saveXmlMarker(writer, from, QStringLiteral("xdr:from")); + saveXmlMarker(writer, to, QStringLiteral("xdr:to")); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); // xdr:twoCellAnchor +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxformat.cpp b/QXlsx/source/xlsxformat.cpp new file mode 100644 index 0000000..3aef838 --- /dev/null +++ b/QXlsx/source/xlsxformat.cpp @@ -0,0 +1,1452 @@ +// xlsxformat.cpp + +#include "xlsxformat.h" + +#include "xlsxcolor_p.h" +#include "xlsxformat_p.h" +#include "xlsxnumformatparser_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +FormatPrivate::FormatPrivate() + : dirty(true) + , font_dirty(true) + , font_index_valid(false) + , font_index(0) + , fill_dirty(true) + , fill_index_valid(false) + , fill_index(0) + , border_dirty(true) + , border_index_valid(false) + , border_index(0) + , xf_index(-1) + , xf_indexValid(false) + , is_dxf_fomat(false) + , dxf_index(-1) + , dxf_indexValid(false) + , theme(0) +{ +} + +FormatPrivate::FormatPrivate(const FormatPrivate &other) + : QSharedData(other) + , dirty(other.dirty) + , formatKey(other.formatKey) + , font_dirty(other.font_dirty) + , font_index_valid(other.font_index_valid) + , font_key(other.font_key) + , font_index(other.font_index) + , fill_dirty(other.fill_dirty) + , fill_index_valid(other.fill_index_valid) + , fill_key(other.fill_key) + , fill_index(other.fill_index) + , border_dirty(other.border_dirty) + , border_index_valid(other.border_index_valid) + , border_key(other.border_key) + , border_index(other.border_index) + , xf_index(other.xf_index) + , xf_indexValid(other.xf_indexValid) + , is_dxf_fomat(other.is_dxf_fomat) + , dxf_index(other.dxf_index) + , dxf_indexValid(other.dxf_indexValid) + , theme(other.theme) + , properties(other.properties) +{ +} + +FormatPrivate::~FormatPrivate() +{ +} + +/*! + * \class Format + * \inmodule QtXlsx + * \brief Providing the methods and properties that are available for formatting cells in Excel. + */ + +/*! + * \enum Format::FontScript + * + * The enum type defines the type of font script. + * + * \value FontScriptNormal normal + * \value FontScriptSuper super script + * \value FontScriptSub sub script + */ + +/*! + * \enum Format::FontUnderline + * + * The enum type defines the type of font underline. + * + * \value FontUnderlineNone + * \value FontUnderlineSingle + * \value FontUnderlineDouble + * \value FontUnderlineSingleAccounting + * \value FontUnderlineDoubleAccounting + */ + +/*! + * \enum Format::HorizontalAlignment + * + * The enum type defines the type of horizontal alignment. + * + * \value AlignHGeneral + * \value AlignLeft + * \value AlignHCenter + * \value AlignRight + * \value AlignHFill + * \value AlignHJustify + * \value AlignHMerge + * \value AlignHDistributed + */ + +/*! + * \enum Format::VerticalAlignment + * + * The enum type defines the type of vertical alignment. + * + * \value AlignTop, + * \value AlignVCenter, + * \value AlignBottom, + * \value AlignVJustify, + * \value AlignVDistributed + */ + +/*! + * \enum Format::BorderStyle + * + * The enum type defines the type of font underline. + * + * \value BorderNone + * \value BorderThin + * \value BorderMedium + * \value BorderDashed + * \value BorderDotted + * \value BorderThick + * \value BorderDouble + * \value BorderHair + * \value BorderMediumDashed + * \value BorderDashDot + * \value BorderMediumDashDot + * \value BorderDashDotDot + * \value BorderMediumDashDotDot + * \value BorderSlantDashDot + */ + +/*! + * \enum Format::DiagonalBorderType + * + * The enum type defines the type of diagonal border. + * + * \value DiagonalBorderNone + * \value DiagonalBorderDown + * \value DiagonalBorderUp + * \value DiagnoalBorderBoth + */ + +/*! + * \enum Format::FillPattern + * + * The enum type defines the type of fill. + * + * \value PatternNone + * \value PatternSolid + * \value PatternMediumGray + * \value PatternDarkGray + * \value PatternLightGray + * \value PatternDarkHorizontal + * \value PatternDarkVertical + * \value PatternDarkDown + * \value PatternDarkUp + * \value PatternDarkGrid + * \value PatternDarkTrellis + * \value PatternLightHorizontal + * \value PatternLightVertical + * \value PatternLightDown + * \value PatternLightUp + * \value PatternLightTrellis + * \value PatternGray125 + * \value PatternGray0625 + * \value PatternLightGrid + */ + +/*! + * Creates a new invalid format. + */ +Format::Format() +{ + // The d pointer is initialized with a null pointer +} + +/*! + Creates a new format with the same attributes as the \a other format. + */ +Format::Format(const Format &other) + : d(other.d) +{ +} + +/*! + Assigns the \a other format to this format, and returns a + reference to this format. + */ +Format &Format::operator=(const Format &other) +{ + d = other.d; + return *this; +} + +/*! + * Destroys this format. + */ +Format::~Format() +{ +} + +/*! + * Returns the number format identifier. + */ +int Format::numberFormatIndex() const +{ + return intProperty(FormatPrivate::P_NumFmt_Id, 0); +} + +/*! + * Set the number format identifier. The \a format + * must be a valid built-in number format identifier + * or the identifier of a custom number format. + */ +void Format::setNumberFormatIndex(int format) +{ + setProperty(FormatPrivate::P_NumFmt_Id, format); + clearProperty(FormatPrivate::P_NumFmt_FormatCode); +} + +/*! + * Returns the number format string. + * \note for built-in number formats, this may + * return an empty string. + */ +QString Format::numberFormat() const +{ + return stringProperty(FormatPrivate::P_NumFmt_FormatCode); +} + +/*! + * Set number \a format. + * http://office.microsoft.com/en-001/excel-help/create-a-custom-number-format-HP010342372.aspx + */ +void Format::setNumberFormat(const QString &format) +{ + if (format.isEmpty()) + return; + setProperty(FormatPrivate::P_NumFmt_FormatCode, format); + clearProperty(FormatPrivate::P_NumFmt_Id); // numFmt id must be re-generated. +} + +/*! + * Returns whether the number format is probably a dateTime or not + */ +bool Format::isDateTimeFormat() const +{ + // NOTICE: + + if (hasProperty(FormatPrivate::P_NumFmt_FormatCode)) { + // Custom numFmt, so + // Gauss from the number string + return NumFormatParser::isDateTime(numberFormat()); + } else if (hasProperty(FormatPrivate::P_NumFmt_Id)) { + // Non-custom numFmt + int idx = numberFormatIndex(); + + // Is built-in date time number id? + if ((idx >= 14 && idx <= 22) || (idx >= 45 && idx <= 47)) + return true; + + if ((idx >= 27 && idx <= 36) || (idx >= 50 && idx <= 58)) // Used in CHS\CHT\JPN\KOR + return true; + } + + return false; +} + +/*! + \internal + Set a custom num \a format with the given \a id. + */ +void Format::setNumberFormat(int id, const QString &format) +{ + setProperty(FormatPrivate::P_NumFmt_Id, id); + setProperty(FormatPrivate::P_NumFmt_FormatCode, format); +} + +/*! + \internal + Called by styles to fix the numFmt + */ +void Format::fixNumberFormat(int id, const QString &format) +{ + setProperty(FormatPrivate::P_NumFmt_Id, id, 0, false); + setProperty(FormatPrivate::P_NumFmt_FormatCode, format, QString(), false); +} + +/*! + \internal + Return true if the format has number format. + */ +bool Format::hasNumFmtData() const +{ + if (!d) + return false; + + if (hasProperty(FormatPrivate::P_NumFmt_Id) || + hasProperty(FormatPrivate::P_NumFmt_FormatCode)) { + return true; + } + return false; +} + +/*! + * Return the size of the font in points. + */ +int Format::fontSize() const +{ + return intProperty(FormatPrivate::P_Font_Size); +} + +/*! + * Set the \a size of the font in points. + */ +void Format::setFontSize(int size) +{ + setProperty(FormatPrivate::P_Font_Size, size, 0); +} + +/*! + * Return whether the font is italic. + */ +bool Format::fontItalic() const +{ + return boolProperty(FormatPrivate::P_Font_Italic); +} + +/*! + * Turn on/off the italic font based on \a italic. + */ +void Format::setFontItalic(bool italic) +{ + setProperty(FormatPrivate::P_Font_Italic, italic, false); +} + +/*! + * Return whether the font is strikeout. + */ +bool Format::fontStrikeOut() const +{ + return boolProperty(FormatPrivate::P_Font_StrikeOut); +} + +/*! + * Turn on/off the strikeOut font based on \a strikeOut. + */ +void Format::setFontStrikeOut(bool strikeOut) +{ + setProperty(FormatPrivate::P_Font_StrikeOut, strikeOut, false); +} + +/*! + * Return the color of the font. + */ +QColor Format::fontColor() const +{ + if (hasProperty(FormatPrivate::P_Font_Color)) + return colorProperty(FormatPrivate::P_Font_Color); + return QColor(); +} + +/*! + * Set the \a color of the font. + */ +void Format::setFontColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Font_Color, XlsxColor(color), XlsxColor()); +} + +/*! + * Return whether the font is bold. + */ +bool Format::fontBold() const +{ + return boolProperty(FormatPrivate::P_Font_Bold); +} + +/*! + * Turn on/off the bold font based on the given \a bold. + */ +void Format::setFontBold(bool bold) +{ + setProperty(FormatPrivate::P_Font_Bold, bold, false); +} + +/*! + * Return the script style of the font. + */ +Format::FontScript Format::fontScript() const +{ + return static_cast(intProperty(FormatPrivate::P_Font_Script)); +} + +/*! + * Set the script style of the font to \a script. + */ +void Format::setFontScript(FontScript script) +{ + setProperty(FormatPrivate::P_Font_Script, script, FontScriptNormal); +} + +/*! + * Return the underline style of the font. + */ +Format::FontUnderline Format::fontUnderline() const +{ + return static_cast(intProperty(FormatPrivate::P_Font_Underline)); +} + +/*! + * Set the underline style of the font to \a underline. + */ +void Format::setFontUnderline(FontUnderline underline) +{ + setProperty(FormatPrivate::P_Font_Underline, underline, FontUnderlineNone); +} + +/*! + * Return whether the font is outline. + */ +bool Format::fontOutline() const +{ + return boolProperty(FormatPrivate::P_Font_Outline); +} + +/*! + * Turn on/off the outline font based on \a outline. + */ +void Format::setFontOutline(bool outline) +{ + setProperty(FormatPrivate::P_Font_Outline, outline, false); +} + +/*! + * Return the name of the font. + */ +QString Format::fontName() const +{ + return stringProperty(FormatPrivate::P_Font_Name, QStringLiteral("Calibri")); +} + +/*! + * Set the name of the font to \a name. + */ +void Format::setFontName(const QString &name) +{ + setProperty(FormatPrivate::P_Font_Name, name, QStringLiteral("Calibri")); +} + +/*! + * Returns a QFont object based on font data contained in the format. + */ +QFont Format::font() const +{ + QFont font; + font.setFamily(fontName()); + if (fontSize() > 0) + font.setPointSize(fontSize()); + font.setBold(fontBold()); + font.setItalic(fontItalic()); + font.setUnderline(fontUnderline() != FontUnderlineNone); + font.setStrikeOut(fontStrikeOut()); + return font; +} + +/*! + * Set the format properties from the given \a font. + */ +void Format::setFont(const QFont &font) +{ + setFontName(font.family()); + if (font.pointSize() > 0) + setFontSize(font.pointSize()); + setFontBold(font.bold()); + setFontItalic(font.italic()); + setFontUnderline(font.underline() ? FontUnderlineSingle : FontUnderlineNone); + setFontStrikeOut(font.strikeOut()); +} + +/*! + * \internal + * When the format has font data, when need to assign a valid index for it. + * The index value is depend on the order in styles.xml + */ +bool Format::fontIndexValid() const +{ + if (!hasFontData()) + return false; + return d->font_index_valid; +} + +/*! + * \internal + */ +int Format::fontIndex() const +{ + if (fontIndexValid()) + return d->font_index; + + return 0; +} + +/*! + * \internal + */ +void Format::setFontIndex(int index) +{ + d->font_index = index; + d->font_index_valid = true; +} + +/*! + * \internal + */ +QByteArray Format::fontKey() const +{ + if (isEmpty()) + return QByteArray(); + + if (d->font_dirty) { + QByteArray key; + QDataStream stream(&key, QIODevice::WriteOnly); + for (int i = FormatPrivate::P_Font_STARTID; i < FormatPrivate::P_Font_ENDID; ++i) { + auto it = d->properties.constFind(i); + if (it != d->properties.constEnd()) + stream << i << it.value(); + }; + + const_cast(this)->d->font_key = key; + const_cast(this)->d->font_dirty = false; + } + + return d->font_key; +} + +/*! + \internal + Return true if the format has font format, otherwise return false. + */ +bool Format::hasFontData() const +{ + if (!d) + return false; + + for (int i = FormatPrivate::P_Font_STARTID; i < FormatPrivate::P_Font_ENDID; ++i) { + if (hasProperty(i)) + return true; + } + return false; +} + +/*! + * Return the horizontal alignment. + */ +Format::HorizontalAlignment Format::horizontalAlignment() const +{ + return static_cast( + intProperty(FormatPrivate::P_Alignment_AlignH, AlignHGeneral)); +} + +/*! + * Set the horizontal alignment with the given \a align. + */ +void Format::setHorizontalAlignment(HorizontalAlignment align) +{ + if (hasProperty(FormatPrivate::P_Alignment_Indent) && + (align != AlignHGeneral && align != AlignLeft && align != AlignRight && + align != AlignHDistributed)) { + clearProperty(FormatPrivate::P_Alignment_Indent); + } + + if (hasProperty(FormatPrivate::P_Alignment_ShinkToFit) && + (align == AlignHFill || align == AlignHJustify || align == AlignHDistributed)) { + clearProperty(FormatPrivate::P_Alignment_ShinkToFit); + } + + setProperty(FormatPrivate::P_Alignment_AlignH, align, AlignHGeneral); +} + +/*! + * Return the vertical alignment. + */ +Format::VerticalAlignment Format::verticalAlignment() const +{ + return static_cast( + intProperty(FormatPrivate::P_Alignment_AlignV, AlignBottom)); +} + +/*! + * Set the vertical alignment with the given \a align. + */ +void Format::setVerticalAlignment(VerticalAlignment align) +{ + setProperty(FormatPrivate::P_Alignment_AlignV, align, AlignBottom); +} + +/*! + * Return whether the cell text is wrapped. + */ +bool Format::textWrap() const +{ + return boolProperty(FormatPrivate::P_Alignment_Wrap); +} + +/*! + * Enable the text wrap if \a wrap is true. + */ +void Format::setTextWrap(bool wrap) +{ + if (wrap && hasProperty(FormatPrivate::P_Alignment_ShinkToFit)) + clearProperty(FormatPrivate::P_Alignment_ShinkToFit); + + setProperty(FormatPrivate::P_Alignment_Wrap, wrap, false); +} + +/*! + * Return the text rotation. + */ +int Format::rotation() const +{ + return intProperty(FormatPrivate::P_Alignment_Rotation); +} + +/*! + * Set the text rotation with the given \a rotation. Must be in the range [0, 180] or 255. + */ +void Format::setRotation(int rotation) +{ + setProperty(FormatPrivate::P_Alignment_Rotation, rotation, 0); +} + +/*! + * Return the text indentation level. + */ +int Format::indent() const +{ + return intProperty(FormatPrivate::P_Alignment_Indent); +} + +/*! + * Set the text indentation level with the given \a indent. Must be less than or equal to 15. + */ +void Format::setIndent(int indent) +{ + if (indent && hasProperty(FormatPrivate::P_Alignment_AlignH)) { + HorizontalAlignment hl = horizontalAlignment(); + + if (hl != AlignHGeneral && hl != AlignLeft && hl != AlignRight && hl != AlignHJustify) { + setHorizontalAlignment(AlignLeft); + } + } + + setProperty(FormatPrivate::P_Alignment_Indent, indent, 0); +} + +/*! + * Return whether the cell is shrink to fit. + */ +bool Format::shrinkToFit() const +{ + return boolProperty(FormatPrivate::P_Alignment_ShinkToFit); +} + +/*! + * Turn on/off shrink to fit base on \a shink. + */ +void Format::setShrinkToFit(bool shink) +{ + if (shink && hasProperty(FormatPrivate::P_Alignment_Wrap)) + clearProperty(FormatPrivate::P_Alignment_Wrap); + + if (shink && hasProperty(FormatPrivate::P_Alignment_AlignH)) { + HorizontalAlignment hl = horizontalAlignment(); + if (hl == AlignHFill || hl == AlignHJustify || hl == AlignHDistributed) + setHorizontalAlignment(AlignLeft); + } + + setProperty(FormatPrivate::P_Alignment_ShinkToFit, shink, false); +} + +/*! + * \internal + */ +bool Format::hasAlignmentData() const +{ + if (!d) + return false; + + for (int i = FormatPrivate::P_Alignment_STARTID; i < FormatPrivate::P_Alignment_ENDID; ++i) { + if (hasProperty(i)) + return true; + } + return false; +} + +/*! + * Set the border style with the given \a style. + */ +void Format::setBorderStyle(BorderStyle style) +{ + setLeftBorderStyle(style); + setRightBorderStyle(style); + setBottomBorderStyle(style); + setTopBorderStyle(style); +} + +/*! + * Sets the border color with the given \a color. + */ +void Format::setBorderColor(const QColor &color) +{ + setLeftBorderColor(color); + setRightBorderColor(color); + setTopBorderColor(color); + setBottomBorderColor(color); +} + +/*! + * Returns the left border style + */ +Format::BorderStyle Format::leftBorderStyle() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_LeftStyle)); +} + +/*! + * Sets the left border style to \a style + */ +void Format::setLeftBorderStyle(BorderStyle style) +{ + setProperty(FormatPrivate::P_Border_LeftStyle, style, BorderNone); +} + +/*! + * Returns the left border color + */ +QColor Format::leftBorderColor() const +{ + return colorProperty(FormatPrivate::P_Border_LeftColor); +} + +/*! + Sets the left border color to the given \a color +*/ +void Format::setLeftBorderColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Border_LeftColor, XlsxColor(color), XlsxColor()); +} + +/*! + Returns the right border style. +*/ +Format::BorderStyle Format::rightBorderStyle() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_RightStyle)); +} + +/*! + Sets the right border style to the given \a style. +*/ +void Format::setRightBorderStyle(BorderStyle style) +{ + setProperty(FormatPrivate::P_Border_RightStyle, style, BorderNone); +} + +/*! + Returns the right border color. +*/ +QColor Format::rightBorderColor() const +{ + return colorProperty(FormatPrivate::P_Border_RightColor); +} + +/*! + Sets the right border color to the given \a color +*/ +void Format::setRightBorderColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Border_RightColor, XlsxColor(color), XlsxColor()); +} + +/*! + Returns the top border style. +*/ +Format::BorderStyle Format::topBorderStyle() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_TopStyle)); +} + +/*! + Sets the top border style to the given \a style. +*/ +void Format::setTopBorderStyle(BorderStyle style) +{ + setProperty(FormatPrivate::P_Border_TopStyle, style, BorderNone); +} + +/*! + Returns the top border color. +*/ +QColor Format::topBorderColor() const +{ + return colorProperty(FormatPrivate::P_Border_TopColor); +} + +/*! + Sets the top border color to the given \a color. +*/ +void Format::setTopBorderColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Border_TopColor, XlsxColor(color), XlsxColor()); +} + +/*! + Returns the bottom border style. +*/ +Format::BorderStyle Format::bottomBorderStyle() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_BottomStyle)); +} + +/*! + Sets the bottom border style to the given \a style. +*/ +void Format::setBottomBorderStyle(BorderStyle style) +{ + setProperty(FormatPrivate::P_Border_BottomStyle, style, BorderNone); +} + +/*! + Returns the bottom border color. +*/ +QColor Format::bottomBorderColor() const +{ + return colorProperty(FormatPrivate::P_Border_BottomColor); +} + +/*! + Sets the bottom border color to the given \a color. +*/ +void Format::setBottomBorderColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Border_BottomColor, XlsxColor(color), XlsxColor()); +} + +/*! + Return the diagonla border style. +*/ +Format::BorderStyle Format::diagonalBorderStyle() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_DiagonalStyle)); +} + +/*! + Sets the diagonal border style to the given \a style. +*/ +void Format::setDiagonalBorderStyle(BorderStyle style) +{ + setProperty(FormatPrivate::P_Border_DiagonalStyle, style, BorderNone); +} + +/*! + Returns the diagonal border type. +*/ +Format::DiagonalBorderType Format::diagonalBorderType() const +{ + return static_cast(intProperty(FormatPrivate::P_Border_DiagonalType)); +} + +/*! + Sets the diagonal border type to the given \a style +*/ +void Format::setDiagonalBorderType(DiagonalBorderType style) +{ + setProperty(FormatPrivate::P_Border_DiagonalType, style, DiagonalBorderNone); +} + +/*! + Returns the diagonal border color. +*/ +QColor Format::diagonalBorderColor() const +{ + return colorProperty(FormatPrivate::P_Border_DiagonalColor); +} + +/*! + Sets the diagonal border color to the given \a color +*/ +void Format::setDiagonalBorderColor(const QColor &color) +{ + setProperty(FormatPrivate::P_Border_DiagonalColor, XlsxColor(color), XlsxColor()); +} + +/*! + \internal + Returns whether this format has been set valid border index. +*/ +bool Format::borderIndexValid() const +{ + if (!hasBorderData()) + return false; + return d->border_index_valid; +} + +/*! + \internal + Returns the border index. +*/ +int Format::borderIndex() const +{ + if (borderIndexValid()) + return d->border_index; + return 0; +} + +/*! + * \internal + */ +void Format::setBorderIndex(int index) +{ + d->border_index = index; + d->border_index_valid = true; +} + +/*! \internal + */ +QByteArray Format::borderKey() const +{ + if (isEmpty()) + return QByteArray(); + + if (d->border_dirty) { + QByteArray key; + QDataStream stream(&key, QIODevice::WriteOnly); + for (int i = FormatPrivate::P_Border_STARTID; i < FormatPrivate::P_Border_ENDID; ++i) { + auto it = d->properties.constFind(i); + if (it != d->properties.constEnd()) + stream << i << it.value(); + }; + + const_cast(this)->d->border_key = key; + const_cast(this)->d->border_dirty = false; + } + + return d->border_key; +} + +/*! + \internal + Return true if the format has border format, otherwise return false. + */ +bool Format::hasBorderData() const +{ + if (!d) + return false; + + for (int i = FormatPrivate::P_Border_STARTID; i < FormatPrivate::P_Border_ENDID; ++i) { + if (hasProperty(i)) + return true; + } + return false; +} + +/*! + Return the fill pattern. +*/ +Format::FillPattern Format::fillPattern() const +{ + return static_cast(intProperty(FormatPrivate::P_Fill_Pattern, PatternNone)); +} + +/*! + Sets the fill pattern to the given \a pattern. +*/ +void Format::setFillPattern(FillPattern pattern) +{ + setProperty(FormatPrivate::P_Fill_Pattern, pattern, PatternNone); +} + +/*! + Returns the foreground color of the pattern. +*/ +QColor Format::patternForegroundColor() const +{ + return colorProperty(FormatPrivate::P_Fill_FgColor); +} + +/*! + Sets the foreground color of the pattern with the given \a color. +*/ +void Format::setPatternForegroundColor(const QColor &color) +{ + if (color.isValid() && !hasProperty(FormatPrivate::P_Fill_Pattern)) + setFillPattern(PatternSolid); + setProperty(FormatPrivate::P_Fill_FgColor, XlsxColor(color), XlsxColor()); +} + +/*! + Returns the background color of the pattern. +*/ +QColor Format::patternBackgroundColor() const +{ + return colorProperty(FormatPrivate::P_Fill_BgColor); +} + +/*! + Sets the background color of the pattern with the given \a color. +*/ +void Format::setPatternBackgroundColor(const QColor &color) +{ + if (color.isValid() && !hasProperty(FormatPrivate::P_Fill_Pattern)) + setFillPattern(PatternSolid); + setProperty(FormatPrivate::P_Fill_BgColor, XlsxColor(color), XlsxColor()); +} + +/*! + * \internal + */ +bool Format::fillIndexValid() const +{ + if (!hasFillData()) + return false; + return d->fill_index_valid; +} + +/*! + * \internal + */ +int Format::fillIndex() const +{ + if (fillIndexValid()) + return d->fill_index; + return 0; +} + +/*! + * \internal + */ +void Format::setFillIndex(int index) +{ + d->fill_index = index; + d->fill_index_valid = true; +} + +/*! + * \internal + */ +QByteArray Format::fillKey() const +{ + if (isEmpty()) + return QByteArray(); + + if (d->fill_dirty) { + QByteArray key; + QDataStream stream(&key, QIODevice::WriteOnly); + for (int i = FormatPrivate::P_Fill_STARTID; i < FormatPrivate::P_Fill_ENDID; ++i) { + auto it = d->properties.constFind(i); + if (it != d->properties.constEnd()) + stream << i << it.value(); + }; + + const_cast(this)->d->fill_key = key; + const_cast(this)->d->fill_dirty = false; + } + + return d->fill_key; +} + +/*! + \internal + Return true if the format has fill format, otherwise return false. + */ +bool Format::hasFillData() const +{ + if (!d) + return false; + + for (int i = FormatPrivate::P_Fill_STARTID; i < FormatPrivate::P_Fill_ENDID; ++i) { + if (hasProperty(i)) + return true; + } + return false; +} + +/*! + Returns whether the hidden protection property is set to true. +*/ +bool Format::hidden() const +{ + return boolProperty(FormatPrivate::P_Protection_Hidden); +} + +/*! + Sets the hidden protection property with the given \a hidden. +*/ +void Format::setHidden(bool hidden) +{ + setProperty(FormatPrivate::P_Protection_Hidden, hidden); +} + +/*! + Returns whether the locked protection property is set to true. +*/ +bool Format::locked() const +{ + return boolProperty(FormatPrivate::P_Protection_Locked); +} + +/*! + Sets the locked protection property with the given \a locked. +*/ +void Format::setLocked(bool locked) +{ + setProperty(FormatPrivate::P_Protection_Locked, locked); +} + +/*! + \internal + Return true if the format has protection data, otherwise return false. + */ +bool Format::hasProtectionData() const +{ + if (!d) + return false; + + if (hasProperty(FormatPrivate::P_Protection_Hidden) || + hasProperty(FormatPrivate::P_Protection_Locked)) { + return true; + } + return false; +} + +/*! + Merges the current format with the properties described by format \a modifier. + */ +void Format::mergeFormat(const Format &modifier) +{ + if (!modifier.isValid()) + return; + + if (!isValid()) { + d = modifier.d; + return; + } + + QMapIterator it(modifier.d->properties); + while (it.hasNext()) { + it.next(); + setProperty(it.key(), it.value()); + } +} + +/*! + Returns true if the format is valid; otherwise returns false. + */ +bool Format::isValid() const +{ + if (d) + return true; + return false; +} + +/*! + Returns true if the format is empty; otherwise returns false. + */ +bool Format::isEmpty() const +{ + if (!d) + return true; + return d->properties.isEmpty(); +} + +/*! + * \internal + */ +QByteArray Format::formatKey() const +{ + if (isEmpty()) + return QByteArray(); + + if (d->dirty) { + QByteArray key; + QDataStream stream(&key, QIODevice::WriteOnly); + + QMapIterator i(d->properties); + while (i.hasNext()) { + i.next(); + stream << i.key() << i.value(); + } + + d->formatKey = key; + d->dirty = false; + } + + return d->formatKey; +} + +/*! + * \internal + * Called by QXlsx::Styles or some unittests. + */ +void Format::setXfIndex(int index) +{ + if (!d) + d = new FormatPrivate; + d->xf_index = index; + d->xf_indexValid = true; +} + +/*! + * \internal + */ +int Format::xfIndex() const +{ + if (!d) + return -1; + return d->xf_index; +} + +/*! + * \internal + */ +bool Format::xfIndexValid() const +{ + if (!d) + return false; + return d->xf_indexValid; +} + +/*! + * \internal + * Called by QXlsx::Styles or some unittests. + */ +void Format::setDxfIndex(int index) +{ + if (!d) + d = new FormatPrivate; + d->dxf_index = index; + d->dxf_indexValid = true; +} + +/*! + * \internal + * Returns the index in the styles dxfs. + */ +int Format::dxfIndex() const +{ + if (!d) + return -1; + return d->dxf_index; +} + +/*! + * \internal + * Returns whether the dxf index is valid or not. + */ +bool Format::dxfIndexValid() const +{ + if (!d) + return false; + return d->dxf_indexValid; +} + +/*! + Returns true if the \a format is equal to this format. +*/ +bool Format::operator==(const Format &format) const +{ + return this->formatKey() == format.formatKey(); +} + +/*! + Returns true if the \a format is not equal to this format. +*/ +bool Format::operator!=(const Format &format) const +{ + return this->formatKey() != format.formatKey(); +} + +int Format::theme() const +{ + return d->theme; +} + +/*! + * \internal + */ +QVariant Format::property(int propertyId, const QVariant &defaultValue) const +{ + if (d) { + auto it = d->properties.constFind(propertyId); + if (it != d->properties.constEnd()) + return it.value(); + } + return defaultValue; +} + +/*! + * \internal + */ +void Format::setProperty(int propertyId, + const QVariant &value, + const QVariant &clearValue, + bool detach) +{ + if (!d) + d = new FormatPrivate; + + if (value != clearValue) { + auto it = d->properties.constFind(propertyId); + if (it != d->properties.constEnd() && it.value() == value) + return; + + if (detach) + d.detach(); + + d->properties[propertyId] = value; + } else { + if (!d->properties.contains(propertyId)) + return; + + if (detach) + d.detach(); + + d->properties.remove(propertyId); + } + + d->dirty = true; + d->xf_indexValid = false; + d->dxf_indexValid = false; + + if (propertyId >= FormatPrivate::P_Font_STARTID && propertyId < FormatPrivate::P_Font_ENDID) { + d->font_dirty = true; + d->font_index_valid = false; + } else if (propertyId >= FormatPrivate::P_Border_STARTID && + propertyId < FormatPrivate::P_Border_ENDID) { + d->border_dirty = true; + d->border_index_valid = false; + } else if (propertyId >= FormatPrivate::P_Fill_STARTID && + propertyId < FormatPrivate::P_Fill_ENDID) { + d->fill_dirty = true; + d->fill_index_valid = false; + } +} + +/*! + * \internal + */ +void Format::clearProperty(int propertyId) +{ + setProperty(propertyId, QVariant()); +} + +/*! + * \internal + */ +bool Format::hasProperty(int propertyId) const +{ + if (!d) + return false; + return d->properties.contains(propertyId); +} + +/*! + * \internal + */ +bool Format::boolProperty(int propertyId, bool defaultValue) const +{ + if (!hasProperty(propertyId)) + return defaultValue; + + const QVariant prop = d->properties[propertyId]; + if (prop.userType() != QMetaType::Bool) + return defaultValue; + return prop.toBool(); +} + +/*! + * \internal + */ +int Format::intProperty(int propertyId, int defaultValue) const +{ + if (!hasProperty(propertyId)) + return defaultValue; + + const QVariant prop = d->properties[propertyId]; + if (prop.userType() != QMetaType::Int) + return defaultValue; + return prop.toInt(); +} + +/*! + * \internal + */ +double Format::doubleProperty(int propertyId, double defaultValue) const +{ + if (!hasProperty(propertyId)) + return defaultValue; + + const QVariant prop = d->properties[propertyId]; + if (prop.userType() != QMetaType::Double && prop.userType() != QMetaType::Float) + return defaultValue; + return prop.toDouble(); +} + +/*! + * \internal + */ +QString Format::stringProperty(int propertyId, const QString &defaultValue) const +{ + if (!hasProperty(propertyId)) + return defaultValue; + + const QVariant prop = d->properties[propertyId]; + if (prop.userType() != QMetaType::QString) + return defaultValue; + return prop.toString(); +} + +/*! + * \internal + */ +QColor Format::colorProperty(int propertyId, const QColor &defaultValue) const +{ + if (!hasProperty(propertyId)) + return defaultValue; + + const QVariant prop = d->properties[propertyId]; + if (prop.userType() != qMetaTypeId()) + return defaultValue; + return qvariant_cast(prop).rgbColor(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const Format &f) +{ + dbg.nospace() << "QXlsx::Format(" << f.d->properties << ")"; + return dbg.space(); +} +#endif + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxmediafile.cpp b/QXlsx/source/xlsxmediafile.cpp new file mode 100644 index 0000000..f930f8f --- /dev/null +++ b/QXlsx/source/xlsxmediafile.cpp @@ -0,0 +1,82 @@ +// xlsxmediafile.cpp + +#include "xlsxmediafile_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +MediaFile::MediaFile(const QByteArray &bytes, const QString &suffix, const QString &mimeType) + : m_contents(bytes) + , m_suffix(suffix) + , m_mimeType(mimeType) + , m_index(0) + , m_indexValid(false) +{ + m_hashKey = QCryptographicHash::hash(m_contents, QCryptographicHash::Md5); +} + +MediaFile::MediaFile(const QString &fileName) + : m_fileName(fileName) + , m_index(0) + , m_indexValid(false) +{ +} + +void MediaFile::set(const QByteArray &bytes, const QString &suffix, const QString &mimeType) +{ + m_contents = bytes; + m_suffix = suffix; + m_mimeType = mimeType; + m_hashKey = QCryptographicHash::hash(m_contents, QCryptographicHash::Md5); + m_indexValid = false; +} + +void MediaFile::setFileName(const QString &name) +{ + m_fileName = name; +} + +QString MediaFile::fileName() const +{ + return m_fileName; +} + +QString MediaFile::suffix() const +{ + return m_suffix; +} + +QString MediaFile::mimeType() const +{ + return m_mimeType; +} + +QByteArray MediaFile::contents() const +{ + return m_contents; +} + +int MediaFile::index() const +{ + return m_index; +} + +bool MediaFile::isIndexValid() const +{ + return m_indexValid; +} + +void MediaFile::setIndex(int idx) +{ + m_index = idx; + m_indexValid = true; +} + +QByteArray MediaFile::hashKey() const +{ + return m_hashKey; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxnumformatparser.cpp b/QXlsx/source/xlsxnumformatparser.cpp new file mode 100644 index 0000000..eaf28e2 --- /dev/null +++ b/QXlsx/source/xlsxnumformatparser.cpp @@ -0,0 +1,74 @@ +// xlsxnumformatparser.cpp + +#include "xlsxnumformatparser_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +bool NumFormatParser::isDateTime(const QString &formatCode) +{ + for (int i = 0; i < formatCode.length(); ++i) { + const QChar &c = formatCode[i]; + + switch (c.unicode()) { + case '[': + // [h], [m], [s] are valid format for time + if (i < formatCode.length() - 2 && formatCode[i + 2] == QLatin1Char(']')) { + const QChar cc = formatCode[i + 1].toLower(); + if (cc == QLatin1Char('h') || cc == QLatin1Char('m') || cc == QLatin1Char('s')) + return true; + i += 2; + break; + } else { + // condition or color: don't care, ignore + while (i < formatCode.length() && formatCode[i] != QLatin1Char(']')) + ++i; + break; + } + + // quoted plain text block: don't care, ignore + case '"': + while (i < formatCode.length() - 1 && formatCode[++i] != QLatin1Char('"')) + ; + break; + + // escaped char: don't care, ignore + case '\\': + if (i < formatCode.length() - 1) + ++i; + break; + + // date/time can only be positive number, + // so only the first section of the format make sense. + case '#': // this is new an working // https://github.com/QtExcel/QXlsx/issues/190 + case ';': + return false; + break; + + // days + case 'D': + case 'd': + // years + case 'Y': + case 'y': + // hours + case 'H': + case 'h': + // seconds + case 'S': + case 's': + // minutes or months, depending on context + case 'M': + case 'm': + return true; + + default: + break; + } + } + return false; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxrelationships.cpp b/QXlsx/source/xlsxrelationships.cpp new file mode 100644 index 0000000..b1e9c1e --- /dev/null +++ b/QXlsx/source/xlsxrelationships.cpp @@ -0,0 +1,177 @@ +// xlsxrelationships.cpp + +#include "xlsxrelationships_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +const QLatin1String + schema_doc("http://schemas.openxmlformats.org/officeDocument/2006/relationships"); +const QLatin1String schema_msPackage("http://schemas.microsoft.com/office/2006/relationships"); +const QLatin1String schema_package("http://schemas.openxmlformats.org/package/2006/relationships"); +// const QString schema_worksheet = +// QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"); +Relationships::Relationships() +{ +} + +QList Relationships::documentRelationships(const QString &relativeType) const +{ + return relationships(schema_doc + relativeType); +} + +void Relationships::addDocumentRelationship(const QString &relativeType, const QString &target) +{ + addRelationship(schema_doc + relativeType, target); +} + +QList Relationships::msPackageRelationships(const QString &relativeType) const +{ + return relationships(schema_msPackage + relativeType); +} + +void Relationships::addMsPackageRelationship(const QString &relativeType, const QString &target) +{ + addRelationship(schema_msPackage + relativeType, target); +} + +QList Relationships::packageRelationships(const QString &relativeType) const +{ + return relationships(schema_package + relativeType); +} + +void Relationships::addPackageRelationship(const QString &relativeType, const QString &target) +{ + addRelationship(schema_package + relativeType, target); +} + +QList Relationships::worksheetRelationships(const QString &relativeType) const +{ + return relationships(schema_doc + relativeType); +} + +void Relationships::addWorksheetRelationship(const QString &relativeType, + const QString &target, + const QString &targetMode) +{ + addRelationship(schema_doc + relativeType, target, targetMode); +} + +QList Relationships::relationships(const QString &type) const +{ + QList res; + for (const XlsxRelationship &ship : m_relationships) { + if (ship.type == type) + res.append(ship); + } + return res; +} + +void Relationships::addRelationship(const QString &type, + const QString &target, + const QString &targetMode) +{ + XlsxRelationship relation; + relation.id = QStringLiteral("rId%1").arg(m_relationships.size() + 1); + relation.type = type; + relation.target = target; + relation.targetMode = targetMode; + + m_relationships.append(relation); +} + +void Relationships::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("Relationships")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/package/2006/relationships")); + for (const XlsxRelationship &relation : m_relationships) { + writer.writeStartElement(QStringLiteral("Relationship")); + writer.writeAttribute(QStringLiteral("Id"), relation.id); + writer.writeAttribute(QStringLiteral("Type"), relation.type); + writer.writeAttribute(QStringLiteral("Target"), relation.target); + if (!relation.targetMode.isNull()) + writer.writeAttribute(QStringLiteral("TargetMode"), relation.targetMode); + writer.writeEndElement(); + } + writer.writeEndElement(); // Relationships + writer.writeEndDocument(); +} + +QByteArray Relationships::saveToXmlData() const +{ + QByteArray data; + QBuffer buffer(&data); + buffer.open(QIODevice::WriteOnly); + saveToXmlFile(&buffer); + + return data; +} + +bool Relationships::loadFromXmlFile(QIODevice *device) +{ + clear(); + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QStringLiteral("Relationship")) { + QXmlStreamAttributes attributes = reader.attributes(); + XlsxRelationship relationship; + relationship.id = attributes.value(QLatin1String("Id")).toString(); + relationship.type = attributes.value(QLatin1String("Type")).toString(); + relationship.target = attributes.value(QLatin1String("Target")).toString(); + relationship.targetMode = attributes.value(QLatin1String("TargetMode")).toString(); + m_relationships.append(relationship); + } + } + + if (reader.hasError()) + return false; + } + return true; +} + +bool Relationships::loadFromXmlData(const QByteArray &data) +{ + QBuffer buffer; + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + return loadFromXmlFile(&buffer); +} + +XlsxRelationship Relationships::getRelationshipById(const QString &id) const +{ + for (const XlsxRelationship &ship : m_relationships) { + if (ship.id == id) + return ship; + } + return XlsxRelationship(); +} + +void Relationships::clear() +{ + m_relationships.clear(); +} + +int Relationships::count() const +{ + return m_relationships.count(); +} + +bool Relationships::isEmpty() const +{ + return m_relationships.isEmpty(); +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxrichstring.cpp b/QXlsx/source/xlsxrichstring.cpp new file mode 100644 index 0000000..af126a4 --- /dev/null +++ b/QXlsx/source/xlsxrichstring.cpp @@ -0,0 +1,327 @@ +// xlsxrichstring.cpp + +#include "xlsxrichstring.h" + +#include "xlsxformat_p.h" +#include "xlsxrichstring_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +RichStringPrivate::RichStringPrivate() + : _dirty(true) +{ +} + +RichStringPrivate::RichStringPrivate(const RichStringPrivate &other) + : QSharedData(other) + , fragmentTexts(other.fragmentTexts) + , fragmentFormats(other.fragmentFormats) + , _idKey(other.idKey()) + , _dirty(other._dirty) +{ +} + +RichStringPrivate::~RichStringPrivate() +{ +} + +/*! + \class RichString + \inmodule QtXlsx + \brief This class add support for the rich text string of the cell. +*/ + +/*! + Constructs a null string. + */ +RichString::RichString() + : d(new RichStringPrivate) +{ +} + +/*! + Constructs a plain string with the given \a text. +*/ +RichString::RichString(const QString &text) + : d(new RichStringPrivate) +{ + addFragment(text, Format()); +} + +/*! + Constructs a copy of \a other. + */ +RichString::RichString(const RichString &other) + : d(other.d) +{ +} + +/*! + Destructs the string. + */ +RichString::~RichString() +{ +} + +/*! + Assigns \a other to this string and returns a reference to this string + */ +RichString &RichString::operator=(const RichString &other) +{ + this->d = other.d; + return *this; +} + +/*! + Returns the rich string as a QVariant +*/ +RichString::operator QVariant() const +{ + const auto &cref +#if QT_VERSION >= 0x060000 // Qt 6.0 or over + = QMetaType::fromType(); +#else + = qMetaTypeId(); +#endif + return QVariant(cref, this); +} + +/*! + Returns true if this is rich text string. + */ +bool RichString::isRichString() const +{ + if (fragmentCount() > 1) // Is this enough?? + return true; + return false; +} + +/*! + Returns true is this is an Null string. + */ +bool RichString::isNull() const +{ + return d->fragmentTexts.size() == 0; +} + +/*! + Returns true is this is an empty string. + */ +bool RichString::isEmtpy() const +{ + for (const auto &str : d->fragmentTexts) { + if (!str.isEmpty()) + return false; + } + + return true; +} + +/*! + Converts to plain text string. +*/ +QString RichString::toPlainString() const +{ + if (isEmtpy()) + return QString(); + if (d->fragmentTexts.size() == 1) + return d->fragmentTexts[0]; + + return d->fragmentTexts.join(QString()); +} + +/*! + Converts to html string +*/ +QString RichString::toHtml() const +{ + //: Todo + return QString(); +} + +/*! + Replaces the entire contents of the document + with the given HTML-formatted text in the \a text string +*/ +void RichString::setHtml(const QString &text) +{ + QTextDocument doc; + doc.setHtml(text); + QTextBlock block = doc.firstBlock(); + QTextBlock::iterator it; + for (it = block.begin(); !(it.atEnd()); ++it) { + QTextFragment textFragment = it.fragment(); + if (textFragment.isValid()) { + Format fmt; + fmt.setFont(textFragment.charFormat().font()); + fmt.setFontColor(textFragment.charFormat().foreground().color()); + addFragment(textFragment.text(), fmt); + } + } +} + +/*! + Returns fragment count. + */ +int RichString::fragmentCount() const +{ + return d->fragmentTexts.size(); +} + +/*! + Appends a fragment with the given \a text and \a format. + */ +void RichString::addFragment(const QString &text, const Format &format) +{ + d->fragmentTexts.append(text); + d->fragmentFormats.append(format); + d->_dirty = true; +} + +/*! + Returns fragment text at the position \a index. + */ +QString RichString::fragmentText(int index) const +{ + if (index < 0 || index >= fragmentCount()) + return QString(); + + return d->fragmentTexts[index]; +} + +/*! + Returns fragment format at the position \a index. + */ +Format RichString::fragmentFormat(int index) const +{ + if (index < 0 || index >= fragmentCount()) + return Format(); + + return d->fragmentFormats[index]; +} + +/*! + * \internal + */ +QByteArray RichStringPrivate::idKey() const +{ + if (_dirty) { + RichStringPrivate *rs = const_cast(this); + QByteArray bytes; + if (fragmentTexts.size() == 1) { + bytes = fragmentTexts[0].toUtf8(); + } else { + // Generate a hash value base on QByteArray ? + bytes.append("@@QtXlsxRichString="); + for (int i = 0; i < fragmentTexts.size(); ++i) { + bytes.append("@Text"); + bytes.append(fragmentTexts[i].toUtf8()); + bytes.append("@Format"); + if (fragmentFormats[i].hasFontData()) + bytes.append(fragmentFormats[i].fontKey()); + } + } + rs->_idKey = bytes; + rs->_dirty = false; + } + + return _idKey; +} + +/*! + Returns true if this string \a rs1 is equal to string \a rs2; + otherwise returns false. + */ +bool operator==(const RichString &rs1, const RichString &rs2) +{ + if (rs1.fragmentCount() != rs2.fragmentCount()) + return false; + + return rs1.d->idKey() == rs2.d->idKey(); +} + +/*! + Returns true if this string \a rs1 is not equal to string \a rs2; + otherwise returns false. + */ +bool operator!=(const RichString &rs1, const RichString &rs2) +{ + if (rs1.fragmentCount() != rs2.fragmentCount()) + return true; + + return rs1.d->idKey() != rs2.d->idKey(); +} + +/*! + * \internal + */ +bool operator<(const RichString &rs1, const RichString &rs2) +{ + return rs1.d->idKey() < rs2.d->idKey(); +} + +/*! + \overload + Returns true if this string \a rs1 is equal to string \a rs2; + otherwise returns false. + */ +bool operator==(const RichString &rs1, const QString &rs2) +{ + if (rs1.fragmentCount() == 1 && rs1.fragmentText(0) == rs2) // format == 0 + return true; + + return false; +} + +/*! + \overload + Returns true if this string \a rs1 is not equal to string \a rs2; + otherwise returns false. + */ +bool operator!=(const RichString &rs1, const QString &rs2) +{ + if (rs1.fragmentCount() == 1 && rs1.fragmentText(0) == rs2) // format == 0 + return false; + + return true; +} + +/*! + \overload + Returns true if this string \a rs1 is equal to string \a rs2; + otherwise returns false. + */ +bool operator==(const QString &rs1, const RichString &rs2) +{ + return rs2 == rs1; +} + +/*! + \overload + Returns true if this string \a rs1 is not equal to string \a rs2; + otherwise returns false. + */ +bool operator!=(const QString &rs1, const RichString &rs2) +{ + return rs2 != rs1; +} + +uint qHash(const RichString &rs, uint seed) Q_DECL_NOTHROW +{ + return qHash(rs.d->idKey(), seed); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const RichString &rs) +{ + dbg.nospace() << "QXlsx::RichString(" << rs.d->fragmentTexts << ")"; + return dbg.space(); +} +#endif + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxsharedstrings.cpp b/QXlsx/source/xlsxsharedstrings.cpp new file mode 100644 index 0000000..d2b20aa --- /dev/null +++ b/QXlsx/source/xlsxsharedstrings.cpp @@ -0,0 +1,394 @@ +// xlsxsharedstrings.cpp + +#include "xlsxcolor_p.h" +#include "xlsxformat_p.h" +#include "xlsxrichstring.h" +#include "xlsxsharedstrings_p.h" +#include "xlsxutility_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +/* + * Note that, when we open an existing .xlsx file (broken file?), + * duplicated string items may exist in the shared string table. + * + * In such case, the size of stringList will larger than stringTable. + * Duplicated items can be removed once we loaded all the worksheets. + */ + +SharedStrings::SharedStrings(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ + m_stringCount = 0; +} + +int SharedStrings::count() const +{ + return m_stringCount; +} + +bool SharedStrings::isEmpty() const +{ + return m_stringList.isEmpty(); +} + +int SharedStrings::addSharedString(const QString &string) +{ + return addSharedString(RichString(string)); +} + +int SharedStrings::addSharedString(const RichString &string) +{ + m_stringCount += 1; + + auto it = m_stringTable.find(string); + if (it != m_stringTable.end()) { + it->count += 1; + return it->index; + } + + int index = m_stringList.size(); + m_stringTable[string] = XlsxSharedStringInfo(index); + m_stringList.append(string); + return index; +} + +void SharedStrings::incRefByStringIndex(int idx) +{ + if (idx < 0 || idx >= m_stringList.size()) { + qDebug("SharedStrings: invalid index"); + return; + } + + addSharedString(m_stringList[idx]); +} + +/* + * Broken, don't use. + */ +void SharedStrings::removeSharedString(const QString &string) +{ + removeSharedString(RichString(string)); +} + +/* + * Broken, don't use. + */ +void SharedStrings::removeSharedString(const RichString &string) +{ + auto it = m_stringTable.find(string); + if (it == m_stringTable.end()) + return; + + m_stringCount -= 1; + + it->count -= 1; + + if (it->count <= 0) { + for (int i = it->index + 1; i < m_stringList.size(); ++i) + m_stringTable[m_stringList[i]].index -= 1; + + m_stringList.removeAt(it->index); + m_stringTable.remove(string); + } +} + +int SharedStrings::getSharedStringIndex(const QString &string) const +{ + return getSharedStringIndex(RichString(string)); +} + +int SharedStrings::getSharedStringIndex(const RichString &string) const +{ + auto it = m_stringTable.constFind(string); + if (it != m_stringTable.constEnd()) + return it->index; + return -1; +} + +RichString SharedStrings::getSharedString(int index) const +{ + if (index < m_stringList.count() && index >= 0) + return m_stringList[index]; + return RichString(); +} + +QList SharedStrings::getSharedStrings() const +{ + return m_stringList; +} + +void SharedStrings::writeRichStringPart_rPr(QXmlStreamWriter &writer, const Format &format) const +{ + if (!format.hasFontData()) + return; + + if (format.fontBold()) + writer.writeEmptyElement(QStringLiteral("b")); + if (format.fontItalic()) + writer.writeEmptyElement(QStringLiteral("i")); + if (format.fontStrikeOut()) + writer.writeEmptyElement(QStringLiteral("strike")); + if (format.fontOutline()) + writer.writeEmptyElement(QStringLiteral("outline")); + if (format.boolProperty(FormatPrivate::P_Font_Shadow)) + writer.writeEmptyElement(QStringLiteral("shadow")); + if (format.hasProperty(FormatPrivate::P_Font_Underline)) { + Format::FontUnderline u = format.fontUnderline(); + if (u != Format::FontUnderlineNone) { + writer.writeEmptyElement(QStringLiteral("u")); + if (u == Format::FontUnderlineDouble) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("double")); + else if (u == Format::FontUnderlineSingleAccounting) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("singleAccounting")); + else if (u == Format::FontUnderlineDoubleAccounting) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("doubleAccounting")); + } + } + if (format.hasProperty(FormatPrivate::P_Font_Script)) { + Format::FontScript s = format.fontScript(); + if (s != Format::FontScriptNormal) { + writer.writeEmptyElement(QStringLiteral("vertAlign")); + if (s == Format::FontScriptSuper) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("superscript")); + else + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("subscript")); + } + } + + if (format.hasProperty(FormatPrivate::P_Font_Size)) { + writer.writeEmptyElement(QStringLiteral("sz")); + writer.writeAttribute(QStringLiteral("val"), QString::number(format.fontSize())); + } + + if (format.hasProperty(FormatPrivate::P_Font_Color)) { + XlsxColor color = format.property(FormatPrivate::P_Font_Color).value(); + color.saveToXml(writer); + } + + if (!format.fontName().isEmpty()) { + writer.writeEmptyElement(QStringLiteral("rFont")); + writer.writeAttribute(QStringLiteral("val"), format.fontName()); + } + if (format.hasProperty(FormatPrivate::P_Font_Family)) { + writer.writeEmptyElement(QStringLiteral("family")); + writer.writeAttribute(QStringLiteral("val"), + QString::number(format.intProperty(FormatPrivate::P_Font_Family))); + } + + if (format.hasProperty(FormatPrivate::P_Font_Scheme)) { + writer.writeEmptyElement(QStringLiteral("scheme")); + writer.writeAttribute(QStringLiteral("val"), + format.stringProperty(FormatPrivate::P_Font_Scheme)); + } +} + +void SharedStrings::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + + if (m_stringList.size() != m_stringTable.size()) { + // Duplicated string items exist in m_stringList + // Clean up can not be done here, as the indices + // have been used when we save the worksheets part. + } + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("sst")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_stringCount)); + writer.writeAttribute(QStringLiteral("uniqueCount"), QString::number(m_stringList.size())); + + for (const RichString &string : m_stringList) { + writer.writeStartElement(QStringLiteral("si")); + if (string.isRichString()) { + // Rich text string + for (int i = 0; i < string.fragmentCount(); ++i) { + writer.writeStartElement(QStringLiteral("r")); + if (string.fragmentFormat(i).hasFontData()) { + writer.writeStartElement(QStringLiteral("rPr")); + writeRichStringPart_rPr(writer, string.fragmentFormat(i)); + writer.writeEndElement(); // rPr + } + writer.writeStartElement(QStringLiteral("t")); + if (isSpaceReserveNeeded(string.fragmentText(i))) + writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve")); + writer.writeCharacters(string.fragmentText(i)); + writer.writeEndElement(); // t + + writer.writeEndElement(); // r + } + } else { + writer.writeStartElement(QStringLiteral("t")); + QString pString = string.toPlainString(); + if (isSpaceReserveNeeded(pString)) + writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve")); + writer.writeCharacters(pString); + writer.writeEndElement(); // t + } + writer.writeEndElement(); // si + } + + writer.writeEndElement(); // sst + writer.writeEndDocument(); +} + +void SharedStrings::readString(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("si")); + + RichString richString; + + while (!reader.atEnd() && !(reader.name() == QLatin1String("si") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("r")) + readRichStringPart(reader, richString); + else if (reader.name() == QLatin1String("t")) + readPlainStringPart(reader, richString); + } + } + + int idx = m_stringList.size(); + m_stringTable[richString] = XlsxSharedStringInfo(idx, 0); + m_stringList.append(richString); +} + +void SharedStrings::readRichStringPart(QXmlStreamReader &reader, RichString &richString) +{ + Q_ASSERT(reader.name() == QLatin1String("r")); + + QString text; + Format format; + while (!reader.atEnd() && !(reader.name() == QLatin1String("r") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("rPr")) { + format = readRichStringPart_rPr(reader); + } else if (reader.name() == QLatin1String("t")) { + text = reader.readElementText(); + } + } + } + richString.addFragment(text, format); +} + +void SharedStrings::readPlainStringPart(QXmlStreamReader &reader, RichString &richString) +{ + Q_ASSERT(reader.name() == QLatin1String("t")); + + // QXmlStreamAttributes attributes = reader.attributes(); + + // NOTICE: CHECK POINT + QString text = reader.readElementText(); + richString.addFragment(text, Format()); +} + +Format SharedStrings::readRichStringPart_rPr(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("rPr")); + Format format; + while (!reader.atEnd() && !(reader.name() == QLatin1String("rPr") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + QXmlStreamAttributes attributes = reader.attributes(); + if (reader.name() == QLatin1String("rFont")) { + format.setFontName(attributes.value(QLatin1String("val")).toString()); + } else if (reader.name() == QLatin1String("charset")) { + format.setProperty(FormatPrivate::P_Font_Charset, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("family")) { + format.setProperty(FormatPrivate::P_Font_Family, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("b")) { + format.setFontBold(true); + } else if (reader.name() == QLatin1String("i")) { + format.setFontItalic(true); + } else if (reader.name() == QLatin1String("strike")) { + format.setFontStrikeOut(true); + } else if (reader.name() == QLatin1String("outline")) { + format.setFontOutline(true); + } else if (reader.name() == QLatin1String("shadow")) { + format.setProperty(FormatPrivate::P_Font_Shadow, true); + } else if (reader.name() == QLatin1String("condense")) { + format.setProperty(FormatPrivate::P_Font_Condense, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("extend")) { + format.setProperty(FormatPrivate::P_Font_Extend, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("color")) { + XlsxColor color; + color.loadFromXml(reader); + format.setProperty(FormatPrivate::P_Font_Color, color); + } else if (reader.name() == QLatin1String("sz")) { + format.setFontSize(attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("u")) { + QString value = attributes.value(QLatin1String("val")).toString(); + if (value == QLatin1String("double")) + format.setFontUnderline(Format::FontUnderlineDouble); + else if (value == QLatin1String("doubleAccounting")) + format.setFontUnderline(Format::FontUnderlineDoubleAccounting); + else if (value == QLatin1String("singleAccounting")) + format.setFontUnderline(Format::FontUnderlineSingleAccounting); + else + format.setFontUnderline(Format::FontUnderlineSingle); + } else if (reader.name() == QLatin1String("vertAlign")) { + QString value = attributes.value(QLatin1String("val")).toString(); + if (value == QLatin1String("superscript")) + format.setFontScript(Format::FontScriptSuper); + else if (value == QLatin1String("subscript")) + format.setFontScript(Format::FontScriptSub); + } else if (reader.name() == QLatin1String("scheme")) { + format.setProperty(FormatPrivate::P_Font_Scheme, + attributes.value(QLatin1String("val")).toString()); + } + } + } + return format; +} + +bool SharedStrings::loadFromXmlFile(QIODevice *device) +{ + QXmlStreamReader reader(device); + int count = 0; + bool hasUniqueCountAttr = true; + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("sst")) { + QXmlStreamAttributes attributes = reader.attributes(); + if ((hasUniqueCountAttr = attributes.hasAttribute(QLatin1String("uniqueCount")))) + count = attributes.value(QLatin1String("uniqueCount")).toInt(); + } else if (reader.name() == QLatin1String("si")) { + readString(reader); + } + } + } + + if (hasUniqueCountAttr && m_stringList.size() != count) { + qDebug("Error: Shared string count"); + return false; + } + + if (m_stringList.size() != m_stringTable.size()) { + // qDebug("Warning: Duplicated items exist in shared string table."); + // Nothing we can do here, as indices of the strings will be used when loading sheets. + } + + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxsimpleooxmlfile.cpp b/QXlsx/source/xlsxsimpleooxmlfile.cpp new file mode 100644 index 0000000..dda4900 --- /dev/null +++ b/QXlsx/source/xlsxsimpleooxmlfile.cpp @@ -0,0 +1,37 @@ +// xlsxsimpleooxmlfile.cpp + +#include "xlsxsimpleooxmlfile_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +SimpleOOXmlFile::SimpleOOXmlFile(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ +} + +void SimpleOOXmlFile::saveToXmlFile(QIODevice *device) const +{ + device->write(xmlData); +} + +QByteArray SimpleOOXmlFile::saveToXmlData() const +{ + return xmlData; +} + +bool SimpleOOXmlFile::loadFromXmlData(const QByteArray &data) +{ + xmlData = data; + return true; +} + +bool SimpleOOXmlFile::loadFromXmlFile(QIODevice *device) +{ + xmlData = device->readAll(); + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxstyles.cpp b/QXlsx/source/xlsxstyles.cpp new file mode 100644 index 0000000..50d00d2 --- /dev/null +++ b/QXlsx/source/xlsxstyles.cpp @@ -0,0 +1,1462 @@ +// xlsxstyles.cpp + +#include "xlsxcolor_p.h" +#include "xlsxformat_p.h" +#include "xlsxglobal.h" +#include "xlsxstyles_p.h" +#include "xlsxutility_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +/* + When loading from existing .xlsx file. we should create a clean styles object. + otherwise, default formats should be added. + +*/ +Styles::Styles(CreateFlag flag) + : AbstractOOXmlFile(flag) + , m_nextCustomNumFmtId(176) + , m_isIndexedColorsDefault(true) + , m_emptyFormatAdded(false) +{ + //! Fix me. Should the custom num fmt Id starts with 164 or 176 or others?? + + //! Fix me! Where should we put these register code? + + // issue #172, #89 +#if QT_VERSION >= 0x060000 // Qt 6.0 or over + if (QMetaType::fromName("XlsxColor").isRegistered()) +#else + if (QMetaType::type("XlsxColor") == QMetaType::UnknownType) +#endif + { + qRegisterMetaType("XlsxColor"); + +#if QT_VERSION >= 0x060000 + // Qt 6 + + /// TODO: + +#else + // Qt 5 + + qRegisterMetaTypeStreamOperators("XlsxColor"); + + QMetaType::registerDebugStreamOperator(); + +#endif + } + + if (flag == F_NewFromScratch) { + // Add default Format + Format defaultFmt; + addXfFormat(defaultFmt); + + // Add another fill format + Format fillFmt; + fillFmt.setFillPattern(Format::PatternGray125); + m_fillsList.append(fillFmt); + m_fillsHash.insert(fillFmt.fillKey(), fillFmt); + } +} + +Styles::~Styles() +{ +} + +Format Styles::xfFormat(int idx) const +{ + if (idx < 0 || idx >= m_xf_formatsList.size()) + return Format(); + + return m_xf_formatsList[idx]; +} + +Format Styles::dxfFormat(int idx) const +{ + if (idx < 0 || idx >= m_dxf_formatsList.size()) + return Format(); + + return m_dxf_formatsList[idx]; +} + +// dev74 issue#57 +void Styles::fixNumFmt(const Format &format) +{ + if (!format.hasNumFmtData()) + return; + + if (format.hasProperty(FormatPrivate::P_NumFmt_Id) && + !format.stringProperty(FormatPrivate::P_NumFmt_FormatCode).isEmpty()) { + return; + } + + if (m_builtinNumFmtsHash.isEmpty()) { + m_builtinNumFmtsHash.insert(QStringLiteral("General"), 0); + m_builtinNumFmtsHash.insert(QStringLiteral("0"), 1); + m_builtinNumFmtsHash.insert(QStringLiteral("0.00"), 2); + m_builtinNumFmtsHash.insert(QStringLiteral("#,##0"), 3); + m_builtinNumFmtsHash.insert(QStringLiteral("#,##0.00"), 4); + // m_builtinNumFmtsHash.insert(QStringLiteral("($#,##0_);($#,##0)"), 5); + // m_builtinNumFmtsHash.insert(QStringLiteral("($#,##0_);[Red]($#,##0)"), 6); + // m_builtinNumFmtsHash.insert(QStringLiteral("($#,##0.00_);($#,##0.00)"), 7); + // m_builtinNumFmtsHash.insert(QStringLiteral("($#,##0.00_);[Red]($#,##0.00)"), + // 8); + m_builtinNumFmtsHash.insert(QStringLiteral("0%"), 9); + m_builtinNumFmtsHash.insert(QStringLiteral("0.00%"), 10); + m_builtinNumFmtsHash.insert(QStringLiteral("0.00E+00"), 11); + m_builtinNumFmtsHash.insert(QStringLiteral("# ?/?"), 12); + m_builtinNumFmtsHash.insert(QStringLiteral("# ?\?/??"), + 13); // Note: "??/" is a c++ trigraph, so escape one "?" + m_builtinNumFmtsHash.insert(QStringLiteral("m/d/yy"), 14); + m_builtinNumFmtsHash.insert(QStringLiteral("d-mmm-yy"), 15); + m_builtinNumFmtsHash.insert(QStringLiteral("d-mmm"), 16); + m_builtinNumFmtsHash.insert(QStringLiteral("mmm-yy"), 17); + m_builtinNumFmtsHash.insert(QStringLiteral("h:mm AM/PM"), 18); + m_builtinNumFmtsHash.insert(QStringLiteral("h:mm:ss AM/PM"), 19); + m_builtinNumFmtsHash.insert(QStringLiteral("h:mm"), 20); + m_builtinNumFmtsHash.insert(QStringLiteral("h:mm:ss"), 21); + m_builtinNumFmtsHash.insert(QStringLiteral("m/d/yy h:mm"), 22); + + m_builtinNumFmtsHash.insert(QStringLiteral("(#,##0_);(#,##0)"), 37); + m_builtinNumFmtsHash.insert(QStringLiteral("(#,##0_);[Red](#,##0)"), 38); + m_builtinNumFmtsHash.insert(QStringLiteral("(#,##0.00_);(#,##0.00)"), 39); + m_builtinNumFmtsHash.insert(QStringLiteral("(#,##0.00_);[Red](#,##0.00)"), 40); + // m_builtinNumFmtsHash.insert(QStringLiteral("_(* #,##0_);_(* (#,##0);_(* + // \"-\"_);_(_)"), 41); m_builtinNumFmtsHash.insert(QStringLiteral("_($* + // #,##0_);_($* (#,##0);_($* \"-\"_);_(_)"), 42); + // m_builtinNumFmtsHash.insert(QStringLiteral("_(* #,##0.00_);_(* (#,##0.00);_(* + // \"-\"??_);_(_)"), 43); m_builtinNumFmtsHash.insert(QStringLiteral("_($* + // #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(_)"), 44); + m_builtinNumFmtsHash.insert(QStringLiteral("mm:ss"), 45); + m_builtinNumFmtsHash.insert(QStringLiteral("[h]:mm:ss"), 46); + m_builtinNumFmtsHash.insert(QStringLiteral("mm:ss.0"), 47); + m_builtinNumFmtsHash.insert(QStringLiteral("##0.0E+0"), 48); + m_builtinNumFmtsHash.insert(QStringLiteral("@"), 49); + + // dev74 + // m_builtinNumFmtsHash.insert(QStringLiteral("0.####"), 176); + } + + const auto &str = format.numberFormat(); + if (!str.isEmpty()) { + QHash>::ConstIterator cIt; + // Assign proper number format index + const auto &it = m_builtinNumFmtsHash.constFind(str); + if (it != m_builtinNumFmtsHash.constEnd()) { + const_cast(&format)->fixNumberFormat(it.value(), str); + } else if ((cIt = m_customNumFmtsHash.constFind(str)) != m_customNumFmtsHash.constEnd()) { + const_cast(&format)->fixNumberFormat(cIt.value()->formatIndex, str); + } else { + // Assign a new fmt Id. + const_cast(&format)->fixNumberFormat(m_nextCustomNumFmtId, str); + + QSharedPointer fmt(new XlsxFormatNumberData); + fmt->formatIndex = m_nextCustomNumFmtId; + fmt->formatString = str; + m_customNumFmtIdMap.insert(m_nextCustomNumFmtId, fmt); + m_customNumFmtsHash.insert(str, fmt); + + m_nextCustomNumFmtId += 1; + } + } else { + const auto id = format.numberFormatIndex(); + // Assign proper format code, this is needed by dxf format + const auto &it = m_customNumFmtIdMap.constFind(id); + if (it != m_customNumFmtIdMap.constEnd()) { + const_cast(&format)->fixNumberFormat(id, it.value()->formatString); + } else { + bool found = false; + for (auto &&it = m_builtinNumFmtsHash.constBegin(); + it != m_builtinNumFmtsHash.constEnd(); + ++it) { + if (it.value() == id) { + const_cast(&format)->fixNumberFormat(id, it.key()); + found = true; + break; + } + } + + if (!found) { + // Wrong numFmt + const_cast(&format)->fixNumberFormat(id, QStringLiteral("General")); + } + } + } +} + +/* + Assign index to Font/Fill/Border and Format + + When \a force is true, add the format to the format list, even other format has + the same key have been in. + This is useful when reading existing .xlsx files which may contains duplicated formats. +*/ +void Styles::addXfFormat(const Format &format, bool force) +{ + if (format.isEmpty()) { + // Try do something for empty Format. + if (m_emptyFormatAdded && !force) + return; + + m_emptyFormatAdded = true; + } + + // numFmt + if (format.hasNumFmtData() && !format.hasProperty(FormatPrivate::P_NumFmt_Id)) { + fixNumFmt(format); + } + + // Font + const auto &fontIt = m_fontsHash.constFind(format.fontKey()); + if (format.hasFontData() && !format.fontIndexValid()) { + // Assign proper font index, if has font data. + if (fontIt == m_fontsHash.constEnd()) + const_cast(&format)->setFontIndex(m_fontsList.size()); + else + const_cast(&format)->setFontIndex(fontIt->fontIndex()); + } + if (fontIt == m_fontsHash.constEnd()) { + // Still a valid font if the format has no fontData. (All font properties are default) + m_fontsList.append(format); + m_fontsHash[format.fontKey()] = format; + } + + // Fill + const auto &fillIt = m_fillsHash.constFind(format.fillKey()); + if (format.hasFillData() && !format.fillIndexValid()) { + // Assign proper fill index, if has fill data. + if (fillIt == m_fillsHash.constEnd()) + const_cast(&format)->setFillIndex(m_fillsList.size()); + else + const_cast(&format)->setFillIndex(fillIt->fillIndex()); + } + if (fillIt == m_fillsHash.constEnd()) { + // Still a valid fill if the format has no fillData. (All fill properties are default) + m_fillsList.append(format); + m_fillsHash[format.fillKey()] = format; + } + + // Border + const auto &borderIt = m_bordersHash.constFind(format.borderKey()); + if (format.hasBorderData() && !format.borderIndexValid()) { + // Assign proper border index, if has border data. + if (borderIt == m_bordersHash.constEnd()) + const_cast(&format)->setBorderIndex(m_bordersList.size()); + else + const_cast(&format)->setBorderIndex(borderIt->borderIndex()); + } + if (borderIt == m_bordersHash.constEnd()) { + // Still a valid border if the format has no borderData. (All border properties are default) + m_bordersList.append(format); + m_bordersHash[format.borderKey()] = format; + } + + // Format + const auto &formatIt = m_xf_formatsHash.constFind(format.formatKey()); + if (!format.isEmpty() && !format.xfIndexValid()) { + if (formatIt == m_xf_formatsHash.constEnd()) + const_cast(&format)->setXfIndex(m_xf_formatsList.size()); + else + const_cast(&format)->setXfIndex(formatIt->xfIndex()); + } + + if (formatIt == m_xf_formatsHash.constEnd() || force) { + m_xf_formatsList.append(format); + m_xf_formatsHash[format.formatKey()] = format; + } +} + +void Styles::addDxfFormat(const Format &format, bool force) +{ + // numFmt + if (format.hasNumFmtData()) { + fixNumFmt(format); + } + + const auto &formatIt = m_dxf_formatsHash.constFind(format.formatKey()); + if (!format.isEmpty() && !format.dxfIndexValid()) { + if (formatIt == m_dxf_formatsHash.constEnd()) // m_xf_formatsHash.constEnd()) // issue #108 + { + const_cast(&format)->setDxfIndex(m_dxf_formatsList.size()); + } else { + const_cast(&format)->setDxfIndex(formatIt->dxfIndex()); + } + } + + if (formatIt == m_xf_formatsHash.constEnd() || force) { + m_dxf_formatsList.append(format); + m_dxf_formatsHash[format.formatKey()] = format; + } +} + +void Styles::saveToXmlFile(QIODevice *device) const +{ + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("styleSheet")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); + + writeNumFmts(writer); + writeFonts(writer); + writeFills(writer); + writeBorders(writer); + + writer.writeStartElement(QStringLiteral("cellStyleXfs")); + writer.writeAttribute(QStringLiteral("count"), QStringLiteral("1")); + writer.writeStartElement(QStringLiteral("xf")); + writer.writeAttribute(QStringLiteral("numFmtId"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("fontId"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("fillId"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("borderId"), QStringLiteral("0")); + writer.writeEndElement(); // xf + writer.writeEndElement(); // cellStyleXfs + + writeCellXfs(writer); + + writer.writeStartElement(QStringLiteral("cellStyles")); + writer.writeAttribute(QStringLiteral("count"), QStringLiteral("1")); + writer.writeStartElement(QStringLiteral("cellStyle")); + writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Normal")); + writer.writeAttribute(QStringLiteral("xfId"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("builtinId"), QStringLiteral("0")); + writer.writeEndElement(); // cellStyle + writer.writeEndElement(); // cellStyles + + writeDxfs(writer); + + writer.writeStartElement(QStringLiteral("tableStyles")); + writer.writeAttribute(QStringLiteral("count"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("defaultTableStyle"), QStringLiteral("TableStyleMedium9")); + writer.writeAttribute(QStringLiteral("defaultPivotStyle"), QStringLiteral("PivotStyleLight16")); + writer.writeEndElement(); // tableStyles + + writeColors(writer); + + writer.writeEndElement(); // styleSheet + writer.writeEndDocument(); +} + +void Styles::writeNumFmts(QXmlStreamWriter &writer) const +{ + if (m_customNumFmtIdMap.size() == 0) + return; + + writer.writeStartElement(QStringLiteral("numFmts")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_customNumFmtIdMap.count())); + + QMapIterator> it(m_customNumFmtIdMap); + while (it.hasNext()) { + it.next(); + writer.writeEmptyElement(QStringLiteral("numFmt")); + writer.writeAttribute(QStringLiteral("numFmtId"), QString::number(it.value()->formatIndex)); + writer.writeAttribute(QStringLiteral("formatCode"), it.value()->formatString); + } + writer.writeEndElement(); // numFmts +} + +/* + */ +void Styles::writeFonts(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("fonts")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_fontsList.count())); + for (const auto &font : m_fontsList) { + writeFont(writer, font, false); + } + writer.writeEndElement(); // fonts +} + +void Styles::writeFont(QXmlStreamWriter &writer, const Format &format, bool isDxf) const +{ + writer.writeStartElement(QStringLiteral("font")); + + // The condense and extend elements are mainly used in dxf format + if (format.hasProperty(FormatPrivate::P_Font_Condense) && + !format.boolProperty(FormatPrivate::P_Font_Condense)) { + writer.writeEmptyElement(QStringLiteral("condense")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("0")); + } + if (format.hasProperty(FormatPrivate::P_Font_Extend) && + !format.boolProperty(FormatPrivate::P_Font_Extend)) { + writer.writeEmptyElement(QStringLiteral("extend")); + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("0")); + } + + if (format.fontBold()) + writer.writeEmptyElement(QStringLiteral("b")); + if (format.fontItalic()) + writer.writeEmptyElement(QStringLiteral("i")); + if (format.fontStrikeOut()) + writer.writeEmptyElement(QStringLiteral("strike")); + if (format.fontOutline()) + writer.writeEmptyElement(QStringLiteral("outline")); + if (format.boolProperty(FormatPrivate::P_Font_Shadow)) + writer.writeEmptyElement(QStringLiteral("shadow")); + if (format.hasProperty(FormatPrivate::P_Font_Underline)) { + Format::FontUnderline u = format.fontUnderline(); + if (u != Format::FontUnderlineNone) { + writer.writeEmptyElement(QStringLiteral("u")); + if (u == Format::FontUnderlineDouble) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("double")); + else if (u == Format::FontUnderlineSingleAccounting) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("singleAccounting")); + else if (u == Format::FontUnderlineDoubleAccounting) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("doubleAccounting")); + } + } + if (format.hasProperty(FormatPrivate::P_Font_Script)) { + Format::FontScript s = format.fontScript(); + if (s != Format::FontScriptNormal) { + writer.writeEmptyElement(QStringLiteral("vertAlign")); + if (s == Format::FontScriptSuper) + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("superscript")); + else + writer.writeAttribute(QStringLiteral("val"), QStringLiteral("subscript")); + } + } + + if (!isDxf && format.hasProperty(FormatPrivate::P_Font_Size)) { + writer.writeEmptyElement(QStringLiteral("sz")); + writer.writeAttribute(QStringLiteral("val"), QString::number(format.fontSize())); + } + + if (format.hasProperty(FormatPrivate::P_Font_Color)) { + XlsxColor color = format.property(FormatPrivate::P_Font_Color).value(); + color.saveToXml(writer); + } + + if (!isDxf) { + if (!format.fontName().isEmpty()) { + writer.writeEmptyElement(QStringLiteral("name")); + writer.writeAttribute(QStringLiteral("val"), format.fontName()); + } + if (format.hasProperty(FormatPrivate::P_Font_Charset)) { + writer.writeEmptyElement(QStringLiteral("charset")); + writer.writeAttribute( + QStringLiteral("val"), + QString::number(format.intProperty(FormatPrivate::P_Font_Charset))); + } + if (format.hasProperty(FormatPrivate::P_Font_Family)) { + writer.writeEmptyElement(QStringLiteral("family")); + writer.writeAttribute( + QStringLiteral("val"), + QString::number(format.intProperty(FormatPrivate::P_Font_Family))); + } + + if (format.hasProperty(FormatPrivate::P_Font_Scheme)) { + writer.writeEmptyElement(QStringLiteral("scheme")); + writer.writeAttribute(QStringLiteral("val"), + format.stringProperty(FormatPrivate::P_Font_Scheme)); + } + } + writer.writeEndElement(); // font +} + +void Styles::writeFills(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("fills")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_fillsList.size())); + + for (const auto &fill : m_fillsList) { + writeFill(writer, fill); + } + + writer.writeEndElement(); // fills +} + +void Styles::writeFill(QXmlStreamWriter &writer, const Format &fill, bool isDxf) const +{ + static const QMap patternStrings = { + {Format::PatternNone, QStringLiteral("none")}, + {Format::PatternSolid, QStringLiteral("solid")}, + {Format::PatternMediumGray, QStringLiteral("mediumGray")}, + {Format::PatternDarkGray, QStringLiteral("darkGray")}, + {Format::PatternLightGray, QStringLiteral("lightGray")}, + {Format::PatternDarkHorizontal, QStringLiteral("darkHorizontal")}, + {Format::PatternDarkVertical, QStringLiteral("darkVertical")}, + {Format::PatternDarkDown, QStringLiteral("darkDown")}, + {Format::PatternDarkUp, QStringLiteral("darkUp")}, + {Format::PatternDarkGrid, QStringLiteral("darkGrid")}, + {Format::PatternDarkTrellis, QStringLiteral("darkTrellis")}, + {Format::PatternLightHorizontal, QStringLiteral("lightHorizontal")}, + {Format::PatternLightVertical, QStringLiteral("lightVertical")}, + {Format::PatternLightDown, QStringLiteral("lightDown")}, + {Format::PatternLightUp, QStringLiteral("lightUp")}, + {Format::PatternLightTrellis, QStringLiteral("lightTrellis")}, + {Format::PatternGray125, QStringLiteral("gray125")}, + {Format::PatternGray0625, QStringLiteral("gray0625")}, + {Format::PatternLightGrid, QStringLiteral("lightGrid")}}; + + writer.writeStartElement(QStringLiteral("fill")); + writer.writeStartElement(QStringLiteral("patternFill")); + Format::FillPattern pattern = fill.fillPattern(); + // For normal fill formats, Excel prefer to outputting the default "none" attribute + // But for dxf, Excel prefer to omitting the default "none" + // Though not make any difference, but it make easier to compare origin files with generate + // files during debug + if (!(pattern == Format::PatternNone && isDxf)) + writer.writeAttribute(QStringLiteral("patternType"), patternStrings[pattern]); + // For a solid fill, Excel reverses the role of foreground and background colours + if (fill.fillPattern() == Format::PatternSolid) { + if (fill.hasProperty(FormatPrivate::P_Fill_BgColor)) + fill.property(FormatPrivate::P_Fill_BgColor) + .value() + .saveToXml(writer, QStringLiteral("fgColor")); + if (fill.hasProperty(FormatPrivate::P_Fill_FgColor)) + fill.property(FormatPrivate::P_Fill_FgColor) + .value() + .saveToXml(writer, QStringLiteral("bgColor")); + } else { + if (fill.hasProperty(FormatPrivate::P_Fill_FgColor)) + fill.property(FormatPrivate::P_Fill_FgColor) + .value() + .saveToXml(writer, QStringLiteral("fgColor")); + if (fill.hasProperty(FormatPrivate::P_Fill_BgColor)) + fill.property(FormatPrivate::P_Fill_BgColor) + .value() + .saveToXml(writer, QStringLiteral("bgColor")); + } + writer.writeEndElement(); // patternFill + writer.writeEndElement(); // fill +} + +void Styles::writeBorders(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("borders")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_bordersList.count())); + + for (const auto &border : m_bordersList) { + writeBorder(writer, border); + } + + writer.writeEndElement(); // borders +} + +void Styles::writeBorder(QXmlStreamWriter &writer, const Format &border, bool isDxf) const +{ + writer.writeStartElement(QStringLiteral("border")); + if (border.hasProperty(FormatPrivate::P_Border_DiagonalType)) { + Format::DiagonalBorderType t = border.diagonalBorderType(); + if (t == Format::DiagonalBorderUp) { + writer.writeAttribute(QStringLiteral("diagonalUp"), QStringLiteral("1")); + } else if (t == Format::DiagonalBorderDown) { + writer.writeAttribute(QStringLiteral("diagonalDown"), QStringLiteral("1")); + } else if (t == Format::DiagnoalBorderBoth) { + writer.writeAttribute(QStringLiteral("diagonalUp"), QStringLiteral("1")); + writer.writeAttribute(QStringLiteral("diagonalDown"), QStringLiteral("1")); + } + } + + writeSubBorder(writer, + QStringLiteral("left"), + border.leftBorderStyle(), + border.property(FormatPrivate::P_Border_LeftColor).value()); + writeSubBorder(writer, + QStringLiteral("right"), + border.rightBorderStyle(), + border.property(FormatPrivate::P_Border_RightColor).value()); + writeSubBorder(writer, + QStringLiteral("top"), + border.topBorderStyle(), + border.property(FormatPrivate::P_Border_TopColor).value()); + writeSubBorder(writer, + QStringLiteral("bottom"), + border.bottomBorderStyle(), + border.property(FormatPrivate::P_Border_BottomColor).value()); + + // Condition DXF formats don't allow diagonal style + if (!isDxf) + writeSubBorder(writer, + QStringLiteral("diagonal"), + border.diagonalBorderStyle(), + border.property(FormatPrivate::P_Border_DiagonalColor).value()); + + if (isDxf) { + // writeSubBorder(writer, QStringLiteral("vertical"), ); + // writeSubBorder(writer, QStringLiteral("horizontal"), ); + } + + writer.writeEndElement(); // border +} + +void Styles::writeSubBorder(QXmlStreamWriter &writer, + const QString &type, + int style, + const XlsxColor &color) const +{ + if (style == Format::BorderNone) { + writer.writeEmptyElement(type); + return; + } + + static const QMap stylesString = { + {Format::BorderNone, QStringLiteral("none")}, + {Format::BorderThin, QStringLiteral("thin")}, + {Format::BorderMedium, QStringLiteral("medium")}, + {Format::BorderDashed, QStringLiteral("dashed")}, + {Format::BorderDotted, QStringLiteral("dotted")}, + {Format::BorderThick, QStringLiteral("thick")}, + {Format::BorderDouble, QStringLiteral("double")}, + {Format::BorderHair, QStringLiteral("hair")}, + {Format::BorderMediumDashed, QStringLiteral("mediumDashed")}, + {Format::BorderDashDot, QStringLiteral("dashDot")}, + {Format::BorderMediumDashDot, QStringLiteral("mediumDashDot")}, + {Format::BorderDashDotDot, QStringLiteral("dashDotDot")}, + {Format::BorderMediumDashDotDot, QStringLiteral("mediumDashDotDot")}, + {Format::BorderSlantDashDot, QStringLiteral("slantDashDot")}}; + + writer.writeStartElement(type); + writer.writeAttribute(QStringLiteral("style"), stylesString[style]); + color.saveToXml(writer); // write color element + + writer.writeEndElement(); // type +} + +void Styles::writeCellXfs(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("cellXfs")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_xf_formatsList.size())); + for (const Format &format : m_xf_formatsList) { + int xf_id = 0; + writer.writeStartElement(QStringLiteral("xf")); + writer.writeAttribute(QStringLiteral("numFmtId"), + QString::number(format.numberFormatIndex())); + writer.writeAttribute(QStringLiteral("fontId"), QString::number(format.fontIndex())); + writer.writeAttribute(QStringLiteral("fillId"), QString::number(format.fillIndex())); + writer.writeAttribute(QStringLiteral("borderId"), QString::number(format.borderIndex())); + writer.writeAttribute(QStringLiteral("xfId"), QString::number(xf_id)); + if (format.hasNumFmtData()) + writer.writeAttribute(QStringLiteral("applyNumberFormat"), QStringLiteral("1")); + if (format.hasFontData()) + writer.writeAttribute(QStringLiteral("applyFont"), QStringLiteral("1")); + if (format.hasFillData()) + writer.writeAttribute(QStringLiteral("applyFill"), QStringLiteral("1")); + if (format.hasBorderData()) + writer.writeAttribute(QStringLiteral("applyBorder"), QStringLiteral("1")); + if (format.hasAlignmentData()) + writer.writeAttribute(QStringLiteral("applyAlignment"), QStringLiteral("1")); + + if (format.hasAlignmentData()) { + writer.writeEmptyElement(QStringLiteral("alignment")); + if (format.hasProperty(FormatPrivate::P_Alignment_AlignH)) { + switch (format.horizontalAlignment()) { + case Format::AlignLeft: + writer.writeAttribute(QStringLiteral("horizontal"), QStringLiteral("left")); + break; + case Format::AlignHCenter: + writer.writeAttribute(QStringLiteral("horizontal"), QStringLiteral("center")); + break; + case Format::AlignRight: + writer.writeAttribute(QStringLiteral("horizontal"), QStringLiteral("right")); + break; + case Format::AlignHFill: + writer.writeAttribute(QStringLiteral("horizontal"), QStringLiteral("fill")); + break; + case Format::AlignHJustify: + writer.writeAttribute(QStringLiteral("horizontal"), QStringLiteral("justify")); + break; + case Format::AlignHMerge: + writer.writeAttribute(QStringLiteral("horizontal"), + QStringLiteral("centerContinuous")); + break; + case Format::AlignHDistributed: + writer.writeAttribute(QStringLiteral("horizontal"), + QStringLiteral("distributed")); + break; + default: + break; + } + } + + if (format.hasProperty(FormatPrivate::P_Alignment_AlignV)) { + switch (format.verticalAlignment()) { + case Format::AlignTop: + writer.writeAttribute(QStringLiteral("vertical"), QStringLiteral("top")); + break; + case Format::AlignVCenter: + writer.writeAttribute(QStringLiteral("vertical"), QStringLiteral("center")); + break; + case Format::AlignVJustify: + writer.writeAttribute(QStringLiteral("vertical"), QStringLiteral("justify")); + break; + case Format::AlignVDistributed: + writer.writeAttribute(QStringLiteral("vertical"), + QStringLiteral("distributed")); + break; + default: + break; + } + } + if (format.hasProperty(FormatPrivate::P_Alignment_Indent)) + writer.writeAttribute(QStringLiteral("indent"), QString::number(format.indent())); + if (format.hasProperty(FormatPrivate::P_Alignment_Wrap) && format.textWrap()) + writer.writeAttribute(QStringLiteral("wrapText"), QStringLiteral("1")); + if (format.hasProperty(FormatPrivate::P_Alignment_ShinkToFit) && format.shrinkToFit()) + writer.writeAttribute(QStringLiteral("shrinkToFit"), QStringLiteral("1")); + if (format.hasProperty(FormatPrivate::P_Alignment_Rotation)) + writer.writeAttribute(QStringLiteral("textRotation"), + QString::number(format.rotation())); + } + + writer.writeEndElement(); // xf + } + writer.writeEndElement(); // cellXfs +} + +void Styles::writeDxfs(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("dxfs")); + writer.writeAttribute(QStringLiteral("count"), QString::number(m_dxf_formatsList.size())); + for (const Format &format : m_dxf_formatsList) + writeDxf(writer, format); + writer.writeEndElement(); // dxfs +} + +void Styles::writeDxf(QXmlStreamWriter &writer, const Format &format) const +{ + writer.writeStartElement(QStringLiteral("dxf")); + + if (format.hasFontData()) + writeFont(writer, format, true); + + if (format.hasNumFmtData()) { + writer.writeEmptyElement(QStringLiteral("numFmt")); + writer.writeAttribute(QStringLiteral("numFmtId"), + QString::number(format.numberFormatIndex())); + writer.writeAttribute(QStringLiteral("formatCode"), format.numberFormat()); + } + + if (format.hasFillData()) + writeFill(writer, format, true); + + if (format.hasBorderData()) + writeBorder(writer, format, true); + + writer.writeEndElement(); // dxf +} + +void Styles::writeColors(QXmlStreamWriter &writer) const +{ + if (m_isIndexedColorsDefault) // Don't output the default indexdeColors + return; + + writer.writeStartElement(QStringLiteral("colors")); + + writer.writeStartElement(QStringLiteral("indexedColors")); + for (const QColor &color : m_indexedColors) { + writer.writeEmptyElement(QStringLiteral("rgbColor")); + writer.writeAttribute(QStringLiteral("rgb"), XlsxColor::toARGBString(color)); + } + + writer.writeEndElement(); // indexedColors + + writer.writeEndElement(); // colors +} + +bool Styles::readNumFmts(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("numFmts")); + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + + // Read until we find the numFmts end tag or .... + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("numFmts"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("numFmt")) { + const auto &attributes = reader.attributes(); + QSharedPointer fmt(new XlsxFormatNumberData); + fmt->formatIndex = attributes.value(QLatin1String("numFmtId")).toInt(); + fmt->formatString = attributes.value(QLatin1String("formatCode")).toString(); + if (fmt->formatIndex >= m_nextCustomNumFmtId) + m_nextCustomNumFmtId = fmt->formatIndex + 1; + m_customNumFmtIdMap.insert(fmt->formatIndex, fmt); + m_customNumFmtsHash.insert(fmt->formatString, fmt); + } + } + } + + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_customNumFmtIdMap.size())) + qWarning("error read custom numFmts"); + + return true; +} + +bool Styles::readFonts(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("fonts")); + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("fonts"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("font")) { + Format format; + readFont(reader, format); + m_fontsList.append(format); + m_fontsHash.insert(format.fontKey(), format); + if (format.isValid()) + format.setFontIndex(m_fontsList.size() - 1); + } + } + } + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_fontsList.size())) + qWarning("error read fonts"); + return true; +} + +bool Styles::readFont(QXmlStreamReader &reader, Format &format) +{ + Q_ASSERT(reader.name() == QLatin1String("font")); + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("font"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + const auto &attributes = reader.attributes(); + if (reader.name() == QLatin1String("name")) { + format.setFontName(attributes.value(QLatin1String("val")).toString()); + } else if (reader.name() == QLatin1String("charset")) { + format.setProperty(FormatPrivate::P_Font_Charset, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("family")) { + format.setProperty(FormatPrivate::P_Font_Family, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("b")) { + format.setFontBold(true); + } else if (reader.name() == QLatin1String("i")) { + format.setFontItalic(true); + } else if (reader.name() == QLatin1String("strike")) { + format.setFontStrikeOut(true); + } else if (reader.name() == QLatin1String("outline")) { + format.setFontOutline(true); + } else if (reader.name() == QLatin1String("shadow")) { + format.setProperty(FormatPrivate::P_Font_Shadow, true); + } else if (reader.name() == QLatin1String("condense")) { + format.setProperty(FormatPrivate::P_Font_Condense, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("extend")) { + format.setProperty(FormatPrivate::P_Font_Extend, + attributes.value(QLatin1String("val")).toInt()); + } else if (reader.name() == QLatin1String("color")) { + XlsxColor color; + color.loadFromXml(reader); + format.setProperty(FormatPrivate::P_Font_Color, color); + } else if (reader.name() == QLatin1String("sz")) { + const auto sz = attributes.value(QLatin1String("val")).toInt(); + format.setFontSize(sz); + } else if (reader.name() == QLatin1String("u")) { + QString value = attributes.value(QLatin1String("val")).toString(); + if (value == QLatin1String("double")) + format.setFontUnderline(Format::FontUnderlineDouble); + else if (value == QLatin1String("doubleAccounting")) + format.setFontUnderline(Format::FontUnderlineDoubleAccounting); + else if (value == QLatin1String("singleAccounting")) + format.setFontUnderline(Format::FontUnderlineSingleAccounting); + else + format.setFontUnderline(Format::FontUnderlineSingle); + } else if (reader.name() == QLatin1String("vertAlign")) { + QString value = attributes.value(QLatin1String("val")).toString(); + if (value == QLatin1String("superscript")) + format.setFontScript(Format::FontScriptSuper); + else if (value == QLatin1String("subscript")) + format.setFontScript(Format::FontScriptSub); + } else if (reader.name() == QLatin1String("scheme")) { + format.setProperty(FormatPrivate::P_Font_Scheme, + attributes.value(QLatin1String("val")).toString()); + } + } + } + return true; +} + +bool Styles::readFills(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("fills")); + + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("fills"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("fill")) { + Format fill; + readFill(reader, fill); + m_fillsList.append(fill); + m_fillsHash.insert(fill.fillKey(), fill); + if (fill.isValid()) + fill.setFillIndex(m_fillsList.size() - 1); + } + } + } + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_fillsList.size())) + qWarning("error read fills"); + return true; +} + +bool Styles::readFill(QXmlStreamReader &reader, Format &fill) +{ + Q_ASSERT(reader.name() == QLatin1String("fill")); + + static const QMap patternValues = { + {QStringLiteral("none"), Format::PatternNone}, + {QStringLiteral("solid"), Format::PatternSolid}, + {QStringLiteral("mediumGray"), Format::PatternMediumGray}, + {QStringLiteral("darkGray"), Format::PatternDarkGray}, + {QStringLiteral("lightGray"), Format::PatternLightGray}, + {QStringLiteral("darkHorizontal"), Format::PatternDarkHorizontal}, + {QStringLiteral("darkVertical"), Format::PatternDarkVertical}, + {QStringLiteral("darkDown"), Format::PatternDarkDown}, + {QStringLiteral("darkUp"), Format::PatternDarkUp}, + {QStringLiteral("darkGrid"), Format::PatternDarkGrid}, + {QStringLiteral("darkTrellis"), Format::PatternDarkTrellis}, + {QStringLiteral("lightHorizontal"), Format::PatternLightHorizontal}, + {QStringLiteral("lightVertical"), Format::PatternLightVertical}, + {QStringLiteral("lightDown"), Format::PatternLightDown}, + {QStringLiteral("lightUp"), Format::PatternLightUp}, + {QStringLiteral("lightTrellis"), Format::PatternLightTrellis}, + {QStringLiteral("gray125"), Format::PatternGray125}, + {QStringLiteral("gray0625"), Format::PatternGray0625}, + {QStringLiteral("lightGrid"), Format::PatternLightGrid}}; + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("fill"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("patternFill")) { + const auto &attributes = reader.attributes(); + if (attributes.hasAttribute(QLatin1String("patternType"))) { + const auto &it = patternValues.constFind( + attributes.value(QLatin1String("patternType")).toString()); + fill.setFillPattern(it != patternValues.constEnd() ? it.value() + : Format::PatternNone); + + // parse foreground and background colors if they exist + while (!reader.atEnd() && + !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("patternFill"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("fgColor")) { + XlsxColor c; + if (c.loadFromXml(reader)) { + if (fill.fillPattern() == Format::PatternSolid) + fill.setProperty(FormatPrivate::P_Fill_BgColor, c); + else + fill.setProperty(FormatPrivate::P_Fill_FgColor, c); + } + } else if (reader.name() == QLatin1String("bgColor")) { + XlsxColor c; + if (c.loadFromXml(reader)) { + if (fill.fillPattern() == Format::PatternSolid) + fill.setProperty(FormatPrivate::P_Fill_FgColor, c); + else + fill.setProperty(FormatPrivate::P_Fill_BgColor, c); + } + } + } + } + } + } + } + } + + return true; +} + +bool Styles::readBorders(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("borders")); + + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("borders"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("border")) { + Format border; + readBorder(reader, border); + m_bordersList.append(border); + m_bordersHash.insert(border.borderKey(), border); + if (border.isValid()) + border.setBorderIndex(m_bordersList.size() - 1); + } + } + } + + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_bordersList.size())) + qWarning("error read borders"); + + return true; +} + +bool Styles::readBorder(QXmlStreamReader &reader, Format &border) +{ + Q_ASSERT(reader.name() == QLatin1String("border")); + + const auto &attributes = reader.attributes(); + const auto isUp = attributes.hasAttribute(QLatin1String("diagonalUp")); + const auto isDown = attributes.hasAttribute(QLatin1String("diagonalDown")); + if (isUp && isDown) + border.setDiagonalBorderType(Format::DiagnoalBorderBoth); + else if (isUp) + border.setDiagonalBorderType(Format::DiagonalBorderUp); + else if (isDown) + border.setDiagonalBorderType(Format::DiagonalBorderDown); + + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("border"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("left") || reader.name() == QLatin1String("right") || + reader.name() == QLatin1String("top") || reader.name() == QLatin1String("bottom") || + reader.name() == QLatin1String("diagonal")) { + Format::BorderStyle style(Format::BorderNone); + XlsxColor color; + readSubBorder(reader, reader.name().toString(), style, color); + + if (reader.name() == QLatin1String("left")) { + border.setLeftBorderStyle(style); + if (!color.isInvalid()) + border.setProperty(FormatPrivate::P_Border_LeftColor, color); + } else if (reader.name() == QLatin1String("right")) { + border.setRightBorderStyle(style); + if (!color.isInvalid()) + border.setProperty(FormatPrivate::P_Border_RightColor, color); + } else if (reader.name() == QLatin1String("top")) { + border.setTopBorderStyle(style); + if (!color.isInvalid()) + border.setProperty(FormatPrivate::P_Border_TopColor, color); + } else if (reader.name() == QLatin1String("bottom")) { + border.setBottomBorderStyle(style); + if (!color.isInvalid()) + border.setProperty(FormatPrivate::P_Border_BottomColor, color); + } else if (reader.name() == QLatin1String("diagonal")) { + border.setDiagonalBorderStyle(style); + if (!color.isInvalid()) + border.setProperty(FormatPrivate::P_Border_DiagonalColor, color); + } + } + } + + if (reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("border")) + break; + } + + return true; +} + +bool Styles::readCellStyleXfs(QXmlStreamReader &reader) +{ + Q_UNUSED(reader); + return true; +} + +bool Styles::readSubBorder(QXmlStreamReader &reader, + const QString &name, + Format::BorderStyle &style, + XlsxColor &color) +{ + Q_ASSERT(reader.name() == name); + + static const QMap stylesStringsMap = { + {QStringLiteral("none"), Format::BorderNone}, + {QStringLiteral("thin"), Format::BorderThin}, + {QStringLiteral("medium"), Format::BorderMedium}, + {QStringLiteral("dashed"), Format::BorderDashed}, + {QStringLiteral("dotted"), Format::BorderDotted}, + {QStringLiteral("thick"), Format::BorderThick}, + {QStringLiteral("double"), Format::BorderDouble}, + {QStringLiteral("hair"), Format::BorderHair}, + {QStringLiteral("mediumDashed"), Format::BorderMediumDashed}, + {QStringLiteral("dashDot"), Format::BorderDashDot}, + {QStringLiteral("mediumDashDot"), Format::BorderMediumDashDot}, + {QStringLiteral("dashDotDot"), Format::BorderDashDotDot}, + {QStringLiteral("mediumDashDotDot"), Format::BorderMediumDashDotDot}, + {QStringLiteral("slantDashDot"), Format::BorderSlantDashDot}}; + + const auto &attributes = reader.attributes(); + if (attributes.hasAttribute(QLatin1String("style"))) { + QString styleString = attributes.value(QLatin1String("style")).toString(); + const auto &it = stylesStringsMap.constFind(styleString); + if (it != stylesStringsMap.constEnd()) { + // get style + style = it.value(); + while (!reader.atEnd() && + !(reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == name)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("color")) + color.loadFromXml(reader); + } + } + } + } + + return true; +} + +bool Styles::readCellXfs(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("cellXfs")); + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("cellXfs"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("xf")) { + + Format format; + const auto &xfAttrs = reader.attributes(); + + // qDebug()<formatString); + } + } + + if (xfAttrs.hasAttribute(QLatin1String("fontId"))) { + const auto fontIndex = xfAttrs.value(QLatin1String("fontId")).toInt(); + if (fontIndex >= m_fontsList.size()) { + qDebug("Error read styles.xml, cellXfs fontId"); + } else { + const auto apply = + parseXsdBoolean(xfAttrs.value(QLatin1String("applyFont")).toString()); + if (apply) { + Format fontFormat = m_fontsList[fontIndex]; + for (int i = FormatPrivate::P_Font_STARTID; + i < FormatPrivate::P_Font_ENDID; + ++i) { + if (fontFormat.hasProperty(i)) + format.setProperty(i, fontFormat.property(i)); + } + } + } + } + + if (xfAttrs.hasAttribute(QLatin1String("fillId"))) { + const auto id = xfAttrs.value(QLatin1String("fillId")).toInt(); + if (id >= m_fillsList.size()) { + qDebug("Error read styles.xml, cellXfs fillId"); + } else { + + // dev20 branch + // NOTE: MIcrosoft Excel does not have 'applyFill' tag. + // + + // bool apply = + // parseXsdBoolean(xfAttrs.value(QLatin1String("applyFill")).toString()); if + // (apply) + + { + Format fillFormat = m_fillsList[id]; + for (int i = FormatPrivate::P_Fill_STARTID; + i < FormatPrivate::P_Fill_ENDID; + ++i) { + if (fillFormat.hasProperty(i)) + format.setProperty(i, fillFormat.property(i)); + } + } + } + } + + if (xfAttrs.hasAttribute(QLatin1String("borderId"))) { + const auto id = xfAttrs.value(QLatin1String("borderId")).toInt(); + if (id >= m_bordersList.size()) { + qDebug("Error read styles.xml, cellXfs borderId"); + } else { + const auto apply = + parseXsdBoolean(xfAttrs.value(QLatin1String("applyBorder")).toString()); + if (apply) { + Format borderFormat = m_bordersList[id]; + for (int i = FormatPrivate::P_Border_STARTID; + i < FormatPrivate::P_Border_ENDID; + ++i) { + if (borderFormat.hasProperty(i)) + format.setProperty(i, borderFormat.property(i)); + } + } + } + } + + const auto apply = + parseXsdBoolean(xfAttrs.value(QLatin1String("applyAlignment")).toString()); + if (apply) { + reader.readNextStartElement(); + if (reader.name() == QLatin1String("alignment")) { + const auto &alignAttrs = reader.attributes(); + + if (alignAttrs.hasAttribute(QLatin1String("horizontal"))) { + static const QMap alignStringMap = + {{QStringLiteral("left"), Format::AlignLeft}, + {QStringLiteral("center"), Format::AlignHCenter}, + {QStringLiteral("right"), Format::AlignRight}, + {QStringLiteral("justify"), Format::AlignHJustify}, + {QStringLiteral("centerContinuous"), Format::AlignHMerge}, + {QStringLiteral("distributed"), Format::AlignHDistributed}}; + + const auto &it = alignStringMap.constFind( + alignAttrs.value(QLatin1String("horizontal")).toString()); + if (it != alignStringMap.constEnd()) + format.setHorizontalAlignment(it.value()); + } + + if (alignAttrs.hasAttribute(QLatin1String("vertical"))) { + static const QMap alignStringMap = { + {QStringLiteral("top"), Format::AlignTop}, + {QStringLiteral("center"), Format::AlignVCenter}, + {QStringLiteral("justify"), Format::AlignVJustify}, + {QStringLiteral("distributed"), Format::AlignVDistributed}}; + + const auto &it = alignStringMap.constFind( + alignAttrs.value(QLatin1String("vertical")).toString()); + if (it != alignStringMap.constEnd()) + format.setVerticalAlignment(it.value()); + } + + if (alignAttrs.hasAttribute(QLatin1String("indent"))) { + const auto indent = alignAttrs.value(QLatin1String("indent")).toInt(); + format.setIndent(indent); + } + + if (alignAttrs.hasAttribute(QLatin1String("textRotation"))) { + const auto rotation = + alignAttrs.value(QLatin1String("textRotation")).toInt(); + format.setRotation(rotation); + } + + if (alignAttrs.hasAttribute(QLatin1String("wrapText"))) + format.setTextWrap(true); + + if (alignAttrs.hasAttribute(QLatin1String("shrinkToFit"))) + format.setShrinkToFit(true); + } + } + + addXfFormat(format, true); + } + } + } + + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_xf_formatsList.size())) + qWarning("error read CellXfs"); + + return true; +} + +bool Styles::readDxfs(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("dxfs")); + const auto &attributes = reader.attributes(); + const auto hasCount = attributes.hasAttribute(QLatin1String("count")); + const auto count = hasCount ? attributes.value(QLatin1String("count")).toInt() : -1; + while (!reader.atEnd() && !(reader.tokenType() == QXmlStreamReader::EndElement && + reader.name() == QLatin1String("dxfs"))) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("dxf")) + readDxf(reader); + } + } + if (reader.hasError()) + qWarning() << reader.errorString(); + + if (hasCount && (count != m_dxf_formatsList.size())) + qWarning("error read dxfs"); + + return true; +} + +bool Styles::readDxf(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("dxf")); + Format format; + while (!reader.atEnd() && !(reader.name() == QLatin1String("dxf") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("numFmt")) { + const auto &attributes = reader.attributes(); + const auto id = attributes.value(QLatin1String("numFmtId")).toInt(); + QString code = attributes.value(QLatin1String("formatCode")).toString(); + format.setNumberFormat(id, code); + } else if (reader.name() == QLatin1String("font")) { + readFont(reader, format); + } else if (reader.name() == QLatin1String("fill")) { + readFill(reader, format); + } else if (reader.name() == QLatin1String("border")) { + readBorder(reader, format); + } + } + } + addDxfFormat(format, true); + return true; +} + +bool Styles::readColors(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("colors")); + while (!reader.atEnd() && !(reader.name() == QLatin1String("colors") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("indexedColors")) { + readIndexedColors(reader); + } else if (reader.name() == QLatin1String("mruColors")) { + } + } + } + return true; +} + +bool Styles::readIndexedColors(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("indexedColors")); + m_indexedColors.clear(); + while (!reader.atEnd() && !(reader.name() == QLatin1String("indexedColors") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("rgbColor")) { + const auto &color = reader.attributes().value(QLatin1String("rgb")).toString(); + m_indexedColors.append(XlsxColor::fromARGBString(color)); + } + } + } + if (!m_indexedColors.isEmpty()) + m_isIndexedColorsDefault = false; + return true; +} + +bool Styles::loadFromXmlFile(QIODevice *device) +{ + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("numFmts")) { + readNumFmts(reader); + } else if (reader.name() == QLatin1String("fonts")) { + readFonts(reader); + } else if (reader.name() == QLatin1String("fills")) { + readFills(reader); + } else if (reader.name() == QLatin1String("borders")) { + readBorders(reader); + } else if (reader.name() == QLatin1String("cellStyleXfs")) { + + readCellStyleXfs(reader); + + } else if (reader.name() == QLatin1String("cellXfs")) { + readCellXfs(reader); + } else if (reader.name() == QLatin1String("cellStyles")) { + + // cellStyles + + } else if (reader.name() == QLatin1String("dxfs")) { + readDxfs(reader); + } else if (reader.name() == QLatin1String("colors")) { + readColors(reader); + } + } + + if (reader.hasError()) { + qDebug() << "Error when read style file: " << reader.errorString(); + } + } + return true; +} + +QColor Styles::getColorByIndex(int idx) +{ + if (m_indexedColors.isEmpty()) { + m_indexedColors = { + QColor(QRgba64::fromArgb32(0xFF000000)), QColor(QRgba64::fromArgb32(0xFFFFFFFF)), + QColor(QRgba64::fromArgb32(0xFFFF0000)), QColor(QRgba64::fromArgb32(0xFF00FF00)), + QColor(QRgba64::fromArgb32(0xFF0000FF)), QColor(QRgba64::fromArgb32(0xFFFFFF00)), + QColor(QRgba64::fromArgb32(0xFFFF00FF)), QColor(QRgba64::fromArgb32(0xFF00FFFF)), + QColor(QRgba64::fromArgb32(0xFF000000)), QColor(QRgba64::fromArgb32(0xFFFFFFFF)), + QColor(QRgba64::fromArgb32(0xFFFF0000)), QColor(QRgba64::fromArgb32(0xFF00FF00)), + QColor(QRgba64::fromArgb32(0xFF0000FF)), QColor(QRgba64::fromArgb32(0xFFFFFF00)), + QColor(QRgba64::fromArgb32(0xFFFF00FF)), QColor(QRgba64::fromArgb32(0xFF00FFFF)), + QColor(QRgba64::fromArgb32(0xFF800000)), QColor(QRgba64::fromArgb32(0xFF008000)), + QColor(QRgba64::fromArgb32(0xFF000080)), QColor(QRgba64::fromArgb32(0xFF808000)), + QColor(QRgba64::fromArgb32(0xFF800080)), QColor(QRgba64::fromArgb32(0xFF008080)), + QColor(QRgba64::fromArgb32(0xFFC0C0C0)), QColor(QRgba64::fromArgb32(0xFF808080)), + QColor(QRgba64::fromArgb32(0xFF9999FF)), QColor(QRgba64::fromArgb32(0xFF993366)), + QColor(QRgba64::fromArgb32(0xFFFFFFCC)), QColor(QRgba64::fromArgb32(0xFFCCFFFF)), + QColor(QRgba64::fromArgb32(0xFF660066)), QColor(QRgba64::fromArgb32(0xFFFF8080)), + QColor(QRgba64::fromArgb32(0xFF0066CC)), QColor(QRgba64::fromArgb32(0xFFCCCCFF)), + QColor(QRgba64::fromArgb32(0xFF000080)), QColor(QRgba64::fromArgb32(0xFFFF00FF)), + QColor(QRgba64::fromArgb32(0xFFFFFF00)), QColor(QRgba64::fromArgb32(0xFF00FFFF)), + QColor(QRgba64::fromArgb32(0xFF800080)), QColor(QRgba64::fromArgb32(0xFF800000)), + QColor(QRgba64::fromArgb32(0xFF008080)), QColor(QRgba64::fromArgb32(0xFF0000FF)), + QColor(QRgba64::fromArgb32(0xFF00CCFF)), QColor(QRgba64::fromArgb32(0xFFCCFFFF)), + QColor(QRgba64::fromArgb32(0xFFCCFFCC)), QColor(QRgba64::fromArgb32(0xFFFFFF99)), + QColor(QRgba64::fromArgb32(0xFF99CCFF)), QColor(QRgba64::fromArgb32(0xFFFF99CC)), + QColor(QRgba64::fromArgb32(0xFFCC99FF)), QColor(QRgba64::fromArgb32(0xFFFFCC99)), + QColor(QRgba64::fromArgb32(0xFF3366FF)), QColor(QRgba64::fromArgb32(0xFF33CCCC)), + QColor(QRgba64::fromArgb32(0xFF99CC00)), QColor(QRgba64::fromArgb32(0xFFFFCC00)), + QColor(QRgba64::fromArgb32(0xFFFF9900)), QColor(QRgba64::fromArgb32(0xFFFF6600)), + QColor(QRgba64::fromArgb32(0xFF666699)), QColor(QRgba64::fromArgb32(0xFF969696)), + QColor(QRgba64::fromArgb32(0xFF003366)), QColor(QRgba64::fromArgb32(0xFF339966)), + QColor(QRgba64::fromArgb32(0xFF003300)), QColor(QRgba64::fromArgb32(0xFF333300)), + QColor(QRgba64::fromArgb32(0xFF993300)), QColor(QRgba64::fromArgb32(0xFF993366)), + QColor(QRgba64::fromArgb32(0xFF333399)), QColor(QRgba64::fromArgb32(0xFF333333)), + }; + m_isIndexedColorsDefault = true; + } + if (idx < 0 || idx >= m_indexedColors.size()) + return QColor(); + return m_indexedColors[idx]; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxtheme.cpp b/QXlsx/source/xlsxtheme.cpp new file mode 100644 index 0000000..b69b7fe --- /dev/null +++ b/QXlsx/source/xlsxtheme.cpp @@ -0,0 +1,233 @@ +// xlsxtheme.cpp + +#include "xlsxtheme_p.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +const char *defaultXmlData = + "\n" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + +Theme::Theme(CreateFlag flag) + : AbstractOOXmlFile(flag) +{ +} + +void Theme::saveToXmlFile(QIODevice *device) const +{ + if (xmlData.isEmpty()) + device->write(defaultXmlData); + else + device->write(xmlData); +} + +QByteArray Theme::saveToXmlData() const +{ + if (xmlData.isEmpty()) + return defaultXmlData; + else + return xmlData; +} + +bool Theme::loadFromXmlData(const QByteArray &data) +{ + xmlData = data; + return true; +} + +bool Theme::loadFromXmlFile(QIODevice *device) +{ + xmlData = device->readAll(); + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxutility.cpp b/QXlsx/source/xlsxutility.cpp new file mode 100644 index 0000000..1249386 --- /dev/null +++ b/QXlsx/source/xlsxutility.cpp @@ -0,0 +1,300 @@ +// xlsxutility.cpp + +#include "xlsxcellreference.h" +#include "xlsxutility_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +bool parseXsdBoolean(const QString &value, bool defaultValue) +{ + if (value == QLatin1String("1") || value == QLatin1String("true")) + return true; + if (value == QLatin1String("0") || value == QLatin1String("false")) + return false; + return defaultValue; +} + +QStringList splitPath(const QString &path) +{ + int idx = path.lastIndexOf(QLatin1Char('/')); + if (idx == -1) + return {QStringLiteral("."), path}; + + return {path.left(idx), path.mid(idx + 1)}; +} + +/* + * Return the .rel file path based on filePath + */ +QString getRelFilePath(const QString &filePath) +{ + QString ret; + + int idx = filePath.lastIndexOf(QLatin1Char('/')); + if (idx == -1) // not found + { + // return QString(); + + // dev34 + ret = QLatin1String("_rels/") + QStringLiteral("%0.rels").arg(filePath); + return ret; + } + + ret = QString(filePath.left(idx) + QLatin1String("/_rels/") + filePath.mid(idx + 1) + + QLatin1String(".rels")); + return ret; +} + +double datetimeToNumber(const QDateTime &dt, bool is1904) +{ + // Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31 + QDateTime epoch(is1904 ? QDate(1904, 1, 1) : QDate(1899, 12, 31), QTime(0, 0)); + + double excel_time = epoch.msecsTo(dt) / (1000 * 60 * 60 * 24.0); + + if (dt.isDaylightTime()) // Add one hour if the date is Daylight + excel_time += 1.0 / 24.0; + + if (!is1904 && excel_time > 59) { // 31+28 + // Account for Excel erroneously treating 1900 as a leap year. + excel_time += 1; + } + + return excel_time; +} + +double timeToNumber(const QTime &time) +{ + return QTime(0, 0).msecsTo(time) / (1000 * 60 * 60 * 24.0); +} + +QVariant datetimeFromNumber(double num, bool is1904) +{ + QDateTime dtRet; // return value + + if (!is1904 && num > 60) // for mac os excel + { + num = num - 1; + } + + qint64 msecs = static_cast(num * 1000 * 60 * 60 * 24.0 + 0.5); + QDateTime epoch(is1904 ? QDate(1904, 1, 1) : QDate(1899, 12, 31), QTime(0, 0)); + QDateTime dtOld = epoch.addMSecs(msecs); + dtRet = dtOld; + + // Remove one hour to see whether the date is Daylight + QDateTime dtNew = dtRet.addMSecs(-3600000); // issue102 + if (dtNew.isDaylightTime()) { + dtRet = dtNew; + } + + double whole = 0; + double fractional = std::modf(num, &whole); + + if (num < double(1)) { + // only time + QTime t = dtRet.time(); + return QVariant(t); + } + + if (fractional == 0.0) { + // only date + QDate onlyDT = dtRet.date(); + return QVariant(onlyDT); + } + + return QVariant(dtRet); +} + +/* + Creates a valid sheet name + minimum length is 1 + maximum length is 31 + doesn't contain special chars: / \ ? * ] [ : + Sheet names must not begin or end with ' (apostrophe) + + Invalid characters are replaced by one space character ' '. + */ +QString createSafeSheetName(const QString &nameProposal) +{ + if (nameProposal.isEmpty()) + return QString(); + + QString ret = nameProposal; + if (nameProposal.length() > 2 && nameProposal.startsWith(QLatin1Char('\'')) && + nameProposal.endsWith(QLatin1Char('\''))) + ret = unescapeSheetName(ret); + + // Replace invalid chars with space. + static QRegularExpression invalidChars(QStringLiteral("[/\\\\?*\\][:]")); + if (nameProposal.contains(invalidChars)) { + static QRegularExpression validChars(QStringLiteral("[/\\\\?*\\][:]")); + ret.replace(validChars, QStringLiteral(" ")); + } + + if (ret.startsWith(QLatin1Char('\''))) + ret[0] = QLatin1Char(' '); + + if (ret.endsWith(QLatin1Char('\''))) + ret[ret.size() - 1] = QLatin1Char(' '); + + if (ret.size() > 31) + ret = ret.left(31); + return ret; +} + +/* + * When sheetName contains space or apostrophe, escaped is needed by + * cellFormula/definedName/chartSerials. + */ +QString escapeSheetName(const QString &sheetName) +{ + // Already escaped. + Q_ASSERT(!sheetName.startsWith(QLatin1Char('\'')) && !sheetName.endsWith(QLatin1Char('\''))); + + // These is no need to escape + static const auto escape = QRegularExpression(QStringLiteral("[ +\\-,%^=<>'&]")); + if (!sheetName.contains(escape)) + return sheetName; + + // OK, escape is needed. + QString name = sheetName; + name.replace(QLatin1Char('\''), QLatin1String("\'\'")); + return QLatin1Char('\'') + name + QLatin1Char('\''); +} + +/* + */ +QString unescapeSheetName(const QString &sheetName) +{ + Q_ASSERT(sheetName.length() > 2 && sheetName.startsWith(QLatin1Char('\'')) && + sheetName.endsWith(QLatin1Char('\''))); + + QString name = sheetName.mid(1, sheetName.length() - 2); + name.replace(QLatin1String("\'\'"), QLatin1String("\'")); + return name; +} + +/* + * whether the string s starts or ends with space + */ +bool isSpaceReserveNeeded(const QString &s) +{ + QString spaces(QStringLiteral(" \t\n\r")); + return !s.isEmpty() && (spaces.contains(s.at(0)) || spaces.contains(s.at(s.length() - 1))); +} + +/* + * Convert shared formula for non-root cells. + * + * For example, if "B1:B10" have shared formula "=A1*A1", this function will return "=A2*A2" + * for "B2" cell, "=A3*A3" for "B3" cell, etc. + * + * Note, the formula "=A1*A1" for B1 can also be written as "=RC[-1]*RC[-1]", which is the same + * for all other cells. In other words, this formula is shared. + * + * For long run, we need a formula parser. + */ +QString convertSharedFormula(const QString &rootFormula, + const CellReference &rootCell, + const CellReference &cell) +{ + Q_UNUSED(rootCell) + Q_UNUSED(cell) + // Find all the "$?[A-Z]+$?[0-9]+" patterns in the rootFormula. + QVector> segments; + + QString segment; + bool inQuote = false; + enum RefState { INVALID, PRE_AZ, AZ, PRE_09, _09 }; + RefState refState = INVALID; + int refFlag = 0; // 0x00, 0x01, 0x02, 0x03 ==> A1, $A1, A$1, $A$1 + for (QChar ch : rootFormula) { + if (inQuote) { + segment.append(ch); + if (ch == QLatin1Char('"')) + inQuote = false; + } else { + if (ch == QLatin1Char('"')) { + inQuote = true; + refState = INVALID; + segment.append(ch); + } else if (ch == QLatin1Char('$')) { + if (refState == AZ) { + segment.append(ch); + refState = PRE_09; + refFlag |= 0x02; + } else { + segments.append(std::make_pair(segment, refState == _09 ? refFlag : -1)); + segment = QString(ch); // Start new segment. + refState = PRE_AZ; + refFlag = 0x01; + } + } else if (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')) { + if (refState == PRE_AZ || refState == AZ) { + segment.append(ch); + } else { + segments.append(std::make_pair(segment, refState == _09 ? refFlag : -1)); + segment = QString(ch); // Start new segment. + refFlag = 0x00; + } + refState = AZ; + } else if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) { + segment.append(ch); + + if (refState == AZ || refState == PRE_09 || refState == _09) + refState = _09; + else + refState = INVALID; + } else { + if (refState == _09) { + segments.append(std::make_pair(segment, refFlag)); + segment = QString(ch); // Start new segment. + } else { + segment.append(ch); + } + refState = INVALID; + } + } + } + + if (!segment.isEmpty()) + segments.append(std::make_pair(segment, refState == _09 ? refFlag : -1)); + + // Replace "A1", "$A1", "A$1" segment with proper one. + QStringList result; + for (const auto &p : segments) { + // qDebug()< +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +WorkbookPrivate::WorkbookPrivate(Workbook *q, Workbook::CreateFlag flag) + : AbstractOOXmlFilePrivate(q, flag) +{ + sharedStrings = QSharedPointer(new SharedStrings(flag)); + styles = QSharedPointer(new Styles(flag)); + theme = QSharedPointer(new Theme(flag)); + + x_window = 240; + y_window = 15; + window_width = 16095; + window_height = 9660; + + strings_to_numbers_enabled = false; + strings_to_hyperlinks_enabled = true; + html_to_richstring_enabled = false; + date1904 = false; + defaultDateFormat = QStringLiteral("yyyy-mm-dd"); + activesheetIndex = 0; + firstsheet = 0; + table_count = 0; + + last_worksheet_index = 0; + last_chartsheet_index = 0; + last_sheet_id = 0; +} + +Workbook::Workbook(CreateFlag flag) + : AbstractOOXmlFile(new WorkbookPrivate(this, flag)) +{ +} + +Workbook::~Workbook() +{ +} + +bool Workbook::isDate1904() const +{ + Q_D(const Workbook); + return d->date1904; +} + +/*! + Excel for Windows uses a default epoch of 1900 and Excel + for Mac uses an epoch of 1904. However, Excel on either + platform will convert automatically between one system + and the other. Qt Xlsx stores dates in the 1900 format + by default. + + \note This function should be called before any date/time + has been written. +*/ +void Workbook::setDate1904(bool date1904) +{ + Q_D(Workbook); + d->date1904 = date1904; +} + +/* + Enable the worksheet.write() method to convert strings + to numbers, where possible, using float() in order to avoid + an Excel warning about "Numbers Stored as Text". + + The default is false + */ +void Workbook::setStringsToNumbersEnabled(bool enable) +{ + Q_D(Workbook); + d->strings_to_numbers_enabled = enable; +} + +bool Workbook::isStringsToNumbersEnabled() const +{ + Q_D(const Workbook); + return d->strings_to_numbers_enabled; +} + +void Workbook::setStringsToHyperlinksEnabled(bool enable) +{ + Q_D(Workbook); + d->strings_to_hyperlinks_enabled = enable; +} + +bool Workbook::isStringsToHyperlinksEnabled() const +{ + Q_D(const Workbook); + return d->strings_to_hyperlinks_enabled; +} + +void Workbook::setHtmlToRichStringEnabled(bool enable) +{ + Q_D(Workbook); + d->html_to_richstring_enabled = enable; +} + +bool Workbook::isHtmlToRichStringEnabled() const +{ + Q_D(const Workbook); + return d->html_to_richstring_enabled; +} + +QString Workbook::defaultDateFormat() const +{ + Q_D(const Workbook); + return d->defaultDateFormat; +} + +void Workbook::setDefaultDateFormat(const QString &format) +{ + Q_D(Workbook); + d->defaultDateFormat = format; +} + +/*! + * \brief Create a defined name in the workbook. + * \param name The defined name + * \param formula The cell or range that the defined name refers to. + * \param comment + * \param scope The name of one worksheet, or empty which means global scope. + * \return Return false if the name invalid. + */ +bool Workbook::defineName(const QString &name, + const QString &formula, + const QString &comment, + const QString &scope) +{ + Q_D(Workbook); + + // Remove the = sign from the formula if it exists. + QString formulaString = formula; + if (formulaString.startsWith(QLatin1Char('='))) + formulaString = formula.mid(1); + + int id = -1; + if (!scope.isEmpty()) { + for (int i = 0; i < d->sheets.size(); ++i) { + if (d->sheets[i]->sheetName() == scope) { + id = d->sheets[i]->sheetId(); + break; + } + } + } + + d->definedNamesList.append(XlsxDefineNameData(name, formulaString, comment, id)); + return true; +} + +AbstractSheet *Workbook::addSheet(const QString &name, AbstractSheet::SheetType type) +{ + Q_D(Workbook); + return insertSheet(d->sheets.size(), name, type); +} + +/*! + * \internal + */ +QStringList Workbook::worksheetNames() const +{ + Q_D(const Workbook); + return d->sheetNames; +} + +/*! + * \internal + * Used only when load the xlsx file!! + */ +AbstractSheet *Workbook::addSheet(const QString &name, int sheetId, AbstractSheet::SheetType type) +{ + Q_D(Workbook); + if (sheetId > d->last_sheet_id) + d->last_sheet_id = sheetId; + + AbstractSheet *sheet = nullptr; + if (type == AbstractSheet::ST_WorkSheet) { + // create work sheet (value sheet) + sheet = new Worksheet(name, sheetId, this, F_LoadFromExists); + } else if (type == AbstractSheet::ST_ChartSheet) { + // create chart sheet + sheet = new Chartsheet(name, sheetId, this, F_LoadFromExists); + } else { + qWarning("unsupported sheet type."); + Q_ASSERT(false); + } + + d->sheets.append(QSharedPointer(sheet)); + d->sheetNames.append(name); + + return sheet; +} + +AbstractSheet *Workbook::insertSheet(int index, const QString &name, AbstractSheet::SheetType type) +{ + Q_D(Workbook); + QString sheetName = createSafeSheetName(name); + if (index > d->last_sheet_id) { + // User tries to insert, where no sheet has gone before. + return nullptr; + } + if (!sheetName.isEmpty()) { + // If user given an already in-used name, we should not continue any more! + if (d->sheetNames.contains(sheetName)) + return nullptr; + } else { + if (type == AbstractSheet::ST_WorkSheet) { + do { + ++d->last_worksheet_index; + sheetName = QStringLiteral("Sheet%1").arg(d->last_worksheet_index); + } while (d->sheetNames.contains(sheetName)); + } else if (type == AbstractSheet::ST_ChartSheet) { + do { + ++d->last_chartsheet_index; + sheetName = QStringLiteral("Chart%1").arg(d->last_chartsheet_index); + } while (d->sheetNames.contains(sheetName)); + } else { + qWarning("unsupported sheet type."); + return nullptr; + } + } + + ++d->last_sheet_id; + + AbstractSheet *sheet = nullptr; + if (type == AbstractSheet::ST_WorkSheet) { + sheet = new Worksheet(sheetName, d->last_sheet_id, this, F_NewFromScratch); + } else if (type == AbstractSheet::ST_ChartSheet) { + sheet = new Chartsheet(sheetName, d->last_sheet_id, this, F_NewFromScratch); + } else { + qWarning("unsupported sheet type."); + Q_ASSERT(false); + } + + d->sheets.insert(index, QSharedPointer(sheet)); + d->sheetNames.insert(index, sheetName); + d->activesheetIndex = index; + + return sheet; +} + +/*! + * Returns current active worksheet. + */ +AbstractSheet *Workbook::activeSheet() const +{ + Q_D(const Workbook); + if (d->sheets.isEmpty()) + const_cast(this)->addSheet(); + return d->sheets[d->activesheetIndex].data(); +} + +bool Workbook::setActiveSheet(int index) +{ + Q_D(Workbook); + if (index < 0 || index >= d->sheets.size()) { + // warning + return false; + } + d->activesheetIndex = index; + return true; +} + +/*! + * Rename the worksheet at the \a index to \a newName. + */ +bool Workbook::renameSheet(int index, const QString &newName) +{ + Q_D(Workbook); + QString name = createSafeSheetName(newName); + if (index < 0 || index >= d->sheets.size()) + return false; + + // If user given an already in-used name, return false + for (int i = 0; i < d->sheets.size(); ++i) { + if (d->sheets[i]->sheetName() == name) + return false; + } + + d->sheets[index]->setSheetName(name); + d->sheetNames[index] = name; + return true; +} + +/*! + * Remove the worksheet at pos \a index. + */ +bool Workbook::deleteSheet(int index) +{ + Q_D(Workbook); + if (d->sheets.size() <= 1) + return false; + if (index < 0 || index >= d->sheets.size()) + return false; + d->sheets.removeAt(index); + d->sheetNames.removeAt(index); + return true; +} + +/*! + * Moves the worksheet form \a srcIndex to \a distIndex. + */ +bool Workbook::moveSheet(int srcIndex, int distIndex) +{ + Q_D(Workbook); + if (srcIndex == distIndex) + return false; + + if (srcIndex < 0 || srcIndex >= d->sheets.size()) + return false; + + QSharedPointer sheet = d->sheets.takeAt(srcIndex); + d->sheetNames.takeAt(srcIndex); + if (distIndex >= 0 || distIndex <= d->sheets.size()) { + d->sheets.insert(distIndex, sheet); + d->sheetNames.insert(distIndex, sheet->sheetName()); + } else { + d->sheets.append(sheet); + d->sheetNames.append(sheet->sheetName()); + } + return true; +} + +bool Workbook::copySheet(int index, const QString &newName) +{ + Q_D(Workbook); + if (index < 0 || index >= d->sheets.size()) + return false; + + QString worksheetName = createSafeSheetName(newName); + if (!newName.isEmpty()) { + // If user given an already in-used name, we should not continue any more! + if (d->sheetNames.contains(newName)) + return false; + } else { + int copy_index = 1; + do { + ++copy_index; + worksheetName = + QStringLiteral("%1(%2)").arg(d->sheets[index]->sheetName()).arg(copy_index); + } while (d->sheetNames.contains(worksheetName)); + } + + ++d->last_sheet_id; + AbstractSheet *sheet = d->sheets[index]->copy(worksheetName, d->last_sheet_id); + d->sheets.append(QSharedPointer(sheet)); + d->sheetNames.append(sheet->sheetName()); + + return true; // #162 +} + +/*! + * Returns count of worksheets. + */ +int Workbook::sheetCount() const +{ + Q_D(const Workbook); + return d->sheets.count(); +} + +/*! + * Returns the sheet object at index \a sheetIndex. + */ +AbstractSheet *Workbook::sheet(int index) const +{ + Q_D(const Workbook); + if (index < 0 || index >= d->sheets.size()) + return nullptr; + return d->sheets.at(index).data(); +} + +SharedStrings *Workbook::sharedStrings() const +{ + Q_D(const Workbook); + return d->sharedStrings.data(); +} + +Styles *Workbook::styles() +{ + Q_D(Workbook); + return d->styles.data(); +} + +Theme *Workbook::theme() +{ + Q_D(Workbook); + return d->theme.data(); +} + +/*! + * \internal + * + * Unlike media files, drawing file is a property of the sheet. + */ +QList Workbook::drawings() +{ + Q_D(Workbook); + QList ds; + for (int i = 0; i < d->sheets.size(); ++i) { + QSharedPointer sheet = d->sheets[i]; + if (sheet->drawing()) + ds.append(sheet->drawing()); + } + + return ds; +} + +/*! + * \internal + */ +QList> Workbook::getSheetsByTypes(AbstractSheet::SheetType type) const +{ + Q_D(const Workbook); + QList> list; + for (int i = 0; i < d->sheets.size(); ++i) { + if (d->sheets[i]->sheetType() == type) + list.append(d->sheets[i]); + } + return list; +} + +void Workbook::saveToXmlFile(QIODevice *device) const +{ + Q_D(const Workbook); + d->relationships->clear(); + if (d->sheets.isEmpty()) + const_cast(this)->addSheet(); + + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("workbook")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + + writer.writeEmptyElement(QStringLiteral("fileVersion")); + writer.writeAttribute(QStringLiteral("appName"), QStringLiteral("xl")); + writer.writeAttribute(QStringLiteral("lastEdited"), QStringLiteral("4")); + writer.writeAttribute(QStringLiteral("lowestEdited"), QStringLiteral("4")); + writer.writeAttribute(QStringLiteral("rupBuild"), QStringLiteral("4505")); + // writer.writeAttribute(QStringLiteral("codeName"), + // QStringLiteral("{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}")); + + writer.writeEmptyElement(QStringLiteral("workbookPr")); + if (d->date1904) + writer.writeAttribute(QStringLiteral("date1904"), QStringLiteral("1")); + writer.writeAttribute(QStringLiteral("defaultThemeVersion"), QStringLiteral("124226")); + + writer.writeStartElement(QStringLiteral("bookViews")); + writer.writeEmptyElement(QStringLiteral("workbookView")); + writer.writeAttribute(QStringLiteral("xWindow"), QString::number(d->x_window)); + writer.writeAttribute(QStringLiteral("yWindow"), QString::number(d->y_window)); + writer.writeAttribute(QStringLiteral("windowWidth"), QString::number(d->window_width)); + writer.writeAttribute(QStringLiteral("windowHeight"), QString::number(d->window_height)); + // Store the firstSheet when it isn't the default + // For example, when "the first sheet 0 is hidden", the first sheet will be 1 + if (d->firstsheet > 0) + writer.writeAttribute(QStringLiteral("firstSheet"), QString::number(d->firstsheet + 1)); + // Store the activeTab when it isn't the first sheet + if (d->activesheetIndex > 0) + writer.writeAttribute(QStringLiteral("activeTab"), QString::number(d->activesheetIndex)); + writer.writeEndElement(); // bookViews + + writer.writeStartElement(QStringLiteral("sheets")); + int worksheetIndex = 0; + int chartsheetIndex = 0; + for (int i = 0; i < d->sheets.size(); ++i) { + QSharedPointer sheet = d->sheets[i]; + writer.writeEmptyElement(QStringLiteral("sheet")); + writer.writeAttribute(QStringLiteral("name"), sheet->sheetName()); + writer.writeAttribute(QStringLiteral("sheetId"), QString::number(sheet->sheetId())); + if (sheet->sheetState() == AbstractSheet::SS_Hidden) + writer.writeAttribute(QStringLiteral("state"), QStringLiteral("hidden")); + else if (sheet->sheetState() == AbstractSheet::SS_VeryHidden) + writer.writeAttribute(QStringLiteral("state"), QStringLiteral("veryHidden")); + + if (sheet->sheetType() == AbstractSheet::ST_WorkSheet) + d->relationships->addDocumentRelationship( + QStringLiteral("/worksheet"), + QStringLiteral("worksheets/sheet%1.xml").arg(++worksheetIndex)); + else + d->relationships->addDocumentRelationship( + QStringLiteral("/chartsheet"), + QStringLiteral("chartsheets/sheet%1.xml").arg(++chartsheetIndex)); + + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(d->relationships->count())); + } + writer.writeEndElement(); // sheets + + if (d->externalLinks.size() > 0) { + writer.writeStartElement(QStringLiteral("externalReferences")); + for (int i = 0; i < d->externalLinks.size(); ++i) { + writer.writeEmptyElement(QStringLiteral("externalReference")); + d->relationships->addDocumentRelationship( + QStringLiteral("/externalLink"), + QStringLiteral("externalLinks/externalLink%1.xml").arg(i + 1)); + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(d->relationships->count())); + } + writer.writeEndElement(); // externalReferences + } + + if (!d->definedNamesList.isEmpty()) { + writer.writeStartElement(QStringLiteral("definedNames")); + for (const XlsxDefineNameData &data : d->definedNamesList) { + writer.writeStartElement(QStringLiteral("definedName")); + writer.writeAttribute(QStringLiteral("name"), data.name); + if (!data.comment.isEmpty()) + writer.writeAttribute(QStringLiteral("comment"), data.comment); + if (data.sheetId != -1) { + // find the local index of the sheet. + for (int i = 0; i < d->sheets.size(); ++i) { + if (d->sheets[i]->sheetId() == data.sheetId) { + writer.writeAttribute(QStringLiteral("localSheetId"), QString::number(i)); + break; + } + } + } + writer.writeCharacters(data.formula); + writer.writeEndElement(); // definedName + } + writer.writeEndElement(); // definedNames + } + + writer.writeStartElement(QStringLiteral("calcPr")); + writer.writeAttribute(QStringLiteral("calcId"), QStringLiteral("124519")); + writer.writeEndElement(); // calcPr + + writer.writeEndElement(); // workbook + writer.writeEndDocument(); + + d->relationships->addDocumentRelationship(QStringLiteral("/theme"), + QStringLiteral("theme/theme1.xml")); + d->relationships->addDocumentRelationship(QStringLiteral("/styles"), + QStringLiteral("styles.xml")); + if (!sharedStrings()->isEmpty()) + d->relationships->addDocumentRelationship(QStringLiteral("/sharedStrings"), + QStringLiteral("sharedStrings.xml")); +} + +bool Workbook::loadFromXmlFile(QIODevice *device) +{ + Q_D(Workbook); + + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("sheet")) { + QXmlStreamAttributes attributes = reader.attributes(); + + const auto &name = attributes.value(QLatin1String("name")).toString(); + + int sheetId = attributes.value(QLatin1String("sheetId")).toInt(); + + const auto &rId = attributes.value(QLatin1String("r:id")).toString(); + + const auto &stateString = attributes.value(QLatin1String("state")); + + AbstractSheet::SheetState state = AbstractSheet::SS_Visible; + if (stateString == QLatin1String("hidden")) + state = AbstractSheet::SS_Hidden; + else if (stateString == QLatin1String("veryHidden")) + state = AbstractSheet::SS_VeryHidden; + + XlsxRelationship relationship = d->relationships->getRelationshipById(rId); + + AbstractSheet::SheetType type = AbstractSheet::ST_WorkSheet; + if (relationship.type.endsWith(QLatin1String("/worksheet"))) { + type = AbstractSheet::ST_WorkSheet; + } else if (relationship.type.endsWith(QLatin1String("/chartsheet"))) { + type = AbstractSheet::ST_ChartSheet; + } else if (relationship.type.endsWith(QLatin1String("/dialogsheet"))) { + type = AbstractSheet::ST_DialogSheet; + } else if (relationship.type.endsWith(QLatin1String("/xlMacrosheet"))) { + type = AbstractSheet::ST_MacroSheet; + } else { + qWarning() << "unknown sheet type : " << relationship.type; + } + + AbstractSheet *sheet = addSheet(name, sheetId, type); + sheet->setSheetState(state); + QString strFilePath = filePath(); + + // const QString fullPath = QDir::cleanPath(splitPath(strFilePath).constFirst() + + // QLatin1String("/") + relationship.target); + const auto parts = splitPath(strFilePath); + QString fullPath = + QDir::cleanPath(parts.first() + QLatin1String("/") + relationship.target); + + sheet->setFilePath(fullPath); + } else if (reader.name() == QLatin1String("workbookPr")) { + QXmlStreamAttributes attrs = reader.attributes(); + if (attrs.hasAttribute(QLatin1String("date1904"))) + d->date1904 = true; + } else if (reader.name() == QLatin1String("bookviews")) { + while (!(reader.name() == QLatin1String("bookviews") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("workbookView")) { + QXmlStreamAttributes attrs = reader.attributes(); + if (attrs.hasAttribute(QLatin1String("xWindow"))) + d->x_window = attrs.value(QLatin1String("xWindow")).toInt(); + if (attrs.hasAttribute(QLatin1String("yWindow"))) + d->y_window = attrs.value(QLatin1String("yWindow")).toInt(); + if (attrs.hasAttribute(QLatin1String("windowWidth"))) + d->window_width = attrs.value(QLatin1String("windowWidth")).toInt(); + if (attrs.hasAttribute(QLatin1String("windowHeight"))) + d->window_height = + attrs.value(QLatin1String("windowHeight")).toInt(); + if (attrs.hasAttribute(QLatin1String("firstSheet"))) + d->firstsheet = attrs.value(QLatin1String("firstSheet")).toInt(); + if (attrs.hasAttribute(QLatin1String("activeTab"))) + d->activesheetIndex = + attrs.value(QLatin1String("activeTab")).toInt(); + } + } + } + } else if (reader.name() == QLatin1String("externalReference")) { + QXmlStreamAttributes attributes = reader.attributes(); + const QString rId = attributes.value(QLatin1String("r:id")).toString(); + XlsxRelationship relationship = d->relationships->getRelationshipById(rId); + + QSharedPointer link(new SimpleOOXmlFile(F_LoadFromExists)); + + const auto parts = splitPath(filePath()); + QString fullPath = + QDir::cleanPath(parts.first() + QLatin1String("/") + relationship.target); + + link->setFilePath(fullPath); + d->externalLinks.append(link); + } else if (reader.name() == QLatin1String("definedName")) { + QXmlStreamAttributes attrs = reader.attributes(); + XlsxDefineNameData data; + + data.name = attrs.value(QLatin1String("name")).toString(); + if (attrs.hasAttribute(QLatin1String("comment"))) + data.comment = attrs.value(QLatin1String("comment")).toString(); + if (attrs.hasAttribute(QLatin1String("localSheetId"))) { + int localId = attrs.value(QLatin1String("localSheetId")).toInt(); + int sheetId = d->sheets.at(localId)->sheetId(); + data.sheetId = sheetId; + } + data.formula = reader.readElementText(); + d->definedNamesList.append(data); + } + } + } + return true; +} + +/*! + * \internal + */ +QList> Workbook::mediaFiles() const +{ + Q_D(const Workbook); + + return d->mediaFiles; +} + +/*! + * \internal + */ +void Workbook::addMediaFile(std::shared_ptr media, bool force) +{ + Q_D(Workbook); + + if (!force) { + for (int i = 0; i < d->mediaFiles.size(); ++i) { + if (d->mediaFiles[i]->hashKey() == media->hashKey()) { + media->setIndex(i); + return; + } + } + } + + media->setIndex(d->mediaFiles.size()); + d->mediaFiles.append(media); +} + +/*! + * \internal + */ +QList> Workbook::chartFiles() const +{ + Q_D(const Workbook); + + return d->chartFiles; +} + +/*! + * \internal + */ +void Workbook::addChartFile(QSharedPointer chart) +{ + Q_D(Workbook); + + if (!d->chartFiles.contains(chart)) + d->chartFiles.append(chart); +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxworksheet.cpp b/QXlsx/source/xlsxworksheet.cpp new file mode 100644 index 0000000..9f15396 --- /dev/null +++ b/QXlsx/source/xlsxworksheet.cpp @@ -0,0 +1,2913 @@ +// xlsxworksheet.cpp + +#include "xlsxworksheet.h" + +#include "xlsxcell.h" +#include "xlsxcell_p.h" +#include "xlsxcellformula.h" +#include "xlsxcellformula_p.h" +#include "xlsxcelllocation.h" +#include "xlsxcellrange.h" +#include "xlsxcellreference.h" +#include "xlsxchart.h" +#include "xlsxconditionalformatting_p.h" +#include "xlsxdrawing_p.h" +#include "xlsxdrawinganchor_p.h" +#include "xlsxformat.h" +#include "xlsxformat_p.h" +#include "xlsxrichstring.h" +#include "xlsxsharedstrings_p.h" +#include "xlsxstyles_p.h" +#include "xlsxutility_p.h" +#include "xlsxworkbook.h" +#include "xlsxworksheet_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +WorksheetPrivate::WorksheetPrivate(Worksheet *p, Worksheet::CreateFlag flag) + : AbstractSheetPrivate(p, flag) + , windowProtection(false) + , showFormulas(false) + , showGridLines(true) + , showRowColHeaders(true) + , showZeros(true) + , rightToLeft(false) + , tabSelected(false) + , showRuler(false) + , showOutlineSymbols(true) + , showWhiteSpace(true) + , urlPattern(QStringLiteral("^([fh]tt?ps?://)|(mailto:)|(file://)")) +{ +} + +WorksheetPrivate::~WorksheetPrivate() +{ +} + +/* + Calculate the "spans" attribute of the tag. This is an + XLSX optimisation and isn't strictly required. However, it + makes comparing files easier. The span is the same for each + block of 16 rows. + */ +void WorksheetPrivate::calculateSpans() const +{ + row_spans.clear(); + int span_min = XLSX_COLUMN_MAX + 1; + int span_max = -1; + + for (int row_num = dimension.firstRow(); row_num <= dimension.lastRow(); row_num++) { + auto it = cellTable.constFind(row_num); + if (it != cellTable.constEnd()) { + for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); + col_num++) { + if (it->contains(col_num)) { + if (span_max == -1) { + span_min = col_num; + span_max = col_num; + } else { + if (col_num < span_min) + span_min = col_num; + else if (col_num > span_max) + span_max = col_num; + } + } + } + } + auto cIt = comments.constFind(row_num); + if (cIt != comments.constEnd()) { + for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); + col_num++) { + if (cIt->contains(col_num)) { + if (span_max == -1) { + span_min = col_num; + span_max = col_num; + } else { + if (col_num < span_min) + span_min = col_num; + else if (col_num > span_max) + span_max = col_num; + } + } + } + } + + if (row_num % 16 == 0 || row_num == dimension.lastRow()) { + if (span_max != -1) { + row_spans[row_num / 16] = QStringLiteral("%1:%2").arg(span_min).arg(span_max); + span_min = XLSX_COLUMN_MAX + 1; + span_max = -1; + } + } + } +} + +QString WorksheetPrivate::generateDimensionString() const +{ + if (!dimension.isValid()) + return QStringLiteral("A1"); + else + return dimension.toString(); +} + +/* + Check that row and col are valid and store the max and min + values for use in other methods/elements. The ignore_row / + ignore_col flags is used to indicate that we wish to perform + the dimension check without storing the value. The ignore + flags are use by setRow() and dataValidate. +*/ +int WorksheetPrivate::checkDimensions(int row, int col, bool ignore_row, bool ignore_col) +{ + Q_ASSERT_X(row != 0, "checkDimensions", "row should start from 1 instead of 0"); + Q_ASSERT_X(col != 0, "checkDimensions", "column should start from 1 instead of 0"); + + if (row > XLSX_ROW_MAX || row < 1 || col > XLSX_COLUMN_MAX || col < 1) + return -1; + + if (!ignore_row) { + if (row < dimension.firstRow() || dimension.firstRow() == -1) + dimension.setFirstRow(row); + if (row > dimension.lastRow()) + dimension.setLastRow(row); + } + if (!ignore_col) { + if (col < dimension.firstColumn() || dimension.firstColumn() == -1) + dimension.setFirstColumn(col); + if (col > dimension.lastColumn()) + dimension.setLastColumn(col); + } + + return 0; +} + +/*! + \class Worksheet + \inmodule QtXlsx + \brief Represent one worksheet in the workbook. +*/ + +/*! + * \internal + */ +Worksheet::Worksheet(const QString &name, int id, Workbook *workbook, CreateFlag flag) + : AbstractSheet(name, id, workbook, new WorksheetPrivate(this, flag)) +{ + if (!workbook) // For unit test propose only. Ignore the memory leak. + d_func()->workbook = new Workbook(flag); +} + +/*! + * \internal + * + * Make a copy of this sheet. + */ + +Worksheet *Worksheet::copy(const QString &distName, int distId) const +{ + Q_D(const Worksheet); + Worksheet *sheet = new Worksheet(distName, distId, d->workbook, F_NewFromScratch); + WorksheetPrivate *sheet_d = sheet->d_func(); + + sheet_d->dimension = d->dimension; + + QMapIterator>> it(d->cellTable); + while (it.hasNext()) { + it.next(); + int row = it.key(); + QMapIterator> it2(it.value()); + while (it2.hasNext()) { + it2.next(); + int col = it2.key(); + + auto cell = std::make_shared(it2.value().get()); + cell->d_ptr->parent = sheet; + + if (cell->cellType() == Cell::SharedStringType) + d->workbook->sharedStrings()->addSharedString(cell->d_ptr->richString); + + sheet_d->cellTable[row][col] = cell; + } + } + + sheet_d->merges = d->merges; + // sheet_d->rowsInfo = d->rowsInfo; + // sheet_d->colsInfo = d->colsInfo; + // sheet_d->colsInfoHelper = d->colsInfoHelper; + // sheet_d->dataValidationsList = d->dataValidationsList; + // sheet_d->conditionalFormattingList = d->conditionalFormattingList; + + return sheet; +} + +/*! + * Destroys this workssheet. + */ +Worksheet::~Worksheet() +{ +} + +/*! + * Returns whether sheet is protected. + */ +bool Worksheet::isWindowProtected() const +{ + Q_D(const Worksheet); + return d->windowProtection; +} + +/*! + * Protects/unprotects the sheet based on \a protect. + */ +void Worksheet::setWindowProtected(bool protect) +{ + Q_D(Worksheet); + d->windowProtection = protect; +} + +/*! + * Return whether formulas instead of their calculated results shown in cells + */ +bool Worksheet::isFormulasVisible() const +{ + Q_D(const Worksheet); + return d->showFormulas; +} + +/*! + * Show formulas in cells instead of their calculated results when \a visible is true. + */ +void Worksheet::setFormulasVisible(bool visible) +{ + Q_D(Worksheet); + d->showFormulas = visible; +} + +/*! + * Return whether gridlines is shown or not. + */ +bool Worksheet::isGridLinesVisible() const +{ + Q_D(const Worksheet); + return d->showGridLines; +} + +/*! + * Show or hide the gridline based on \a visible + */ +void Worksheet::setGridLinesVisible(bool visible) +{ + Q_D(Worksheet); + d->showGridLines = visible; +} + +/*! + * Return whether is row and column headers is vislbe. + */ +bool Worksheet::isRowColumnHeadersVisible() const +{ + Q_D(const Worksheet); + return d->showRowColHeaders; +} + +/*! + * Show or hide the row column headers based on \a visible + */ +void Worksheet::setRowColumnHeadersVisible(bool visible) +{ + Q_D(Worksheet); + d->showRowColHeaders = visible; +} + +/*! + * Return whether the sheet is shown right-to-left or not. + */ +bool Worksheet::isRightToLeft() const +{ + Q_D(const Worksheet); + return d->rightToLeft; +} + +/*! + * Enable or disable the right-to-left based on \a enable. + */ +void Worksheet::setRightToLeft(bool enable) +{ + Q_D(Worksheet); + d->rightToLeft = enable; +} + +/*! + * Return whether is cells that have zero value show a zero. + */ +bool Worksheet::isZerosVisible() const +{ + Q_D(const Worksheet); + return d->showZeros; +} + +/*! + * Show a zero in cells that have zero value if \a visible is true. + */ +void Worksheet::setZerosVisible(bool visible) +{ + Q_D(Worksheet); + d->showZeros = visible; +} + +/*! + * Return whether this tab is selected. + */ +bool Worksheet::isSelected() const +{ + Q_D(const Worksheet); + return d->tabSelected; +} + +/*! + * Select this sheet if \a select is true. + */ +void Worksheet::setSelected(bool select) +{ + Q_D(Worksheet); + d->tabSelected = select; +} + +/*! + * Return whether is ruler is shown. + */ +bool Worksheet::isRulerVisible() const +{ + Q_D(const Worksheet); + return d->showRuler; +} + +/*! + * Show or hide the ruler based on \a visible. + */ +void Worksheet::setRulerVisible(bool visible) +{ + Q_D(Worksheet); + d->showRuler = visible; +} + +/*! + * Return whether is outline symbols is shown. + */ +bool Worksheet::isOutlineSymbolsVisible() const +{ + Q_D(const Worksheet); + return d->showOutlineSymbols; +} + +/*! + * Show or hide the outline symbols based ib \a visible. + */ +void Worksheet::setOutlineSymbolsVisible(bool visible) +{ + Q_D(Worksheet); + d->showOutlineSymbols = visible; +} + +/*! + * Return whether is white space is shown. + */ +bool Worksheet::isWhiteSpaceVisible() const +{ + Q_D(const Worksheet); + return d->showWhiteSpace; +} + +/*! + * Show or hide the white space based on \a visible. + */ +void Worksheet::setWhiteSpaceVisible(bool visible) +{ + Q_D(Worksheet); + d->showWhiteSpace = visible; +} + +/*! + * Write \a value to cell (\a row, \a column) with the \a format. + * Both \a row and \a column are all 1-indexed value. + * + * Returns true on success. + */ +bool Worksheet::write(int row, int column, const QVariant &value, const Format &format) +{ + Q_D(Worksheet); + + if (d->checkDimensions(row, column)) + return false; + + bool ret = true; + if (value.isNull()) { + // Blank + ret = writeBlank(row, column, format); + } else if (value.userType() == QMetaType::QString) { + // String + QString token = value.toString(); + bool ok; + + if (token.startsWith(QLatin1String("="))) { + // convert to formula + ret = writeFormula(row, column, CellFormula(token), format); + } else if (d->workbook->isStringsToHyperlinksEnabled() && token.contains(d->urlPattern)) { + // convert to url + ret = writeHyperlink(row, column, QUrl(token)); + } else if (d->workbook->isStringsToNumbersEnabled() && (value.toDouble(&ok), ok)) { + // Try convert string to number if the flag enabled. + ret = writeNumeric(row, column, value.toDouble(), format); + } else { + // normal string now + ret = writeString(row, column, token, format); + } + } else if (value.userType() == qMetaTypeId()) { + ret = writeString(row, column, value.value(), format); + } else if (value.userType() == QMetaType::Int || value.userType() == QMetaType::UInt || + value.userType() == QMetaType::LongLong || + value.userType() == QMetaType::ULongLong || value.userType() == QMetaType::Double || + value.userType() == QMetaType::Float) { + // Number + + ret = writeNumeric(row, column, value.toDouble(), format); + } else if (value.userType() == QMetaType::Bool) { + // Bool + ret = writeBool(row, column, value.toBool(), format); + } else if (value.userType() == QMetaType::QDateTime) // dev67 + { + // DateTime, Date + // note that, QTime can't convert to QDateTime + ret = writeDateTime(row, column, value.toDateTime(), format); + } else if (value.userType() == QMetaType::QDate) // dev67 + { + ret = writeDate(row, column, value.toDate(), format); + } else if (value.userType() == QMetaType::QTime) { + // Time + ret = writeTime(row, column, value.toTime(), format); + } else if (value.userType() == QMetaType::QUrl) { + // Url + ret = writeHyperlink(row, column, value.toUrl(), format); + } else { + // Wrong type + return false; + } + + return ret; +} + +/*! + * \overload + * Write \a value to cell \a row_column with the \a format. + * Both row and column are all 1-indexed value. + * Returns true on success. + */ +bool Worksheet::write(const CellReference &row_column, const QVariant &value, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return write(row_column.row(), row_column.column(), value, format); +} + +/*! + \overload + Return the contents of the cell \a row_column. + */ +QVariant Worksheet::read(const CellReference &row_column) const +{ + if (!row_column.isValid()) + return QVariant(); + + return read(row_column.row(), row_column.column()); +} + +/*! + Return the contents of the cell (\a row, \a column). + */ +QVariant Worksheet::read(int row, int column) const +{ + Q_D(const Worksheet); + + Cell *cell = cellAt(row, column); + if (!cell) + return QVariant(); + + if (cell->hasFormula()) { + if (cell->formula().formulaType() == CellFormula::NormalType) { + return QVariant(QLatin1String("=") + cell->formula().formulaText()); + } else if (cell->formula().formulaType() == CellFormula::SharedType) { + if (!cell->formula().formulaText().isEmpty()) { + return QVariant(QLatin1String("=") + cell->formula().formulaText()); + } else { + int si = cell->formula().sharedIndex(); + const CellFormula &rootFormula = d->sharedFormulaMap[si]; + CellReference rootCellRef = rootFormula.reference().topLeft(); + QString rootFormulaText = rootFormula.formulaText(); + QString newFormulaText = + convertSharedFormula(rootFormulaText, rootCellRef, CellReference(row, column)); + return QVariant(QLatin1String("=") + newFormulaText); + } + } + } + + if (cell->isDateTime()) { + QVariant vDateTime = cell->dateTime(); + return vDateTime; + } + + return cell->value(); +} + +/*! + * Returns the cell at the given \a row_column. If there + * is no cell at the specified position, the function returns 0. + */ +Cell *Worksheet::cellAt(const CellReference &row_column) const +{ + if (!row_column.isValid()) + return nullptr; + + return cellAt(row_column.row(), row_column.column()); +} + +/*! + * Returns the cell at the given \a row and \a column. If there + * is no cell at the specified position, the function returns 0. + */ +Cell *Worksheet::cellAt(int row, int col) const +{ + Q_D(const Worksheet); + auto it = d->cellTable.constFind(row); + if (it == d->cellTable.constEnd()) + return nullptr; + if (!it->contains(col)) + return nullptr; + + return (*it)[col].get(); +} + +Format WorksheetPrivate::cellFormat(int row, int col) const +{ + auto it = cellTable.constFind(row); + if (it == cellTable.constEnd()) + return Format(); + if (!it->contains(col)) + return Format(); + return (*it)[col]->format(); +} + +/*! + \overload + Write string \a value to the cell \a row_column with the \a format. + + Returns true on success. + */ +bool Worksheet::writeString(const CellReference &row_column, + const RichString &value, + const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeString(row_column.row(), row_column.column(), value, format); +} + +/*! + Write string \a value to the cell (\a row, \a column) with the \a format. + Returns true on success. +*/ +bool Worksheet::writeString(int row, int column, const RichString &value, const Format &format) +{ + Q_D(Worksheet); + // QString content = value.toPlainString(); + if (d->checkDimensions(row, column)) + return false; + + // if (content.size() > d->xls_strmax) { + // content = content.left(d->xls_strmax); + // error = -2; + // } + + d->sharedStrings()->addSharedString(value); + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + if (value.fragmentCount() == 1 && value.fragmentFormat(0).isValid()) + fmt.mergeFormat(value.fragmentFormat(0)); + d->workbook->styles()->addXfFormat(fmt); + auto cell = std::make_shared(value.toPlainString(), Cell::SharedStringType, fmt, this); + cell->d_ptr->richString = value; + d->cellTable[row][column] = cell; + return true; +} + +/*! + \overload + Write string \a value to the cell \a row_column with the \a format. + */ +bool Worksheet::writeString(const CellReference &row_column, + const QString &value, + const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeString(row_column.row(), row_column.column(), value, format); +} + +/*! + \overload + + Write string \a value to the cell (\a row, \a column) with the \a format. + Returns true on success. +*/ +bool Worksheet::writeString(int row, int column, const QString &value, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + RichString rs; + if (d->workbook->isHtmlToRichStringEnabled() && Qt::mightBeRichText(value)) + rs.setHtml(value); + else + rs.addFragment(value, Format()); + + return writeString(row, column, rs, format); +} + +/*! + \overload + Write string \a value to the cell \a row_column with the \a format + */ +bool Worksheet::writeInlineString(const CellReference &row_column, + const QString &value, + const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeInlineString(row_column.row(), row_column.column(), value, format); +} + +/*! + Write string \a value to the cell (\a row, \a column) with the \a format. + Returns true on success. +*/ +bool Worksheet::writeInlineString(int row, int column, const QString &value, const Format &format) +{ + Q_D(Worksheet); + // int error = 0; + QString content = value; + if (d->checkDimensions(row, column)) + return false; + + if (value.size() > XLSX_STRING_MAX) { + content = value.left(XLSX_STRING_MAX); + // error = -2; + } + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + d->workbook->styles()->addXfFormat(fmt); + d->cellTable[row][column] = std::make_shared(value, Cell::InlineStringType, fmt, this); + return true; +} + +/*! + \overload + Write numeric \a value to the cell \a row_column with the \a format. + Returns true on success. + */ +bool Worksheet::writeNumeric(const CellReference &row_column, double value, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeNumeric(row_column.row(), row_column.column(), value, format); +} + +/*! + Write numeric \a value to the cell (\a row, \a column) with the \a format. + Returns true on success. +*/ +bool Worksheet::writeNumeric(int row, int column, double value, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + d->workbook->styles()->addXfFormat(fmt); + d->cellTable[row][column] = std::make_shared(value, Cell::NumberType, fmt, this); + return true; +} + +/*! + \overload + Write \a formula to the cell \a row_column with the \a format and \a result. + Returns true on success. + */ +bool Worksheet::writeFormula(const CellReference &row_column, + const CellFormula &formula, + const Format &format, + double result) +{ + if (!row_column.isValid()) + return false; + + return writeFormula(row_column.row(), row_column.column(), formula, format, result); +} + +/*! + Write \a formula_ to the cell (\a row, \a column) with the \a format and \a result. + Returns true on success. +*/ +bool Worksheet::writeFormula(int row, + int column, + const CellFormula &formula_, + const Format &format, + double result) +{ + Q_D(Worksheet); + + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + d->workbook->styles()->addXfFormat(fmt); + + CellFormula formula = formula_; + formula.d->ca = true; + if (formula.formulaType() == CellFormula::SharedType) { + // Assign proper shared index for shared formula + int si = 0; + while (d->sharedFormulaMap.contains(si)) { + ++si; + } + formula.d->si = si; + d->sharedFormulaMap[si] = formula; + } + + auto data = std::make_shared(result, Cell::NumberType, fmt, this); + data->d_ptr->formula = formula; + d->cellTable[row][column] = data; + + CellRange range = formula.reference(); + if (formula.formulaType() == CellFormula::SharedType) { + CellFormula sf(QString(), CellFormula::SharedType); + sf.d->si = formula.sharedIndex(); + for (int r = range.firstRow(); r <= range.lastRow(); ++r) { + for (int c = range.firstColumn(); c <= range.lastColumn(); ++c) { + if (!(r == row && c == column)) { + if (Cell *cell = cellAt(r, c)) { + cell->d_ptr->formula = sf; + } else { + auto newCell = std::make_shared(result, Cell::NumberType, fmt, this); + newCell->d_ptr->formula = sf; + d->cellTable[r][c] = newCell; + } + } + } + } + } + + return true; +} + +/*! + \overload + Write a empty cell \a row_column with the \a format. + Returns true on success. + */ +bool Worksheet::writeBlank(const CellReference &row_column, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeBlank(row_column.row(), row_column.column(), format); +} + +/*! + Write a empty cell (\a row, \a column) with the \a format. + Returns true on success. + */ +bool Worksheet::writeBlank(int row, int column, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + d->workbook->styles()->addXfFormat(fmt); + + // Note: NumberType with an invalid QVariant value means blank. + d->cellTable[row][column] = std::make_shared(QVariant{}, Cell::NumberType, fmt, this); + + return true; +} +/*! + \overload + Write a bool \a value to the cell \a row_column with the \a format. + Returns true on success. + */ +bool Worksheet::writeBool(const CellReference &row_column, bool value, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeBool(row_column.row(), row_column.column(), value, format); +} + +/*! + Write a bool \a value to the cell (\a row, \a column) with the \a format. + Returns true on success. + */ +bool Worksheet::writeBool(int row, int column, bool value, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + d->workbook->styles()->addXfFormat(fmt); + d->cellTable[row][column] = std::make_shared(value, Cell::BooleanType, fmt, this); + + return true; +} +/*! + \overload + Write a QDateTime \a dt to the cell \a row_column with the \a format. + Returns true on success. + */ +bool Worksheet::writeDateTime(const CellReference &row_column, + const QDateTime &dt, + const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeDateTime(row_column.row(), row_column.column(), dt, format); +} + +/*! + Write a QDateTime \a dt to the cell (\a row, \a column) with the \a format. + Returns true on success. + */ +bool Worksheet::writeDateTime(int row, int column, const QDateTime &dt, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + if (!fmt.isValid() || !fmt.isDateTimeFormat()) + fmt.setNumberFormat(d->workbook->defaultDateFormat()); + d->workbook->styles()->addXfFormat(fmt); + + double value = datetimeToNumber(dt, d->workbook->isDate1904()); + + d->cellTable[row][column] = std::make_shared(value, Cell::NumberType, fmt, this); + + return true; +} + +// dev67 +bool Worksheet::writeDate(const CellReference &row_column, const QDate &dt, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeDate(row_column.row(), row_column.column(), dt, format); +} + +// dev67 +bool Worksheet::writeDate(int row, int column, const QDate &dt, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + + if (!fmt.isValid() || !fmt.isDateTimeFormat()) + fmt.setNumberFormat(d->workbook->defaultDateFormat()); + + d->workbook->styles()->addXfFormat(fmt); + + double value = datetimeToNumber(QDateTime(dt, QTime(0, 0, 0)), d->workbook->isDate1904()); + + d->cellTable[row][column] = std::make_shared(value, Cell::NumberType, fmt, this); + + return true; +} + +/*! + \overload + Write a QTime \a t to the cell \a row_column with the \a format. + Returns true on success. + */ +bool Worksheet::writeTime(const CellReference &row_column, const QTime &t, const Format &format) +{ + if (!row_column.isValid()) + return false; + + return writeTime(row_column.row(), row_column.column(), t, format); +} + +/*! + Write a QTime \a t to the cell (\a row, \a column) with the \a format. + Returns true on success. + */ +bool Worksheet::writeTime(int row, int column, const QTime &t, const Format &format) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + if (!fmt.isValid() || !fmt.isDateTimeFormat()) + fmt.setNumberFormat(QStringLiteral("hh:mm:ss")); + d->workbook->styles()->addXfFormat(fmt); + + d->cellTable[row][column] = + std::make_shared(timeToNumber(t), Cell::NumberType, fmt, this); + + return true; +} + +/*! + \overload + Write a QUrl \a url to the cell \a row_column with the given \a format \a display and \a + tip. Returns true on success. + */ +bool Worksheet::writeHyperlink(const CellReference &row_column, + const QUrl &url, + const Format &format, + const QString &display, + const QString &tip) +{ + if (!row_column.isValid()) + return false; + + return writeHyperlink(row_column.row(), row_column.column(), url, format, display, tip); +} + +/*! + Write a QUrl \a url to the cell (\a row, \a column) with the given \a format \a display and + \a tip. Returns true on success. + */ +bool Worksheet::writeHyperlink(int row, + int column, + const QUrl &url, + const Format &format, + const QString &display, + const QString &tip) +{ + Q_D(Worksheet); + if (d->checkDimensions(row, column)) + return false; + + // int error = 0; + + QString urlString = url.toString(); + + // Generate proper display string + QString displayString = display.isEmpty() ? urlString : display; + if (displayString.startsWith(QLatin1String("mailto:"))) + displayString.replace(QLatin1String("mailto:"), QString()); + if (displayString.size() > XLSX_STRING_MAX) { + displayString = displayString.left(XLSX_STRING_MAX); + // error = -2; + } + + /* + Location within target. If target is a workbook (or this workbook) + this shall refer to a sheet and cell or a defined name. Can also + be an HTML anchor if target is HTML file. + + c:\temp\file.xlsx#Sheet!A1 + http://a.com/aaa.html#aaaaa + */ + QString locationString; + if (url.hasFragment()) { + locationString = url.fragment(); + urlString = url.toString(QUrl::RemoveFragment); + } + + Format fmt = format.isValid() ? format : d->cellFormat(row, column); + // Given a default style for hyperlink + if (!fmt.isValid()) { + fmt.setVerticalAlignment(Format::AlignVCenter); + fmt.setFontColor(Qt::blue); + fmt.setFontUnderline(Format::FontUnderlineSingle); + } + d->workbook->styles()->addXfFormat(fmt); + + // Write the hyperlink string as normal string. + d->sharedStrings()->addSharedString(displayString); + d->cellTable[row][column] = + std::make_shared(displayString, Cell::SharedStringType, fmt, this); + + // Store the hyperlink data in a separate table + d->urlTable[row][column] = QSharedPointer(new XlsxHyperlinkData( + XlsxHyperlinkData::External, urlString, locationString, QString(), tip)); + + return true; +} + +/*! + * Add one DataValidation \a validation to the sheet. + * Returns true on success. + */ +bool Worksheet::addDataValidation(const DataValidation &validation) +{ + Q_D(Worksheet); + if (validation.ranges().isEmpty() || validation.validationType() == DataValidation::None) + return false; + + d->dataValidationsList.append(validation); + return true; +} + +/*! + * Add one ConditionalFormatting \a cf to the sheet. + * Returns true on success. + */ +bool Worksheet::addConditionalFormatting(const ConditionalFormatting &cf) +{ + Q_D(Worksheet); + if (cf.ranges().isEmpty()) + return false; + + for (int i = 0; i < cf.d->cfRules.size(); ++i) { + const std::shared_ptr &rule = cf.d->cfRules[i]; + if (!rule->dxfFormat.isEmpty()) + d->workbook->styles()->addDxfFormat(rule->dxfFormat); + rule->priority = 1; + } + d->conditionalFormattingList.append(cf); + return true; +} + +/*! + * Insert an \a image at the position \a row, \a column + * Returns true on success. + */ +int Worksheet::insertImage(int row, int column, const QImage &image) +{ + Q_D(Worksheet); + + int imageIndex = 0; + + if (image.isNull()) + return imageIndex; + + if (!d->drawing) { + d->drawing = std::make_shared(this, F_NewFromScratch); + } + + DrawingOneCellAnchor *anchor = + new DrawingOneCellAnchor(d->drawing.get(), DrawingAnchor::Picture); + + /* + The size are expressed as English Metric Units (EMUs). + EMU is 1/360 000 of centimiter. + */ + anchor->from = XlsxMarker(row, column, 0, 0); + float scaleX = 36e6f / std::max(1, image.dotsPerMeterX()); + float scaleY = 36e6f / std::max(1, image.dotsPerMeterY()); + anchor->ext = QSize(int(image.width() * scaleX), int(image.height() * scaleY)); + + anchor->setObjectPicture(image); + + imageIndex = anchor->getm_id(); + + return imageIndex; +} + +bool Worksheet::getImage(int imageIndex, QImage &img) +{ + Q_D(Worksheet); + + if (imageIndex <= (-1)) { + return false; + } + + if (d->drawing == nullptr) { + return false; + } + + int realImageIndex = imageIndex - 1; // minus one + + DrawingAnchor *danchor = d->drawing->anchors.at(realImageIndex); + // QSharedPointer // for multithread + if (danchor == nullptr) { + return false; + } + + bool ret = danchor->getObjectPicture(img); + return ret; +} + +bool Worksheet::getImage(int row, int column, QImage &img) +{ + Q_D(Worksheet); + + if (d->drawing == nullptr) { + return false; + } + + for (int i = 0; i < d->drawing->anchors.size(); i++) { + if (d->drawing->anchors[i]->row() == row && d->drawing->anchors[i]->col() == column) { + DrawingAnchor *danchor = d->drawing->anchors.at(i); + + if (danchor == nullptr) { + return false; + } + + bool ret = danchor->getObjectPicture(img); + return ret; + } + } + return false; +} + +uint Worksheet::getImageCount() +{ + Q_D(Worksheet); + + if (d->drawing == nullptr) { + return false; + } + + int size = d->drawing->anchors.size(); + return uint(size); +} + +/*! + * Creates an chart with the given \a size and insert + * at the position \a row, \a column. + * The chart will be returned. + */ +Chart *Worksheet::insertChart(int row, int column, const QSize &size) +{ + Q_D(Worksheet); + + if (!d->drawing) + d->drawing = std::make_shared(this, F_NewFromScratch); + + DrawingOneCellAnchor *anchor = + new DrawingOneCellAnchor(d->drawing.get(), DrawingAnchor::Picture); + + /* + The size are expressed as English Metric Units (EMUs). There are + 12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per + pixel + */ + anchor->from = XlsxMarker(row, column, 0, 0); + anchor->ext = size * 9525; + + QSharedPointer chart = QSharedPointer(new Chart(this, F_NewFromScratch)); + anchor->setObjectGraphicFrame(chart); + + return chart.data(); +} + +/*! + Merge a \a range of cells. The first cell should contain the data and the others should + be blank. All cells will be applied the same style if a valid \a format is given. + Returns true on success. + + \note All cells except the top-left one will be cleared. + */ +bool Worksheet::mergeCells(const CellRange &range, const Format &format) +{ + Q_D(Worksheet); + if (range.rowCount() < 2 && range.columnCount() < 2) + return false; + + if (d->checkDimensions(range.firstRow(), range.firstColumn())) + return false; + + if (format.isValid()) { + d->workbook->styles()->addXfFormat(format); + } + + for (int row = range.firstRow(); row <= range.lastRow(); ++row) { + for (int col = range.firstColumn(); col <= range.lastColumn(); ++col) { + if (row == range.firstRow() && col == range.firstColumn()) { + Cell *cell = cellAt(row, col); + if (cell) { + if (format.isValid()) + cell->d_ptr->format = format; + } else { + writeBlank(row, col, format); + } + } else { + writeBlank(row, col, format); + } + } + } + + d->merges.append(range); + return true; +} + +/*! + Unmerge the cells in the \a range. Returns true on success. + +*/ +bool Worksheet::unmergeCells(const CellRange &range) +{ + Q_D(Worksheet); + return d->merges.removeOne(range); +} + +/*! + Returns all the merged cells. +*/ +QList Worksheet::mergedCells() const +{ + Q_D(const Worksheet); + + // dev57 + + QList emptyList; + + if (d->type == AbstractSheet::ST_WorkSheet) { + return d->merges; + } else if (d->type == AbstractSheet::ST_ChartSheet) { + } else if (d->type == AbstractSheet::ST_DialogSheet) { + } else if (d->type == AbstractSheet::ST_MacroSheet) { + } else { // undefined + } + + return emptyList; +} + +/*! + * \internal + */ +void Worksheet::saveToXmlFile(QIODevice *device) const +{ + Q_D(const Worksheet); + d->relationships->clear(); + + QXmlStreamWriter writer(device); + + writer.writeStartDocument(QStringLiteral("1.0"), true); + writer.writeStartElement(QStringLiteral("worksheet")); + writer.writeAttribute( + QStringLiteral("xmlns"), + QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); + writer.writeAttribute( + QStringLiteral("xmlns:r"), + QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + + // for Excel 2010 + // writer.writeAttribute("xmlns:mc", + // "http://schemas.openxmlformats.org/markup-compatibility/2006"); + // writer.writeAttribute("xmlns:x14ac", + // "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"); + // writer.writeAttribute("mc:Ignorable", "x14ac"); + + writer.writeStartElement(QStringLiteral("dimension")); + writer.writeAttribute(QStringLiteral("ref"), d->generateDimensionString()); + writer.writeEndElement(); // dimension + + writer.writeStartElement(QStringLiteral("sheetViews")); + writer.writeStartElement(QStringLiteral("sheetView")); + if (d->windowProtection) + writer.writeAttribute(QStringLiteral("windowProtection"), QStringLiteral("1")); + if (d->showFormulas) + writer.writeAttribute(QStringLiteral("showFormulas"), QStringLiteral("1")); + if (!d->showGridLines) + writer.writeAttribute(QStringLiteral("showGridLines"), QStringLiteral("0")); + if (!d->showRowColHeaders) + writer.writeAttribute(QStringLiteral("showRowColHeaders"), QStringLiteral("0")); + if (!d->showZeros) + writer.writeAttribute(QStringLiteral("showZeros"), QStringLiteral("0")); + if (d->rightToLeft) + writer.writeAttribute(QStringLiteral("rightToLeft"), QStringLiteral("1")); + if (d->tabSelected) + writer.writeAttribute(QStringLiteral("tabSelected"), QStringLiteral("1")); + if (!d->showRuler) + writer.writeAttribute(QStringLiteral("showRuler"), QStringLiteral("0")); + if (!d->showOutlineSymbols) + writer.writeAttribute(QStringLiteral("showOutlineSymbols"), QStringLiteral("0")); + if (!d->showWhiteSpace) + writer.writeAttribute(QStringLiteral("showWhiteSpace"), QStringLiteral("0")); + writer.writeAttribute(QStringLiteral("workbookViewId"), QStringLiteral("0")); + writer.writeEndElement(); // sheetView + writer.writeEndElement(); // sheetViews + + writer.writeStartElement(QStringLiteral("sheetFormatPr")); + writer.writeAttribute(QStringLiteral("defaultRowHeight"), + QString::number(d->sheetFormatProps.defaultRowHeight)); + writer.writeAttribute(QStringLiteral("customHeight"), + xsdBoolean(d->sheetFormatProps.customHeight)); + writer.writeAttribute(QStringLiteral("zeroHeight"), xsdBoolean(d->sheetFormatProps.zeroHeight)); + writer.writeAttribute(QStringLiteral("outlineLevelRow"), + QString::number(d->sheetFormatProps.outlineLevelRow)); + writer.writeAttribute(QStringLiteral("outlineLevelCol"), + QString::number(d->sheetFormatProps.outlineLevelCol)); + // for Excel 2010 + // writer.writeAttribute("x14ac:dyDescent", "0.25"); + writer.writeEndElement(); // sheetFormatPr + + if (!d->colsInfo.isEmpty()) { + writer.writeStartElement(QStringLiteral("cols")); + QMapIterator> it(d->colsInfo); + while (it.hasNext()) { + it.next(); + QSharedPointer col_info = it.value(); + writer.writeStartElement(QStringLiteral("col")); + writer.writeAttribute(QStringLiteral("min"), QString::number(col_info->firstColumn)); + writer.writeAttribute(QStringLiteral("max"), QString::number(col_info->lastColumn)); + if (col_info->width) + writer.writeAttribute(QStringLiteral("width"), + QString::number(col_info->width, 'g', 15)); + if (!col_info->format.isEmpty()) + writer.writeAttribute(QStringLiteral("style"), + QString::number(col_info->format.xfIndex())); + if (col_info->hidden) + writer.writeAttribute(QStringLiteral("hidden"), QStringLiteral("1")); + if (col_info->width) + writer.writeAttribute(QStringLiteral("customWidth"), QStringLiteral("1")); + if (col_info->outlineLevel) + writer.writeAttribute(QStringLiteral("outlineLevel"), + QString::number(col_info->outlineLevel)); + if (col_info->collapsed) + writer.writeAttribute(QStringLiteral("collapsed"), QStringLiteral("1")); + writer.writeEndElement(); // col + } + writer.writeEndElement(); // cols + } + + writer.writeStartElement(QStringLiteral("sheetData")); + if (d->dimension.isValid()) + d->saveXmlSheetData(writer); + writer.writeEndElement(); // sheetData + + d->saveXmlMergeCells(writer); + for (const ConditionalFormatting &cf : d->conditionalFormattingList) + cf.saveToXml(writer); + d->saveXmlDataValidations(writer); + + //{{ liufeijin : write pagesettings add by liufeijin 20181028 + + // fixed by j2doll [dev18] + // NOTE: empty element is not problem. but, empty structure of element is not parsed by Excel. + + // pageMargins + if (false == d->PMleft.isEmpty() && false == d->PMright.isEmpty() && + false == d->PMtop.isEmpty() && false == d->PMbotton.isEmpty() && + false == d->PMheader.isEmpty() && false == d->PMfooter.isEmpty()) { + writer.writeStartElement(QStringLiteral("pageMargins")); + + writer.writeAttribute(QStringLiteral("left"), d->PMleft); + writer.writeAttribute(QStringLiteral("right"), d->PMright); + writer.writeAttribute(QStringLiteral("top"), d->PMtop); + writer.writeAttribute(QStringLiteral("bottom"), d->PMbotton); + writer.writeAttribute(QStringLiteral("header"), d->PMheader); + writer.writeAttribute(QStringLiteral("footer"), d->PMfooter); + + writer.writeEndElement(); // pageMargins + } + + // dev57 + if (!d->Prid.isEmpty()) { + writer.writeStartElement(QStringLiteral("pageSetup")); // pageSetup + + writer.writeAttribute(QStringLiteral("r:id"), d->Prid); + + if (!d->PverticalDpi.isEmpty()) { + writer.writeAttribute(QStringLiteral("verticalDpi"), d->PverticalDpi); + } + + if (!d->PhorizontalDpi.isEmpty()) { + writer.writeAttribute(QStringLiteral("horizontalDpi"), d->PhorizontalDpi); + } + + if (!d->PuseFirstPageNumber.isEmpty()) { + writer.writeAttribute(QStringLiteral("useFirstPageNumber"), d->PuseFirstPageNumber); + } + + if (!d->PfirstPageNumber.isEmpty()) { + writer.writeAttribute(QStringLiteral("firstPageNumber"), d->PfirstPageNumber); + } + + if (!d->Pscale.isEmpty()) { + writer.writeAttribute(QStringLiteral("scale"), d->Pscale); + } + + if (!d->PpaperSize.isEmpty()) { + writer.writeAttribute(QStringLiteral("paperSize"), d->PpaperSize); + } + + if (!d->Porientation.isEmpty()) { + writer.writeAttribute(QStringLiteral("orientation"), d->Porientation); + } + + if (!d->Pcopies.isEmpty()) { + writer.writeAttribute(QStringLiteral("copies"), d->Pcopies); + } + + writer.writeEndElement(); // pageSetup + + } // if ( !d->Prid.isEmpty() ) + + // headerFooter + if (!(d->ModdHeader.isNull()) || !(d->MoodFooter.isNull())) { + writer.writeStartElement(QStringLiteral("headerFooter")); // headerFooter + + if (!d->MoodalignWithMargins.isEmpty()) { + writer.writeAttribute(QStringLiteral("alignWithMargins"), d->MoodalignWithMargins); + } + + if (!d->ModdHeader.isNull()) { + writer.writeStartElement(QStringLiteral("oddHeader")); + writer.writeCharacters(d->ModdHeader); + writer.writeEndElement(); // oddHeader + } + + if (!d->MoodFooter.isNull()) { + writer.writeTextElement(QStringLiteral("oddFooter"), d->MoodFooter); + } + + writer.writeEndElement(); // headerFooter + } + + d->saveXmlHyperlinks(writer); + d->saveXmlDrawings(writer); + + writer.writeEndElement(); // worksheet + writer.writeEndDocument(); +} + +//{{ liufeijin +bool Worksheet::setStartPage(int spagen) +{ + Q_D(Worksheet); + + d->PfirstPageNumber = QString::number(spagen); + + return true; +} +//}} + +void WorksheetPrivate::saveXmlSheetData(QXmlStreamWriter &writer) const +{ + calculateSpans(); + for (int row_num = dimension.firstRow(); row_num <= dimension.lastRow(); row_num++) { + auto ctIt = cellTable.constFind(row_num); + auto riIt = rowsInfo.constFind(row_num); + if (ctIt == cellTable.constEnd() && riIt == rowsInfo.constEnd() && + !comments.contains(row_num)) { + // Only process rows with cell data / comments / formatting + continue; + } + + int span_index = (row_num - 1) / 16; + QString span; + auto rsIt = row_spans.constFind(span_index); + if (rsIt != row_spans.constEnd()) + span = rsIt.value(); + + writer.writeStartElement(QStringLiteral("row")); + writer.writeAttribute(QStringLiteral("r"), QString::number(row_num)); + + if (!span.isEmpty()) + writer.writeAttribute(QStringLiteral("spans"), span); + + if (riIt != rowsInfo.constEnd()) { + QSharedPointer rowInfo = riIt.value(); + if (!rowInfo->format.isEmpty()) { + writer.writeAttribute(QStringLiteral("s"), + QString::number(rowInfo->format.xfIndex())); + writer.writeAttribute(QStringLiteral("customFormat"), QStringLiteral("1")); + } + + //! Todo: support customHeight from info struct + //! Todo: where does this magic number '15' come from? + if (rowInfo->customHeight) { + writer.writeAttribute(QStringLiteral("ht"), QString::number(rowInfo->height)); + writer.writeAttribute(QStringLiteral("customHeight"), QStringLiteral("1")); + } else { + writer.writeAttribute(QStringLiteral("customHeight"), QStringLiteral("0")); + } + + if (rowInfo->hidden) + writer.writeAttribute(QStringLiteral("hidden"), QStringLiteral("1")); + if (rowInfo->outlineLevel > 0) + writer.writeAttribute(QStringLiteral("outlineLevel"), + QString::number(rowInfo->outlineLevel)); + if (rowInfo->collapsed) + writer.writeAttribute(QStringLiteral("collapsed"), QStringLiteral("1")); + } + + // Write cell data if row contains filled cells + if (ctIt != cellTable.constEnd()) { + for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); + col_num++) { + if (ctIt->contains(col_num)) { + saveXmlCellData(writer, row_num, col_num, (*ctIt)[col_num]); + } + } + } + writer.writeEndElement(); // row + } +} + +void WorksheetPrivate::saveXmlCellData(QXmlStreamWriter &writer, + int row, + int col, + std::shared_ptr cell) const +{ + Q_Q(const Worksheet); + + // This is the innermost loop so efficiency is important. + QString cell_pos = CellReference(row, col).toString(); + + writer.writeStartElement(QStringLiteral("c")); + writer.writeAttribute(QStringLiteral("r"), cell_pos); + + QMap>::ConstIterator rIt; + QMap>::ConstIterator cIt; + + // Style used by the cell, row or col + if (!cell->format().isEmpty()) + writer.writeAttribute(QStringLiteral("s"), QString::number(cell->format().xfIndex())); + else if ((rIt = rowsInfo.constFind(row)) != rowsInfo.constEnd() && !(*rIt)->format.isEmpty()) + writer.writeAttribute(QStringLiteral("s"), QString::number((*rIt)->format.xfIndex())); + else if ((cIt = colsInfoHelper.constFind(col)) != colsInfoHelper.constEnd() && + !(*cIt)->format.isEmpty()) + writer.writeAttribute(QStringLiteral("s"), QString::number((*cIt)->format.xfIndex())); + + if (cell->cellType() == Cell::SharedStringType) // 's' + { + int sst_idx; + if (cell->isRichString()) + sst_idx = sharedStrings()->getSharedStringIndex(cell->d_ptr->richString); + else + sst_idx = sharedStrings()->getSharedStringIndex(cell->value().toString()); + + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("s")); + writer.writeTextElement(QStringLiteral("v"), QString::number(sst_idx)); + } else if (cell->cellType() == Cell::InlineStringType) // 'inlineStr' + { + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("inlineStr")); + writer.writeStartElement(QStringLiteral("is")); + if (cell->isRichString()) { + // Rich text string + RichString string = cell->d_ptr->richString; + for (int i = 0; i < string.fragmentCount(); ++i) { + writer.writeStartElement(QStringLiteral("r")); + if (string.fragmentFormat(i).hasFontData()) { + writer.writeStartElement(QStringLiteral("rPr")); + //: Todo + writer.writeEndElement(); // rPr + } + writer.writeStartElement(QStringLiteral("t")); + if (isSpaceReserveNeeded(string.fragmentText(i))) + writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve")); + writer.writeCharacters(string.fragmentText(i)); + writer.writeEndElement(); // t + writer.writeEndElement(); // r + } + } else { + writer.writeStartElement(QStringLiteral("t")); + QString string = cell->value().toString(); + if (isSpaceReserveNeeded(string)) + writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve")); + writer.writeCharacters(string); + writer.writeEndElement(); // t + } + writer.writeEndElement(); // is + } else if (cell->cellType() == Cell::NumberType) // 'n' + { + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("n")); // dev67 + + if (cell->hasFormula()) { + QString strFormula = cell->formula().d->formula; + Q_UNUSED(strFormula); + cell->formula().saveToXml(writer); + } + + if (cell->value().isValid()) { // note that, invalid value means 'v' is blank + double value = cell->value().toDouble(); + writer.writeTextElement(QStringLiteral("v"), QString::number(value, 'g', 15)); + } + } else if (cell->cellType() == Cell::StringType) // 'str' + { + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("str")); + if (cell->hasFormula()) + cell->formula().saveToXml(writer); + + writer.writeTextElement(QStringLiteral("v"), cell->value().toString()); + } else if (cell->cellType() == Cell::BooleanType) // 'b' + { + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("b")); + + // dev34 + + if (cell->hasFormula()) { + QString strFormula = cell->formula().d->formula; + Q_UNUSED(strFormula); + cell->formula().saveToXml(writer); + } + + writer.writeTextElement(QStringLiteral("v"), + cell->value().toBool() ? QStringLiteral("1") : QStringLiteral("0")); + } else if (cell->cellType() == Cell::DateType) // 'd' + { + // dev67 + + double num = cell->value().toDouble(); + bool is1904 = q->workbook()->isDate1904(); + if (!is1904 && num > 60) // for mac os excel + { + num = num - 1; + } + + // number type. see for 18.18.11 ST_CellType (Cell Type) more information. + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("n")); + writer.writeTextElement(QStringLiteral("v"), cell->value().toString()); + + } else if (cell->cellType() == Cell::ErrorType) // 'e' + { + writer.writeAttribute(QStringLiteral("t"), QStringLiteral("e")); + writer.writeTextElement(QStringLiteral("v"), cell->value().toString()); + } else // if (cell->cellType() == Cell::CustomType) + { + // custom type + + if (cell->hasFormula()) { + QString strFormula = cell->formula().d->formula; + Q_UNUSED(strFormula); + cell->formula().saveToXml(writer); + } + + if (cell->value().isValid()) { // note that, invalid value means 'v' is blank + double value = cell->value().toDouble(); + writer.writeTextElement(QStringLiteral("v"), QString::number(value, 'g', 15)); + } + } + + writer.writeEndElement(); // c +} + +void WorksheetPrivate::saveXmlMergeCells(QXmlStreamWriter &writer) const +{ + if (merges.isEmpty()) + return; + + writer.writeStartElement(QStringLiteral("mergeCells")); + writer.writeAttribute(QStringLiteral("count"), QString::number(merges.size())); + + for (const CellRange &range : merges) { + writer.writeEmptyElement(QStringLiteral("mergeCell")); + writer.writeAttribute(QStringLiteral("ref"), range.toString()); + } + + writer.writeEndElement(); // mergeCells +} + +void WorksheetPrivate::saveXmlDataValidations(QXmlStreamWriter &writer) const +{ + if (dataValidationsList.isEmpty()) + return; + + writer.writeStartElement(QStringLiteral("dataValidations")); + writer.writeAttribute(QStringLiteral("count"), QString::number(dataValidationsList.size())); + + for (const DataValidation &validation : dataValidationsList) + validation.saveToXml(writer); + + writer.writeEndElement(); // dataValidations +} + +void WorksheetPrivate::saveXmlHyperlinks(QXmlStreamWriter &writer) const +{ + if (urlTable.isEmpty()) + return; + + writer.writeStartElement(QStringLiteral("hyperlinks")); + QMapIterator>> it(urlTable); + + while (it.hasNext()) { + it.next(); + int row = it.key(); + QMapIterator> it2(it.value()); + + while (it2.hasNext()) { + it2.next(); + int col = it2.key(); + QSharedPointer data = it2.value(); + QString ref = CellReference(row, col).toString(); + + // dev57 + // writer.writeEmptyElement(QStringLiteral("hyperlink")); + writer.writeStartElement(QStringLiteral("hyperlink")); + + writer.writeAttribute(QStringLiteral("ref"), ref); // required field + + if (data->linkType == XlsxHyperlinkData::External) { + // Update relationships + relationships->addWorksheetRelationship( + QStringLiteral("/hyperlink"), data->target, QStringLiteral("External")); + + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(relationships->count())); + } + + if (!data->location.isEmpty()) { + writer.writeAttribute(QStringLiteral("location"), data->location); + } + + if (!data->display.isEmpty()) { + writer.writeAttribute(QStringLiteral("display"), data->display); + } + + if (!data->tooltip.isEmpty()) { + writer.writeAttribute(QStringLiteral("tooltip"), data->tooltip); + } + + // dev57 + writer.writeEndElement(); // hyperlink + } + } + + writer.writeEndElement(); // hyperlinks +} + +void WorksheetPrivate::saveXmlDrawings(QXmlStreamWriter &writer) const +{ + if (!drawing) + return; + + int idx = workbook->drawings().indexOf(drawing.get()); + relationships->addWorksheetRelationship( + QStringLiteral("/drawing"), QStringLiteral("../drawings/drawing%1.xml").arg(idx + 1)); + + writer.writeEmptyElement(QStringLiteral("drawing")); + writer.writeAttribute(QStringLiteral("r:id"), + QStringLiteral("rId%1").arg(relationships->count())); +} + +void WorksheetPrivate::splitColsInfo(int colFirst, int colLast) +{ + // Split current columnInfo, for example, if "A:H" has been set, + // we are trying to set "B:D", there should be "A", "B:D", "E:H". + // This will be more complex if we try to set "C:F" after "B:D". + { + QMapIterator> it(colsInfo); + while (it.hasNext()) { + it.next(); + QSharedPointer info = it.value(); + if (colFirst > info->firstColumn && colFirst <= info->lastColumn) { + // split the range, + QSharedPointer info2(new XlsxColumnInfo(*info)); + info->lastColumn = colFirst - 1; + info2->firstColumn = colFirst; + colsInfo.insert(colFirst, info2); + for (int c = info2->firstColumn; c <= info2->lastColumn; ++c) + colsInfoHelper[c] = info2; + + break; + } + } + } + { + QMapIterator> it(colsInfo); + while (it.hasNext()) { + it.next(); + QSharedPointer info = it.value(); + if (colLast >= info->firstColumn && colLast < info->lastColumn) { + QSharedPointer info2(new XlsxColumnInfo(*info)); + info->lastColumn = colLast; + info2->firstColumn = colLast + 1; + colsInfo.insert(colLast + 1, info2); + for (int c = info2->firstColumn; c <= info2->lastColumn; ++c) + colsInfoHelper[c] = info2; + + break; + } + } + } +} + +bool WorksheetPrivate::isColumnRangeValid(int colFirst, int colLast) +{ + bool ignore_row = true; + bool ignore_col = false; + + if (colFirst > colLast) + return false; + + if (checkDimensions(1, colLast, ignore_row, ignore_col)) + return false; + if (checkDimensions(1, colFirst, ignore_row, ignore_col)) + return false; + + return true; +} + +QList WorksheetPrivate ::getColumnIndexes(int colFirst, int colLast) +{ + splitColsInfo(colFirst, colLast); + + QList nodes; + nodes.append(colFirst); + for (int col = colFirst; col <= colLast; ++col) { + auto it = colsInfo.constFind(col); + if (it != colsInfo.constEnd()) { + if (nodes.last() != col) + nodes.append(col); + + int nextCol = (*it)->lastColumn + 1; + if (nextCol <= colLast) + nodes.append(nextCol); + } + } + + return nodes; +} + +/*! + Sets width in characters of a \a range of columns to \a width. + Returns true on success. + */ +bool Worksheet::setColumnWidth(const CellRange &range, double width) +{ + if (!range.isValid()) + return false; + + return setColumnWidth(range.firstColumn(), range.lastColumn(), width); +} + +/*! + Sets format property of a \a range of columns to \a format. Columns are 1-indexed. + Returns true on success. + */ +bool Worksheet::setColumnFormat(const CellRange &range, const Format &format) +{ + if (!range.isValid()) + return false; + + return setColumnFormat(range.firstColumn(), range.lastColumn(), format); +} + +/*! + Sets hidden property of a \a range of columns to \a hidden. Columns are 1-indexed. + Hidden columns are not visible. + Returns true on success. + */ +bool Worksheet::setColumnHidden(const CellRange &range, bool hidden) +{ + if (!range.isValid()) + return false; + + return setColumnHidden(range.firstColumn(), range.lastColumn(), hidden); +} + +/*! + Sets width in characters for columns [\a colFirst, \a colLast] to \a width. + Columns are 1-indexed. + Returns true on success. + */ +bool Worksheet::setColumnWidth(int colFirst, int colLast, double width) +{ + Q_D(Worksheet); + + const QList> columnInfoList = + d->getColumnInfoList(colFirst, colLast); + for (const QSharedPointer &columnInfo : columnInfoList) { + columnInfo->width = width; + } + + return (columnInfoList.count() > 0); +} + +/*! + Sets format property of a range of columns [\a colFirst, \a colLast] to \a format. + Columns are 1-indexed. + Returns true on success. + */ +bool Worksheet::setColumnFormat(int colFirst, int colLast, const Format &format) +{ + Q_D(Worksheet); + + const QList> columnInfoList = + d->getColumnInfoList(colFirst, colLast); + for (const QSharedPointer &columnInfo : columnInfoList) + columnInfo->format = format; + + if (columnInfoList.count() > 0) { + d->workbook->styles()->addXfFormat(format); + return true; + } + + return false; +} + +/*! + Sets hidden property of a range of columns [\a colFirst, \a colLast] to \a hidden. + Columns are 1-indexed. Returns true on success. + */ +bool Worksheet::setColumnHidden(int colFirst, int colLast, bool hidden) +{ + Q_D(Worksheet); + + const QList> columnInfoList = + d->getColumnInfoList(colFirst, colLast); + for (const QSharedPointer &columnInfo : columnInfoList) + columnInfo->hidden = hidden; + + return (columnInfoList.count() > 0); +} + +/*! + Returns width of the \a column in characters of the normal font. Columns are 1-indexed. + */ +double Worksheet::columnWidth(int column) +{ + Q_D(Worksheet); + + QList> columnInfoList = d->getColumnInfoList(column, column); + + // [dev54] + if (columnInfoList.size() == 0) { + // column information is not found + // qDebug() << "[debug]" << __FUNCTION__ << "column (info) is not found. " << column; + } + + if (columnInfoList.count() == 1) { + // column information is found + // qDebug() << "[debug]" << __FUNCTION__ << "column (info) is found. " << column << + // oneColWidth; + double oneColWidth = columnInfoList.at(0)->width; + bool isSetWidth = columnInfoList.at(0)->isSetWidth; + if (isSetWidth) { + return oneColWidth; + } + } + + // use default width + double defaultColWidth = d->sheetFormatProps.defaultColWidth; + return defaultColWidth; +} + +/*! + Returns formatting of the \a column. Columns are 1-indexed. + */ +Format Worksheet::columnFormat(int column) +{ + Q_D(Worksheet); + + QList> columnInfoList = d->getColumnInfoList(column, column); + if (columnInfoList.count() == 1) + return columnInfoList.at(0)->format; + + return Format(); +} + +/*! + Returns true if \a column is hidden. Columns are 1-indexed. + */ +bool Worksheet::isColumnHidden(int column) +{ + Q_D(Worksheet); + + QList> columnInfoList = d->getColumnInfoList(column, column); + if (columnInfoList.count() == 1) + return columnInfoList.at(0)->hidden; + + return false; +} + +/*! + Sets the \a height of the rows including and between \a rowFirst and \a rowLast. + Row height measured in point size. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Worksheet::setRowHeight(int rowFirst, int rowLast, double height) +{ + Q_D(Worksheet); + + const QList> rowInfoList = d->getRowInfoList(rowFirst, rowLast); + for (const QSharedPointer &rowInfo : rowInfoList) { + rowInfo->height = height; + rowInfo->customHeight = true; + } + + return rowInfoList.count() > 0; +} + +/*! + Sets the \a format of the rows including and between \a rowFirst and \a rowLast. + Rows are 1-indexed. + + Returns true if success. +*/ +bool Worksheet::setRowFormat(int rowFirst, int rowLast, const Format &format) +{ + Q_D(Worksheet); + + const QList> rowInfoList = d->getRowInfoList(rowFirst, rowLast); + for (const QSharedPointer &rowInfo : rowInfoList) + rowInfo->format = format; + + d->workbook->styles()->addXfFormat(format); + return rowInfoList.count() > 0; +} + +/*! + Sets the \a hidden property of the rows including and between \a rowFirst and \a rowLast. + Rows are 1-indexed. If hidden is true rows will not be visible. + + Returns true if success. +*/ +bool Worksheet::setRowHidden(int rowFirst, int rowLast, bool hidden) +{ + Q_D(Worksheet); + + const QList> rowInfoList = d->getRowInfoList(rowFirst, rowLast); + for (const QSharedPointer &rowInfo : rowInfoList) + rowInfo->hidden = hidden; + + return rowInfoList.count() > 0; +} + +/*! + Returns height of \a row in points. +*/ +double Worksheet::rowHeight(int row) +{ + Q_D(Worksheet); + const int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1; + + auto it = d->rowsInfo.constFind(row); + if (d->checkDimensions(row, min_col, false, true) || it == d->rowsInfo.constEnd()) { + return d->sheetFormatProps.defaultRowHeight; // return default on invalid row + } + + return (*it)->height; +} + +/*! + Returns format of \a row. +*/ +Format Worksheet::rowFormat(int row) +{ + Q_D(Worksheet); + const int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1; + auto it = d->rowsInfo.constFind(row); + if (d->checkDimensions(row, min_col, false, true) || it == d->rowsInfo.constEnd()) + return Format(); // return default on invalid row + + return (*it)->format; +} + +/*! + Returns true if \a row is hidden. +*/ +bool Worksheet::isRowHidden(int row) +{ + Q_D(Worksheet); + const int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1; + auto it = d->rowsInfo.constFind(row); + if (d->checkDimensions(row, min_col, false, true) || it == d->rowsInfo.constEnd()) + return false; // return default on invalid row + + return (*it)->hidden; +} + +/*! + Groups rows from \a rowFirst to \a rowLast with the given \a collapsed. + + Returns false if error occurs. + */ +bool Worksheet::groupRows(int rowFirst, int rowLast, bool collapsed) +{ + Q_D(Worksheet); + + for (int row = rowFirst; row <= rowLast; ++row) { + auto it = d->rowsInfo.find(row); + if (it != d->rowsInfo.end()) { + (*it)->outlineLevel += 1; + } else { + QSharedPointer info(new XlsxRowInfo); + info->outlineLevel += 1; + it = d->rowsInfo.insert(row, info); + } + if (collapsed) + (*it)->hidden = true; + } + if (collapsed) { + auto it = d->rowsInfo.find(rowLast + 1); + if (it == d->rowsInfo.end()) + it = d->rowsInfo.insert(rowLast + 1, QSharedPointer(new XlsxRowInfo)); + (*it)->collapsed = true; + } + return true; +} + +/*! + \overload + + Groups columns with the given \a range and \a collapsed. + */ +bool Worksheet::groupColumns(const CellRange &range, bool collapsed) +{ + if (!range.isValid()) + return false; + + return groupColumns(range.firstColumn(), range.lastColumn(), collapsed); +} + +/*! + Groups columns from \a colFirst to \a colLast with the given \a collapsed. + Returns false if error occurs. +*/ +bool Worksheet::groupColumns(int colFirst, int colLast, bool collapsed) +{ + Q_D(Worksheet); + + d->splitColsInfo(colFirst, colLast); + + QList nodes; + nodes.append(colFirst); + for (int col = colFirst; col <= colLast; ++col) { + auto it = d->colsInfo.constFind(col); + if (it != d->colsInfo.constEnd()) { + if (nodes.last() != col) + nodes.append(col); + int nextCol = (*it)->lastColumn + 1; + if (nextCol <= colLast) + nodes.append(nextCol); + } + } + + for (int idx = 0; idx < nodes.size(); ++idx) { + int colStart = nodes[idx]; + auto it = d->colsInfo.constFind(colStart); + if (it != d->colsInfo.constEnd()) { + (*it)->outlineLevel += 1; + if (collapsed) + (*it)->hidden = true; + } else { + int colEnd = (idx == nodes.size() - 1) ? colLast : nodes[idx + 1] - 1; + QSharedPointer info(new XlsxColumnInfo(colStart, colEnd, false)); + info->outlineLevel += 1; + d->colsInfo.insert(colFirst, info); + if (collapsed) + info->hidden = true; + for (int c = colStart; c <= colEnd; ++c) + d->colsInfoHelper[c] = info; + } + } + + if (collapsed) { + int col = colLast + 1; + d->splitColsInfo(col, col); + auto it = d->colsInfo.constFind(col); + if (it != d->colsInfo.constEnd()) + (*it)->collapsed = true; + else { + QSharedPointer info(new XlsxColumnInfo(col, col, false)); + info->collapsed = true; + d->colsInfo.insert(col, info); + d->colsInfoHelper[col] = info; + } + } + + return false; +} + +/*! + Return the range that contains cell data. + */ +CellRange Worksheet::dimension() const +{ + Q_D(const Worksheet); + return d->dimension; +} + +/* + Convert the height of a cell from user's units to pixels. If the + height hasn't been set by the user we use the default value. If + the row is hidden it has a value of zero. +*/ +int WorksheetPrivate::rowPixelsSize(int row) const +{ + double height; + auto it = row_sizes.constFind(row); + if (it != row_sizes.constEnd()) + height = it.value(); + else + height = sheetFormatProps.defaultRowHeight; + return static_cast(4.0 / 3.0 * height); +} + +/* + Convert the width of a cell from user's units to pixels. Excel rounds + the column width to the nearest pixel. If the width hasn't been set + by the user we use the default value. If the column is hidden it + has a value of zero. +*/ +int WorksheetPrivate::colPixelsSize(int col) const +{ + double max_digit_width = 7.0; // For Calabri 11 + double padding = 5.0; + int pixels = 0; + + auto it = col_sizes.constFind(col); + if (it != col_sizes.constEnd()) { + double width = it.value(); + if (width < 1) + pixels = static_cast(width * (max_digit_width + padding) + 0.5); + else + pixels = static_cast(width * max_digit_width + 0.5) + padding; + } else { + pixels = 64; + } + return pixels; +} + +void WorksheetPrivate::loadXmlSheetData(QXmlStreamReader &reader) +{ + Q_Q(Worksheet); + + Q_ASSERT(reader.name() == QLatin1String("sheetData")); + + int row_num = 0; + int col_num = 0; + + while (!reader.atEnd() && !(reader.name() == QLatin1String("sheetData") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("row")) { + QXmlStreamAttributes attributes = reader.attributes(); + + if (attributes.hasAttribute(QLatin1String("customFormat")) || + attributes.hasAttribute(QLatin1String("customHeight")) || + attributes.hasAttribute(QLatin1String("hidden")) || + attributes.hasAttribute(QLatin1String("outlineLevel")) || + attributes.hasAttribute(QLatin1String("collapsed"))) { + + QSharedPointer info(new XlsxRowInfo); + if (attributes.hasAttribute(QLatin1String("customFormat")) && + attributes.hasAttribute(QLatin1String("s"))) { + int idx = attributes.value(QLatin1String("s")).toInt(); + info->format = workbook->styles()->xfFormat(idx); + } + + if (attributes.hasAttribute(QLatin1String("customHeight"))) { + info->customHeight = + attributes.value(QLatin1String("customHeight")) == QLatin1String("1"); + // Row height is only specified when customHeight is set + if (attributes.hasAttribute(QLatin1String("ht"))) { + info->height = attributes.value(QLatin1String("ht")).toDouble(); + } + } + + // both "hidden" and "collapsed" default are false + info->hidden = attributes.value(QLatin1String("hidden")) == QLatin1String("1"); + info->collapsed = + attributes.value(QLatin1String("collapsed")) == QLatin1String("1"); + + if (attributes.hasAttribute(QLatin1String("outlineLevel"))) + info->outlineLevel = + attributes.value(QLatin1String("outlineLevel")).toInt(); + + //"r" is optional too. + if (attributes.hasAttribute(QLatin1String("r"))) { + int row = attributes.value(QLatin1String("r")).toInt(); + rowsInfo[row] = info; + } + } + + if (attributes.hasAttribute(QLatin1String("r"))) + row_num = attributes.value(QLatin1String("r")).toInt(); + else + ++row_num; + col_num = 0; + + } else if (reader.name() == QLatin1String("c")) // Cell + { + + // Cell + QXmlStreamAttributes attributes = reader.attributes(); + QString r = attributes.value(QLatin1String("r")).toString(); + CellReference pos(r); + if (r.isEmpty()) + { + pos.setRow(row_num); + pos.setColumn(++col_num); + } + + // get format + Format format; + qint32 styleIndex = -1; + if (attributes.hasAttribute( + QLatin1String("s"))) // Style (defined in the styles.xml file) + { + //"s" == style index + int idx = attributes.value(QLatin1String("s")).toInt(); + format = workbook->styles()->xfFormat(idx); + styleIndex = idx; + } + + // Cell::CellType cellType = Cell::NumberType; + Cell::CellType cellType = Cell::CustomType; + + if (attributes.hasAttribute(QLatin1String("t"))) // Type + { + const auto typeString = attributes.value(QLatin1String("t")); + if (typeString == QLatin1String("s")) // Shared string + { + cellType = Cell::SharedStringType; + } else if (typeString == QLatin1String("inlineStr")) // Inline String + { + cellType = Cell::InlineStringType; + } else if (typeString == QLatin1String("str")) // String + { + cellType = Cell::StringType; + } else if (typeString == QLatin1String("b")) // Boolean + { + cellType = Cell::BooleanType; + } else if (typeString == QLatin1String("e")) // Error + { + cellType = Cell::ErrorType; + } else if (typeString == QLatin1String("d")) // Date + { + cellType = Cell::DateType; + } else if (typeString == QLatin1String("n")) // Number + { + cellType = Cell::NumberType; + } else { + // custom type + cellType = Cell::CustomType; + } + } + + if (Cell::isDateType(cellType, format)) { + cellType = Cell::DateType; + } + + // create a heap of new cell + auto cell = std::make_shared(QVariant{}, cellType, format, q, styleIndex); + + while (!reader.atEnd() && !(reader.name() == QLatin1String("c") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("f")) // formula + { + CellFormula &formula = cell->d_func()->formula; + formula.loadFromXml(reader); + if (formula.formulaType() == CellFormula::SharedType && + !formula.formulaText().isEmpty()) { + int si = formula.sharedIndex(); + sharedFormulaMap[si] = formula; + } + } else if (reader.name() == QLatin1String("v")) // Value + { + QString value = reader.readElementText(); + if (cellType == Cell::SharedStringType) { + int sst_idx = value.toInt(); + sharedStrings()->incRefByStringIndex(sst_idx); + RichString rs = sharedStrings()->getSharedString(sst_idx); + QString strPlainString = rs.toPlainString(); + cell->d_func()->value = strPlainString; + if (rs.isRichString()) + cell->d_func()->richString = rs; + } else if (cellType == Cell::NumberType) { + cell->d_func()->value = value.toDouble(); + } else if (cellType == Cell::BooleanType) { + cell->d_func()->value = value.toInt() ? true : false; + } else if (cellType == Cell::DateType) { + // [dev54] DateType + + double dValue = value.toDouble(); // days from 1900(or 1904) + bool bIsDate1904 = q->workbook()->isDate1904(); + + QVariant vDatetimeValue = datetimeFromNumber(dValue, bIsDate1904); + Q_UNUSED(vDatetimeValue); + // cell->d_func()->value = vDatetimeValue; + cell->d_func()->value = dValue; // dev67 + } else { + // ELSE type + cell->d_func()->value = value; + } + + } else if (reader.name() == QLatin1String("is")) { + while (!reader.atEnd() && + !(reader.name() == QLatin1String("is") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + if (reader.readNextStartElement()) { + //: Todo, add rich text read support + if (reader.name() == QLatin1String("t")) { + cell->d_func()->value = reader.readElementText(); + } + } + } + } else if (reader.name() == QLatin1String("extLst")) { + // skip extLst element + while (!reader.atEnd() && + !(reader.name() == QLatin1String("extLst") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + } + } + } + } + + cellTable[pos.row()][pos.column()] = cell; + } + } + } + + if (dimension.lastRow() < row_num) + dimension.setLastRow(row_num); + + if (dimension.lastColumn() < col_num) + dimension.setLastColumn(col_num); +} + +void WorksheetPrivate::loadXmlColumnsInfo(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("cols")); + + while (!reader.atEnd() && !(reader.name() == QLatin1String("cols") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("col")) { + QSharedPointer info(new XlsxColumnInfo(0, 1, false)); + + QXmlStreamAttributes colAttrs = reader.attributes(); + int min = colAttrs.value(QLatin1String("min")).toInt(); + int max = colAttrs.value(QLatin1String("max")).toInt(); + info->firstColumn = min; + info->lastColumn = max; + + // Flag indicating that the column width for the affected column(s) is different + // from the + // default or has been manually set + if (colAttrs.hasAttribute(QLatin1String("customWidth"))) { + info->customWidth = + colAttrs.value(QLatin1String("customWidth")) == QLatin1String("1"); + } + + // Note, node may have "width" without "customWidth" + // [dev54] + if (colAttrs.hasAttribute(QLatin1String("width"))) { + double width = colAttrs.value(QLatin1String("width")).toDouble(); + info->width = width; + info->isSetWidth = true; // [dev54] + } + + info->hidden = colAttrs.value(QLatin1String("hidden")) == QLatin1String("1"); + info->collapsed = colAttrs.value(QLatin1String("collapsed")) == QLatin1String("1"); + + if (colAttrs.hasAttribute(QLatin1String("style"))) { + int idx = colAttrs.value(QLatin1String("style")).toInt(); + info->format = workbook->styles()->xfFormat(idx); + } + + if (colAttrs.hasAttribute(QLatin1String("outlineLevel"))) { + info->outlineLevel = colAttrs.value(QLatin1String("outlineLevel")).toInt(); + } + + // qDebug() << "[debug] " << __FUNCTION__ << min << max << info->width << hasWidth; + + colsInfo.insert(min, info); + for (int col = min; col <= max; ++col) { + colsInfoHelper[col] = info; + } + } + } + } +} + +void WorksheetPrivate::loadXmlMergeCells(QXmlStreamReader &reader) +{ + // issue #173 https://github.com/QtExcel/QXlsx/issues/173 + + Q_ASSERT(reader.name() == QLatin1String("mergeCells")); + + QXmlStreamAttributes attributes = reader.attributes(); + + bool isCount = attributes.hasAttribute(QLatin1String("count")); + int count = 0; + if (!isCount) { + qWarning("no count"); + } else { + count = attributes.value(QLatin1String("count")).toInt(); + } + + while (!reader.atEnd() && !(reader.name() == QLatin1String("mergeCells") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("mergeCell")) { + QXmlStreamAttributes attrs = reader.attributes(); + QString rangeStr = attrs.value(QLatin1String("ref")).toString(); + merges.append(CellRange(rangeStr)); + } + } + } + + if (isCount) { + int mergesSize = merges.size(); + if (mergesSize != count) { + qWarning("read merge cells error"); + } + } +} + +void WorksheetPrivate::loadXmlDataValidations(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("dataValidations")); + QXmlStreamAttributes attributes = reader.attributes(); + int count = attributes.value(QLatin1String("count")).toInt(); + + while (!reader.atEnd() && !(reader.name() == QLatin1String("dataValidations") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement && + reader.name() == QLatin1String("dataValidation")) { + dataValidationsList.append(DataValidation::loadFromXml(reader)); + } + } + + if (dataValidationsList.size() != count) + qDebug("read data validation error"); +} + +void WorksheetPrivate::loadXmlSheetViews(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("sheetViews")); + + while (!reader.atEnd() && !(reader.name() == QLatin1String("sheetViews") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement && + reader.name() == QLatin1String("sheetView")) { + QXmlStreamAttributes attrs = reader.attributes(); + // default false + windowProtection = attrs.value(QLatin1String("windowProtection")) == QLatin1String("1"); + showFormulas = attrs.value(QLatin1String("showFormulas")) == QLatin1String("1"); + rightToLeft = attrs.value(QLatin1String("rightToLeft")) == QLatin1String("1"); + tabSelected = attrs.value(QLatin1String("tabSelected")) == QLatin1String("1"); + // default true + showGridLines = attrs.value(QLatin1String("showGridLines")) != QLatin1String("0"); + showRowColHeaders = + attrs.value(QLatin1String("showRowColHeaders")) != QLatin1String("0"); + showZeros = attrs.value(QLatin1String("showZeros")) != QLatin1String("0"); + showRuler = attrs.value(QLatin1String("showRuler")) != QLatin1String("0"); + showOutlineSymbols = + attrs.value(QLatin1String("showOutlineSymbols")) != QLatin1String("0"); + showWhiteSpace = attrs.value(QLatin1String("showWhiteSpace")) != QLatin1String("0"); + } + } +} + +void WorksheetPrivate::loadXmlSheetFormatProps(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("sheetFormatPr")); + + const QXmlStreamAttributes attributes = reader.attributes(); + XlsxSheetFormatProps formatProps; + bool isSetWidth = false; + + // Retain default values + for (const QXmlStreamAttribute &attrib : attributes) { + if (attrib.name() == QLatin1String("baseColWidth")) { + formatProps.baseColWidth = attrib.value().toInt(); + } else if (attrib.name() == QLatin1String("customHeight")) { + formatProps.customHeight = attrib.value() == QLatin1String("1"); + } else if (attrib.name() == QLatin1String("defaultColWidth")) { + double dDefaultColWidth = attrib.value().toDouble(); + formatProps.defaultColWidth = dDefaultColWidth; + isSetWidth = true; + } else if (attrib.name() == QLatin1String("defaultRowHeight")) { + formatProps.defaultRowHeight = attrib.value().toDouble(); + } else if (attrib.name() == QLatin1String("outlineLevelCol")) { + formatProps.outlineLevelCol = attrib.value().toInt(); + } else if (attrib.name() == QLatin1String("outlineLevelRow")) { + formatProps.outlineLevelRow = attrib.value().toInt(); + } else if (attrib.name() == QLatin1String("thickBottom")) { + formatProps.thickBottom = attrib.value() == QLatin1String("1"); + } else if (attrib.name() == QLatin1String("thickTop")) { + formatProps.thickTop = attrib.value() == QLatin1String("1"); + } else if (attrib.name() == QLatin1String("zeroHeight")) { + formatProps.zeroHeight = attrib.value() == QLatin1String("1"); + } + } + + // if (formatProps.defaultColWidth == 0.0) + if (!isSetWidth) { + // not set + double dCalcWidth = WorksheetPrivate::calculateColWidth(formatProps.baseColWidth); + formatProps.defaultColWidth = dCalcWidth; + } + + // [dev54] + // Where is code of setting 'formatProps'? + this->sheetFormatProps = formatProps; +} +double WorksheetPrivate::calculateColWidth(int characters) +{ + // //!Todo + // Take normal style' font maximum width and add padding and margin pixels + // return characters + 0.5; + return characters; +} + +void WorksheetPrivate::loadXmlHyperlinks(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("hyperlinks")); + + while (!reader.atEnd() && !(reader.name() == QLatin1String("hyperlinks") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement && + reader.name() == QLatin1String("hyperlink")) { + QXmlStreamAttributes attrs = reader.attributes(); + CellReference pos(attrs.value(QLatin1String("ref")).toString()); + if (pos.isValid()) { // Valid + QSharedPointer link(new XlsxHyperlinkData); + link->display = attrs.value(QLatin1String("display")).toString(); + link->tooltip = attrs.value(QLatin1String("tooltip")).toString(); + link->location = attrs.value(QLatin1String("location")).toString(); + + if (attrs.hasAttribute(QLatin1String("r:id"))) { + link->linkType = XlsxHyperlinkData::External; + XlsxRelationship ship = relationships->getRelationshipById( + attrs.value(QLatin1String("r:id")).toString()); + link->target = ship.target; + } else { + link->linkType = XlsxHyperlinkData::Internal; + } + + urlTable[pos.row()][pos.column()] = link; + } + } + } +} + +QList> WorksheetPrivate::getColumnInfoList(int colFirst, int colLast) +{ + QList> columnsInfoList; + if (isColumnRangeValid(colFirst, colLast)) { + QList nodes = getColumnIndexes(colFirst, colLast); + + for (int idx = 0; idx < nodes.size(); ++idx) { + int colStart = nodes[idx]; + auto it = colsInfo.constFind(colStart); + if (it != colsInfo.constEnd()) { + columnsInfoList.append(*it); + } else { + int colEnd = (idx == nodes.size() - 1) ? colLast : nodes[idx + 1] - 1; + QSharedPointer info(new XlsxColumnInfo(colStart, colEnd, false)); + colsInfo.insert(colFirst, info); + columnsInfoList.append(info); + for (int c = colStart; c <= colEnd; ++c) { + colsInfoHelper[c] = info; + } + } + } + } + + return columnsInfoList; +} + +QList> WorksheetPrivate::getRowInfoList(int rowFirst, int rowLast) +{ + QList> rowInfoList; + + int min_col = dimension.firstColumn() < 1 ? 1 : dimension.firstColumn(); + + for (int row = rowFirst; row <= rowLast; ++row) { + if (checkDimensions(row, min_col, false, true)) + continue; + + QSharedPointer rowInfo; + if ((rowsInfo[row]).isNull()) { + rowsInfo[row] = QSharedPointer(new XlsxRowInfo()); + } + rowInfoList.append(rowsInfo[row]); + } + + return rowInfoList; +} + +bool Worksheet::loadFromXmlFile(QIODevice *device) +{ + Q_D(Worksheet); + + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("dimension")) { + QXmlStreamAttributes attributes = reader.attributes(); + QString range = attributes.value(QLatin1String("ref")).toString(); + d->dimension = CellRange(range); + } else if (reader.name() == QLatin1String("sheetViews")) { + d->loadXmlSheetViews(reader); + } else if (reader.name() == QLatin1String("sheetFormatPr")) { + d->loadXmlSheetFormatProps(reader); + } else if (reader.name() == QLatin1String("cols")) { + d->loadXmlColumnsInfo(reader); + } else if (reader.name() == QLatin1String("sheetData")) { + d->loadXmlSheetData(reader); + } else if (reader.name() == QLatin1String("mergeCells")) { + d->loadXmlMergeCells(reader); + } else if (reader.name() == QLatin1String("dataValidations")) { + d->loadXmlDataValidations(reader); + } else if (reader.name() == QLatin1String("conditionalFormatting")) { + ConditionalFormatting cf; + cf.loadFromXml(reader, workbook()->styles()); + d->conditionalFormattingList.append(cf); + } else if (reader.name() == QLatin1String("hyperlinks")) { + d->loadXmlHyperlinks(reader); + } else if (reader.name() == QLatin1String("pageSetup")) { + QXmlStreamAttributes attributes = reader.attributes(); + + d->PpaperSize = attributes.value(QLatin1String("paperSize")).toString().trimmed(); + d->Pscale = attributes.value(QLatin1String("scale")).toString().trimmed(); + d->PfirstPageNumber = + attributes.value(QLatin1String("firstPageNumber")).toString().trimmed(); + d->Porientation = + attributes.value(QLatin1String("orientation")).toString().trimmed(); + d->PuseFirstPageNumber = + attributes.value(QLatin1String("useFirstPageNumber")).toString().trimmed(); + d->PhorizontalDpi = + attributes.value(QLatin1String("horizontalDpi")).toString().trimmed(); + d->PverticalDpi = + attributes.value(QLatin1String("verticalDpi")).toString().trimmed(); + d->Prid = attributes.value(QLatin1String("r:id")).toString().trimmed(); + d->Pcopies = attributes.value(QLatin1String("copies")).toString().trimmed(); + } else if (reader.name() == QLatin1String("pageMargins")) { + QXmlStreamAttributes attributes = reader.attributes(); + + d->PMfooter = attributes.value(QLatin1String("footer")).toString().trimmed(); + d->PMheader = attributes.value(QLatin1String("header")).toString().trimmed(); + d->PMbotton = attributes.value(QLatin1String("bottom")).toString().trimmed(); + d->PMtop = attributes.value(QLatin1String("top")).toString().trimmed(); + d->PMright = attributes.value(QLatin1String("right")).toString().trimmed(); + d->PMleft = attributes.value(QLatin1String("left")).toString().trimmed(); + } else if (reader.name() == QLatin1String("headerFooter")) { + // dev40 + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("oddHeader")) + d->ModdHeader = reader.readElementText(); + + if (reader.name() == QLatin1String("oddFooter")) + d->MoodFooter = reader.readElementText(); + } + } else if (reader.name() == QLatin1String("drawing")) { + QString rId = reader.attributes().value(QStringLiteral("r:id")).toString(); + QString name = d->relationships->getRelationshipById(rId).target; + + const auto parts = splitPath(filePath()); + QString path = QDir::cleanPath(parts.first() + QLatin1String("/") + name); + + d->drawing = std::make_shared(this, F_LoadFromExists); + d->drawing->setFilePath(path); + } else if (reader.name() == QLatin1String("extLst")) { + // Todo: add extLst support + while (!reader.atEnd() && !(reader.name() == QLatin1String("extLst") && + reader.tokenType() == QXmlStreamReader::EndElement)) { + reader.readNextStartElement(); + } + } + } + } + + d->validateDimension(); + return true; +} + +/* + * Documents imported from Google Docs does not contain dimension data. + */ +void WorksheetPrivate::validateDimension() +{ + if (dimension.isValid() || cellTable.isEmpty()) + return; + + const auto firstRow = cellTable.constBegin().key(); + + const auto lastRow = (--cellTable.constEnd()).key(); + + int firstColumn = -1; + int lastColumn = -1; + + for (auto &&it = cellTable.constBegin(); it != cellTable.constEnd(); ++it) { + Q_ASSERT(!it.value().isEmpty()); + + if (firstColumn == -1 || it.value().constBegin().key() < firstColumn) + firstColumn = it.value().constBegin().key(); + + if (lastColumn == -1 || (--it.value().constEnd()).key() > lastColumn) { + lastColumn = (--it.value().constEnd()).key(); + } + } + + CellRange cr(firstRow, firstColumn, lastRow, lastColumn); + + if (cr.isValid()) + dimension = cr; +} + +/*! + * \internal + * Unit test can use this member to get sharedString object. + */ +SharedStrings *WorksheetPrivate::sharedStrings() const +{ + return workbook->sharedStrings(); +} + +QVector Worksheet::getFullCells(int *maxRow, int *maxCol) +{ + Q_D(const Worksheet); + + // return values + (*maxRow) = -1; + (*maxCol) = -1; + QVector ret; + + // QString privateName = d->name; // name of sheet (not object type) + // qDebug() << privateName ; + + if (d->type == AbstractSheet::ST_WorkSheet) { + // use current sheet + } else if (d->type == AbstractSheet::ST_ChartSheet) { + return ret; + } else { + qWarning("unsupported sheet type."); + Q_ASSERT(false); + return ret; + } + + QMapIterator>> _it(d->cellTable); + + while (_it.hasNext()) { + _it.next(); + + int keyI = _it.key(); // key (cell row) + QMapIterator> _iit(_it.value()); // value + + while (_iit.hasNext()) { + _iit.next(); + + int keyII = _iit.key(); // key (cell column) + std::shared_ptr ptrCell = _iit.value(); // value + + CellLocation cl; + + cl.row = keyI; + if (keyI > (*maxRow)) { + (*maxRow) = keyI; + } + + cl.col = keyII; + if (keyII > (*maxCol)) { + (*maxCol) = keyII; + } + + cl.cell = ptrCell; + + ret.push_back(cl); + } + } + + return ret; +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxzipreader.cpp b/QXlsx/source/xlsxzipreader.cpp new file mode 100644 index 0000000..dc2cb5e --- /dev/null +++ b/QXlsx/source/xlsxzipreader.cpp @@ -0,0 +1,49 @@ +// xlsxzipreader.cpp + +#include "xlsxzipreader_p.h" + +#include + +QT_BEGIN_NAMESPACE_XLSX + +ZipReader::ZipReader(const QString &filePath) + : m_reader(new QZipReader(filePath)) +{ + init(); +} + +ZipReader::ZipReader(QIODevice *device) + : m_reader(new QZipReader(device)) +{ + init(); +} + +ZipReader::~ZipReader() +{ +} + +void ZipReader::init() +{ + const auto &allFiles = m_reader->fileInfoList(); + for (const auto &fi : allFiles) { + if (fi.isFile || (!fi.isDir && !fi.isFile && !fi.isSymLink)) + m_filePaths.append(fi.filePath); + } +} + +bool ZipReader::exists() const +{ + return m_reader->exists(); +} + +QStringList ZipReader::filePaths() const +{ + return m_filePaths; +} + +QByteArray ZipReader::fileData(const QString &fileName) const +{ + return m_reader->fileData(fileName); +} + +QT_END_NAMESPACE_XLSX diff --git a/QXlsx/source/xlsxzipwriter.cpp b/QXlsx/source/xlsxzipwriter.cpp new file mode 100644 index 0000000..0ee17e3 --- /dev/null +++ b/QXlsx/source/xlsxzipwriter.cpp @@ -0,0 +1,49 @@ +// xlsxzipwriter.cpp + +#include "xlsxzipwriter_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ZipWriter::ZipWriter(const QString &filePath) +{ + m_writer = new QZipWriter(filePath, QIODevice::WriteOnly); + m_writer->setCompressionPolicy(QZipWriter::AutoCompress); +} + +ZipWriter::ZipWriter(QIODevice *device) +{ + m_writer = new QZipWriter(device); + m_writer->setCompressionPolicy(QZipWriter::AutoCompress); +} + +ZipWriter::~ZipWriter() +{ + delete m_writer; +} + +bool ZipWriter::error() const +{ + return m_writer->status() != QZipWriter::NoError; +} + +void ZipWriter::addFile(const QString &filePath, QIODevice *device) +{ + m_writer->addFile(filePath, device); +} + +void ZipWriter::addFile(const QString &filePath, const QByteArray &data) +{ + m_writer->addFile(filePath, data); +} + +void ZipWriter::close() +{ + m_writer->close(); +} + +QT_END_NAMESPACE_XLSX diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3f1c4e --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +【淘宝】7天无理由退货 https://e.tb.cn/h.7nwe4hmnZblda1n?tk=ROy6U5lf0kD MF287 「智能数显电阻表 欧姆表 带继电器报警 RS485通信 4-20mA变送输出」 +点击链接直接打开 或者 淘宝搜索直接打开 + +使用之前先设置线材阻值 diff --git a/SerialConfig.cpp b/SerialConfig.cpp new file mode 100644 index 0000000..66329e6 --- /dev/null +++ b/SerialConfig.cpp @@ -0,0 +1,75 @@ +#include "SerialConfig.h" +#include +#include +#include + +SerialConfig::SerialConfig(QWidget *parent) : QDialog(parent) { + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowTitle("串口配置"); + + QFormLayout *formLayout = new QFormLayout(this); + + serialPortComboBox = new QComboBox(); + formLayout->addRow(new QLabel("串口:"), serialPortComboBox); + scanSerialPorts(); + + baudRateComboBox = new QComboBox(); + baudRateComboBox->addItems({"9600", "19200", "38400", "57600", "115200"}); + formLayout->addRow(new QLabel("波特率:"), baudRateComboBox); + + dataBitsComboBox = new QComboBox(); + dataBitsComboBox->addItems({"8", "7", "6", "5"}); + formLayout->addRow(new QLabel("数据位:"), dataBitsComboBox); + + stopBitsComboBox = new QComboBox(); + stopBitsComboBox->addItems({"1", "1.5", "2"}); + formLayout->addRow(new QLabel("停止位:"), stopBitsComboBox); + + parityComboBox = new QComboBox(); + parityComboBox->addItems({"0", "偶校验", "奇校验"}); + formLayout->addRow(new QLabel("校验:"), parityComboBox); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(); + QPushButton *okButton = new QPushButton("确认"); + QPushButton *cancelButton = new QPushButton("取消"); + buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); + buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole); + + connect(okButton, &QPushButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + + formLayout->addRow(buttonBox); +} + +QString SerialConfig::getSerialPort() const { + return serialPortComboBox->currentText(); +} + +QString SerialConfig::getBaudRate() const { + return baudRateComboBox->currentText(); +} + +QString SerialConfig::getDataBits() const { + return dataBitsComboBox->currentText(); +} + +QString SerialConfig::getStopBits() const { + return stopBitsComboBox->currentText(); +} + +QString SerialConfig::getParity() const { + return parityComboBox->currentText(); +} + +void SerialConfig::scanSerialPorts() { + serialPortComboBox->clear(); + const auto ports = QSerialPortInfo::availablePorts(); + for (const QSerialPortInfo &port : ports) { + serialPortComboBox->addItem(port.portName()); + } + + if (serialPortComboBox->count() == 0) { + serialPortComboBox->addItem("无可用串口"); + } +} diff --git a/SerialConfig.h b/SerialConfig.h new file mode 100644 index 0000000..0281885 --- /dev/null +++ b/SerialConfig.h @@ -0,0 +1,38 @@ +#ifndef SERIALCONFIG_H +#define SERIALCONFIG_H + + +#include +#include +#include +#include +#include +#include + +class SerialConfig : public QDialog { + Q_OBJECT + +public: + SerialConfig(QWidget *parent = nullptr); + + QString getSerialPort() const; + QString getBaudRate() const; + QString getDataBits() const; + QString getStopBits() const; + QString getParity() const; + void scanSerialPorts(); + +private slots: + + +private: + QComboBox *serialPortComboBox; + QComboBox *baudRateComboBox; + QComboBox *dataBitsComboBox; + QComboBox *stopBitsComboBox; + QComboBox *parityComboBox; +}; + + +#endif // SERIALCONFIG_H + diff --git a/databaseutils.cpp b/databaseutils.cpp new file mode 100644 index 0000000..1ef4a7a --- /dev/null +++ b/databaseutils.cpp @@ -0,0 +1,812 @@ +#include "databaseutils.h" + +/* + * + "all_count INTEGER DEFAULT 0," 总检验次数 + "manual_cnt INTEGER DEFAULT 0," 手动检验次数 + "auto_cnt INTEGER DEFAULT 0," 自动检验次数 + "up_cnt INTEGER DEFAULT 0," 超过大于范围的电阻值 + "down_cnt INTEGER DEFAULT 0," 超过小于范围的电阻值 + "max_res INTEGER DEFAULT 0," 检测范围范最大电阻值 + "min_res INTEGER DEFAULT 0," 检测范围范最小电阻值 + "auto_time INTEGER DEFAULT 1," 自动检测重复时间 + "auto_sd INTEGER DEFAULT 5," 自动检测误差允许 + "chexing TEXT DEFAULT ''," 车型 + "sm_num INTEGER DEFAULT 0)"; 护面扫码编号 + * + * */ + +namespace DatabaseUtils +{ + QSqlDatabase db; + + bool init_Database() + { + db = QSqlDatabase::database(); + if (!db.isValid()) + { + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("RB_RES.db"); + } + + if (!db.open()) + { + qDebug() << "Error: Unable to open the database"; + return false; + } + + QSqlQuery query; + + // value 阻值 state 0正常 1过大 2过小 flag 0手动 1自动 + QString createResDataTable = "CREATE TABLE IF NOT EXISTS res_data_t (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "chexing TEXT DEFAULT ''," + "sm_num TEXT DEFAULT ''," + "hot_num TEXT DEFAULT ''," + "value REAL," + "detect TEXT DEFAULT ''," + "state TEXT DEFAULT ''," + "flag TEXT DEFAULT ''," + "name TEXT DEFAULT ''," + "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"; + + if (!query.exec(createResDataTable)) + { + qDebug() << "Error: Unable to create res_data_t table" << query.lastError(); + } + + // 检查并添加 hot_num 列(兼容旧数据库) + query.exec("ALTER TABLE res_data_t ADD COLUMN hot_num TEXT DEFAULT ''"); + + // 日检验次数 手动检测次数 合格 过大 过小 最大值 最小值 合格率 日期 + QString createDailyInspectionTable = "CREATE TABLE IF NOT EXISTS daily_data_t (" + "ID INTEGER PRIMARY KEY AUTOINCREMENT," + "daily_inspection_count INTEGER," + "manual_inspection_count INTEGER," + "qualified INTEGER," + "over_limit INTEGER," + "under_limit INTEGER," + "max_value REAL," + "min_value REAL," + "qualification_rate REAL," + "date DATE)"; + + if (!query.exec(createDailyInspectionTable)) + { + qDebug() << "Error: Unable to create daily_data_t table" << query.lastError(); + } + + QString createInfoTable = "CREATE TABLE IF NOT EXISTS info_t (" + "all_count INTEGER DEFAULT 0," // 总次数 + "manual_cnt INTEGER DEFAULT 0," // 手动检测次数 + "auto_cnt INTEGER DEFAULT 0," // 自动检测次数 + "qualified_cnt INTEGER DEFAULT 0," // 合格次数 + "up_cnt INTEGER DEFAULT 0," // 超过最大范围次数 + "down_cnt INTEGER DEFAULT 0," // 超过最小范围次数 + "res_value REAL DEFAULT 0," // 电阻标准值 + "res_sd REAL DEFAULT 0," // 电阻误差范围 + "max_res REAL DEFAULT 0," // 最大阻值 + "min_res REAL DEFAULT 0," // 最小阻值 + "auto_time INTEGER DEFAULT 3," // 自动检测时间 + "xc_sd REAL DEFAULT 0," // 线材阻值 + "name TEXT DEFAULT ''," // 姓名 + "pwd TEXT DEFAULT ''," // 密码 + "chexing TEXT DEFAULT ''," // 车型 + "sm_num TEXT DEFAULT ''," // 护面扫码编号 + "hot_num TEXT DEFAULT '')"; // 加热垫扫码编号 + + QString checkTableEmpty = "SELECT COUNT(*) FROM info_t"; + QString insertInitialData = "INSERT INTO info_t (all_count, manual_cnt, auto_cnt, qualified_cnt, up_cnt, down_cnt, res_value, res_sd,max_res, min_res, auto_time, xc_sd, name, pwd, chexing, sm_num, hot_num) " + "VALUES (0, 0, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 3, 0.0, '', 'nuobo123', '', '', '');"; + + if (!query.exec(createInfoTable)) + { + qDebug() << "Error: Unable to create info_t table" << query.lastError(); + } + + // 检查并添加 hot_num 列(兼容旧数据库) + query.exec("ALTER TABLE info_t ADD COLUMN hot_num TEXT DEFAULT ''"); + + if (query.exec(checkTableEmpty)) + { + if (query.next() && query.value(0).toInt() == 0) + { + if (!query.exec(insertInitialData)) + { + qDebug() << "Error inserting initial data:" << query.lastError(); + } + } + } + else + { + qDebug() << "Error checking table:" << query.lastError(); + } + + return true; + // db.close(); + } + + bool closeDatabase() + { + if (db.isOpen()) + { + db.close(); + return true; + } + return false; + } + + bool insert_ResData(const QString &chexing, const QString &sm_num, const QString &hot_num, double value, const QString &detect, const QString &state, const QString &flag, const QString &name) + { + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return false; + } + } + + QSqlQuery query(db); + query.prepare("INSERT INTO res_data_t (chexing, sm_num, hot_num, value, detect, state, flag, name, timestamp) VALUES (:chexing, :sm_num, :hot_num, :value, :detect, :state, :flag, :name, datetime('now', 'localtime'))"); + query.bindValue(":chexing", chexing); + query.bindValue(":sm_num", sm_num); + query.bindValue(":hot_num", hot_num); + query.bindValue(":value", value); + query.bindValue(":detect", detect); + query.bindValue(":state", state); + query.bindValue(":flag", flag); + query.bindValue(":name", name); + + if (!query.exec()) + { + qDebug() << "Error: Unable to insert data into res_data_t table" << query.lastError(); + return false; + } + + return true; + } + + int delete_ResData(int id) + { + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return -1; + } + } + + QSqlQuery query(db); + query.prepare("DELETE FROM res_data_t WHERE id = :id"); + query.bindValue(":id", id); + + if (!query.exec()) + { + qDebug() << "Error: Unable to delete data from res_data_t table" << query.lastError(); + return -1; + } + + // 没有行受到影响 + if (query.numRowsAffected() == 0) + { + qDebug() << "Warning: No data deleted, ID not found"; + return 0; + } + + return 1; + } + + bool update_ResData(int id, const QString &chexing, const QString &sm_num, const QString &hot_num, double value, const QString &detect, const QString &state, const QString &flag, const QString &name) + { + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return false; + } + } + + QSqlQuery query(db); + query.prepare("UPDATE res_data_t SET chexing = :chexing, sm_num = :sm_num, hot_num = :hot_num, value = :value, detect = :detect, state = :state, flag = :flag, name = :name WHERE id = :id"); + query.bindValue(":chexing", chexing); + query.bindValue(":sm_num", sm_num); + query.bindValue(":hot_num", hot_num); + query.bindValue(":value", value); + query.bindValue(":detect", detect); + query.bindValue(":state", state); + query.bindValue(":flag", flag); + query.bindValue(":name", name); + query.bindValue(":id", id); + + if (!query.exec()) + { + qDebug() << "Error: Unable to update data in res_data_t table" << query.lastError(); + return false; + } + + return true; + } + + QList read_ResData() + { + QList dataList; + + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return dataList; + } + } + + QSqlQuery query(db); + if (!query.exec("SELECT * FROM res_data_t")) + { + qDebug() << "Error: Unable to read data from res_data_t table" << query.lastError(); + return dataList; + } + + while (query.next()) + { + QVariantMap data; + data["id"] = query.value("id"); + data["chexing"] = query.value("chexing"); + data["sm_num"] = query.value("sm_num"); + data["hot_num"] = query.value("hot_num"); + data["value"] = query.value("value"); + data["detect"] = query.value("detect"); + data["state"] = query.value("state"); + data["flag"] = query.value("flag"); + data["name"] = query.value("name"); + data["timestamp"] = query.value("timestamp"); + dataList.append(data); + } + + return dataList; + } + + int getResTotalRows() + { + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return 0; + } + } + + QSqlQuery query(db); + query.prepare("SELECT COUNT(*) FROM res_data_t"); + if (query.exec() && query.next()) + { + return query.value(0).toInt(); + } + return 0; + } + + QVector getData(int limit, int offset) + { + QVector data; + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return data; + } + } + + QSqlQuery query(db); + query.prepare("SELECT * FROM res_data_t ORDER BY id LIMIT :limit OFFSET :offset"); + query.bindValue(":limit", limit); + query.bindValue(":offset", offset); + + if (query.exec()) + { + while (query.next()) + { + QVariantList row; + for (int i = 0; i < query.record().count(); ++i) + { + row.append(query.value(i)); + } + data.append(row); + } + } + else + { + qDebug() << "Error: Unable to fetch data from res_data_t table" << query.lastError(); + } + + return data; + } + + bool insertTestData(int numRows) + { + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return false; + } + } + + QSqlQuery query(db); + query.prepare("INSERT INTO res_data_t (chexing, sm_num, hot_num, value, detect, state, flag, name, timestamp) VALUES (:chexing, :sm_num, :hot_num, :value, :detect, :state, :flag, :name, datetime('now', 'localtime'))"); + + db.transaction(); + for (int i = 1; i <= numRows; ++i) + { + query.bindValue(":chexing", QString("chexing%1").arg(i)); + query.bindValue(":sm_num", QString("sm_num%1").arg(i)); + query.bindValue(":hot_num", QString("hot_num%1").arg(i)); + query.bindValue(":value", i * 1.0); + query.bindValue(":detect", QString("detect%1").arg(i)); + query.bindValue(":state", QString("state%1").arg(i)); + query.bindValue(":flag", QString("flag%1").arg(i)); + query.bindValue(":name", QString("name%1").arg(i)); + if (!query.exec()) + { + db.rollback(); + qDebug() << "Error inserting data:" << query.lastError(); + return false; + } + } + db.commit(); + return true; + } + + QVector getAllData() + { + QVector data; + if (!db.isOpen() && !db.open()) + { + if (!init_Database()) + { + qDebug() << "Error: Unable to open the database"; + return data; + } + } + + QSqlQuery query(db); + // 明确指定列顺序,确保与导出表头匹配 + if (!query.exec("SELECT id, chexing, sm_num, hot_num, value, detect, state, flag, name, timestamp FROM res_data_t")) + { + qDebug() << "Error: failed to execute query -" << query.lastError(); + return data; + } + + while (query.next()) + { + QVariantList row; + for (int i = 0; i < query.record().count(); ++i) + { + row.append(query.value(i)); + } + data.append(row); + } + + return data; + } + + bool insert_Info_t(const QString &column, const QVariant &value) + { + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return false; + } + } + + QSqlQuery query(db); + + query.prepare("SELECT COUNT(*) FROM info_t"); + if (!query.exec() || !query.next()) + { + qDebug() << "Error: Unable to check data in info_t table" << query.lastError(); + return false; + } + + int count = query.value(0).toInt(); + + if (count == 0) + { + query.prepare(QString("INSERT INTO info_t (%1) VALUES (:value)").arg(column)); + } + else + { + query.prepare(QString("UPDATE info_t SET %1 = :value").arg(column)); + } + + query.bindValue(":value", value); + + if (!query.exec()) + { + qDebug() << "Error: Unable to insert/update data in info_t table" << query.lastError(); + return false; + } + + return true; + } + + bool update_Info_t(const QString &column, const QVariant &value) + { + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return false; + } + } + + QSqlQuery query(db); + query.prepare(QString("UPDATE info_t SET %1 = :value").arg(column)); + query.bindValue(":value", value); + + if (!query.exec()) + { + qDebug() << "Error: Unable to update data in info_t table" << query.lastError(); + return false; + } + + return true; + } + + QVariant read_Info_t(const QString &column) + { + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return QVariant(); + } + } + + QSqlQuery query(db); + query.prepare(QString("SELECT %1 FROM info_t").arg(column)); + + if (!query.exec()) + { + qDebug() << "Error: Unable to read data from info_t table" << query.lastError(); + return QVariant(); + } + + if (query.next()) + { + return query.value(0); + } + + return QVariant(); + } + + bool delete_Info_t() + { + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return false; + } + } + + QSqlQuery query(db); + query.prepare("DELETE FROM info_t"); + + if (!query.exec()) + { + qDebug() << "Error: Unable to clear data from info_t table" << query.lastError(); + return false; + } + + return true; + } + + QMap read_all_Info_t() + { + QMap data; + QSqlDatabase db = QSqlDatabase::database(); + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return data; + } + } + + QSqlQuery query(db); + query.prepare("SELECT * FROM info_t"); + + if (!query.exec()) + { + qDebug() << "Error: Unable to read data from info_t table" << query.lastError(); + return data; + } + + if (query.next()) + { + for (int i = 0; i < query.record().count(); ++i) + { + QString columnName = query.record().fieldName(i); + data[columnName] = query.value(i); + } + } + + return data; + } + + bool insertOrUpdateDailyData(int daily_inspection_count, int manual_inspection_count, int qualified, int over_limit, int under_limit, double max_value, double min_value, double qualification_rate) + { + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + if (!init_Database()) + { + return false; + } + } + + QSqlQuery query(db); + + QString currentDate = QDate::currentDate().toString("yyyy-MM-dd"); + + query.prepare("SELECT ID, date FROM daily_data_t ORDER BY ID DESC LIMIT 1"); + if (!query.exec()) + { + qDebug() << "Error: Unable to retrieve data from daily_data_t table" << query.lastError(); + return false; + } + + int latestId = -1; + QString latestDate; + if (query.next()) + { + latestId = query.value(0).toInt(); + latestDate = query.value(1).toString(); + } + + // qDebug() << "latestDate:" << latestDate; + // qDebug() << "currentDate:" << currentDate; + + QString latestDateOnly = latestDate.left(10); + + // qDebug() << "latestDateOnly:" << latestDateOnly; + + if (latestDateOnly == currentDate) + { + query.prepare("UPDATE daily_data_t SET daily_inspection_count = :daily_inspection_count, " + "manual_inspection_count = :manual_inspection_count, qualified = :qualified, " + "over_limit = :over_limit, under_limit = :under_limit, max_value = :max_value, " + "min_value = :min_value, qualification_rate = :qualification_rate " + "WHERE ID = :id"); + query.bindValue(":id", latestId); + } + else + { + query.prepare("INSERT INTO daily_data_t (daily_inspection_count, manual_inspection_count, qualified, over_limit, under_limit, max_value, min_value, qualification_rate, date) " + "VALUES (:daily_inspection_count, :manual_inspection_count, :qualified, :over_limit, :under_limit, :max_value, :min_value, :qualification_rate, datetime('now', 'localtime'))"); + } + + query.bindValue(":daily_inspection_count", daily_inspection_count); + query.bindValue(":manual_inspection_count", manual_inspection_count); + query.bindValue(":qualified", qualified); + query.bindValue(":over_limit", over_limit); + query.bindValue(":under_limit", under_limit); + query.bindValue(":max_value", max_value); + query.bindValue(":min_value", min_value); + query.bindValue(":qualification_rate", qualification_rate); + + if (!query.exec()) + { + qDebug() << "Error: Unable to insert or update data in daily_data_t table" << query.lastError(); + return false; + } + + return true; + } + + QList getLastFiveDailyDataSkippingToday() + { + QList dataList; + + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + return dataList; + } + + QSqlQuery query(db); + + QString currentDate = QDate::currentDate().toString("yyyy-MM-dd"); + + query.prepare("SELECT date FROM daily_data_t ORDER BY ID DESC LIMIT 1"); + if (!query.exec()) + { + qDebug() << "Error: Unable to retrieve data from daily_data_t table" << query.lastError(); + return dataList; + } + + QString latestDate; + if (query.next()) + { + latestDate = query.value(0).toString(); + } + + QString latestDateOnly = latestDate.left(10); + + // qDebug() << "currentDate:" << currentDate; + // qDebug() << "latestDateOnly:" << latestDateOnly; + + if (latestDateOnly == currentDate) + { + query.prepare("SELECT ID, daily_inspection_count, manual_inspection_count, qualified, over_limit, under_limit, max_value, min_value, qualification_rate, date " + "FROM daily_data_t WHERE date < :currentDate ORDER BY ID DESC LIMIT 5"); + query.bindValue(":currentDate", currentDate); + } + else + { + query.prepare("SELECT ID, daily_inspection_count, manual_inspection_count, qualified, over_limit, under_limit, max_value, min_value, qualification_rate, date " + "FROM daily_data_t ORDER BY ID DESC LIMIT 5"); + } + + if (!query.exec()) + { + qDebug() << "Error: Unable to retrieve data from daily_data_t table" << query.lastError(); + return dataList; + } + + while (query.next()) + { + QVariantList row; + row << query.value(0) // ID + << query.value(1) // daily_inspection_count + << query.value(2) // manual_inspection_count + << query.value(3) // qualified + << query.value(4) // over_limit + << query.value(5) // under_limit + << query.value(6) // max_value + << query.value(7) // min_value + << query.value(8) // qualification_rate + << query.value(9); // date + dataList.append(row); + } + + return dataList; + } + + QString get_today_daliy_t(int &daily_cnt, int &daily_cnt_manual, + int &daily_qualified_cnt, int &daily_cnt_over, + int &daily_cnt_down, double &daily_cnt_max, + double &daily_cnt_min) + { + + if (!db.isOpen() && !db.open()) + { + qDebug() << "Error: Unable to open the database"; + return ""; + } + + QSqlQuery query(db); + + QString currentDate = QDate::currentDate().toString("yyyy-MM-dd"); + // qDebug() << "Current date: " << currentDate; + + query.prepare("SELECT date FROM daily_data_t ORDER BY ID DESC LIMIT 1"); + if (!query.exec()) + { + qDebug() << "Error: Unable to retrieve data from daily_data_t table" << query.lastError(); + return ""; + } + + QString latestDate; + if (query.next()) + { + latestDate = query.value(0).toString(); + // qDebug() << "Latest date in database: " << latestDate; + } + else + { + qDebug() << "No records found in daily_data_t table"; + return ""; + } + + QString latestDateOnly = latestDate.left(10); + // qDebug() << "Latest date only: " << latestDateOnly; + + if (latestDateOnly == currentDate) + { + query.prepare("SELECT daily_inspection_count, manual_inspection_count, qualified, over_limit, under_limit, max_value, min_value " + "FROM daily_data_t WHERE date(date) = :date"); + query.bindValue(":date", currentDate); + + if (!query.exec()) + { + qDebug() << "Error: Unable to retrieve today's data from daily_data_t table" << query.lastError(); + return ""; + } + + if (query.next()) + { + daily_cnt = query.value(0).toInt(); // daily_inspection_count + // qDebug() << "daily_cnt db " << daily_cnt; + daily_cnt_manual = query.value(1).toInt(); // manual_inspection_count + daily_qualified_cnt = query.value(2).toInt(); // qualified + daily_cnt_over = query.value(3).toInt(); // over_limit + daily_cnt_down = query.value(4).toInt(); // under_limit + daily_cnt_max = query.value(5).toDouble(); // max_value + daily_cnt_min = query.value(6).toDouble(); // min_value + } + else + { + qDebug() << "No data found for today's date"; + return ""; + } + + return latestDateOnly; + } + else + { + daily_cnt = 0; + daily_cnt_manual = 0; + daily_qualified_cnt = 0; + daily_cnt_over = 0; + daily_cnt_down = 0; + daily_cnt_max = 0; + daily_cnt_min = 0; + qDebug() << "Latest date does not match current date"; + return ""; + } + } + + bool updateLastDate() + { + if (!db.isOpen()) + { + qWarning() << "Database is not open!"; + return false; + } + + QSqlQuery query(db); + if (!query.exec("SELECT date FROM daily_data_t ORDER BY ID DESC LIMIT 1")) + { + qWarning() << "Failed to execute query:" << query.lastError().text(); + return false; + } + + if (!query.next()) + { + qWarning() << "No records found in the table."; + return false; + } + + QDate lastDate = query.value(0).toDate(); + qDebug() << "Last date in the table:" << lastDate; + + QDate newDate = lastDate.addDays(-1); + qDebug() << "New date after subtracting one day:" << newDate; + QSqlQuery updateQuery(db); + updateQuery.prepare("UPDATE daily_data_t SET date = :newDate WHERE ID = (SELECT MAX(ID) FROM daily_data_t)"); + updateQuery.bindValue(":newDate", newDate); + + if (!updateQuery.exec()) + { + qWarning() << "Failed to update date:" << updateQuery.lastError().text(); + return false; + } + + qDebug() << "Date updated successfully."; + return true; + } + +} diff --git a/databaseutils.h b/databaseutils.h new file mode 100644 index 0000000..c27961a --- /dev/null +++ b/databaseutils.h @@ -0,0 +1,42 @@ +#ifndef DATABASEUTILS_H +#define DATABASEUTILS_H + +#include +#include +#include +#include +#include +#include +#include + +namespace DatabaseUtils +{ + extern QSqlDatabase db; + + bool init_Database(); + bool closeDatabase(); + bool insert_ResData(const QString &chexing, const QString &sm_num, const QString &hot_num, double value, const QString &detect, const QString &state, const QString &flag, const QString &name); + int delete_ResData(int id); + bool update_ResData(int id, const QString &chexing, const QString &sm_num, const QString &hot_num, double value, const QString &detect, const QString &state, const QString &flag, const QString &name); + QList read_ResData(); + int getResTotalRows(); + QVector getData(int limit, int offset); + QVector getAllData(); + bool insertTestData(int numRows); + bool insert_Info_t(const QString &column, const QVariant &value); + bool delete_Info_t(); + bool update_Info_t(const QString &column, const QVariant &value); + QVariant read_Info_t(const QString &column); + QMap read_all_Info_t(); + + QList getLastFiveDailyDataSkippingToday(); + bool insertOrUpdateDailyData(int daily_inspection_count, int manual_inspection_count, int qualified, int over_limit, int under_limit, double max_value, double min_value, double qualification_rate); + QString get_today_daliy_t(int &daily_cnt, int &daily_cnt_manual, + int &daily_qualified_cnt, int &daily_cnt_over, + int &daily_cnt_down, double &daily_cnt_max, + double &daily_cnt_min); + bool updateLastDate(); + +} + +#endif // DATABASEUTILS_H diff --git a/exe/再登高电阻检测软件.exe b/exe/再登高电阻检测软件.exe new file mode 100644 index 0000000..fe797a3 Binary files /dev/null and b/exe/再登高电阻检测软件.exe differ diff --git a/generate_activation_code.py b/generate_activation_code.py new file mode 100644 index 0000000..e02398c --- /dev/null +++ b/generate_activation_code.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +激活码生成工具 +用于根据客户设备UUID生成激活码 + +使用方法: + python generate_activation_code.py <设备UUID> <客户密码> + +示例: + python generate_activation_code.py "12345678-1234-1234-1234-123456789ABC" "888888" + +参数说明: + 设备UUID: 客户设备的主板UUID + 客户密码: 与程序中 customerPassword 一致的密码 +""" + +import sys +import base64 +import hashlib + +# ============ 配置参数 ============ +ENCRYPT_KEY = b"GetonAgain2026SecretKey!@#$%" # 加密密钥(必须与程序一致) +# ================================== + +def encrypt(plain_text: str) -> str: + """ + 加密函数 - 与 C++ LicenseManager::encrypt 完全对应 + """ + data = plain_text.encode('utf-8') + + # 第一步:计算SHA256哈希 + hash_obj = hashlib.sha256(data) + hash_bytes = hash_obj.digest() + + # 第二步:构造加密数据 = 哈希前8字节 + 原始数据 + to_encrypt = hash_bytes[:8] + data + + # 第三步:多层加密 + encrypted = bytearray() + for i, byte in enumerate(to_encrypt): + c = byte + + # 第一层:与主密钥XOR + c = c ^ ENCRYPT_KEY[i % len(ENCRYPT_KEY)] + + # 第二层:字节置换(非线性变换) + c = (c * 7 + 13) % 256 + + # 第三层:与位置相关的混淆 + c = c ^ ((i * 31 + 17) % 256) + + encrypted.append(c) + + # 第四步:Base64编码 + return base64.b64encode(bytes(encrypted)).decode('latin-1') + + +def decrypt(encrypted_text: str) -> str: + """ + 解密函数 - 与 C++ LicenseManager::decrypt 完全对应 + """ + # Base64解码 + encrypted = bytearray(base64.b64decode(encrypted_text.encode('latin-1'))) + + if len(encrypted) < 9: # 至少8字节校验头 + 1字节数据 + return "" + + # 逆向解密 + decrypted = bytearray() + for i, byte in enumerate(encrypted): + c = byte + + # 逆向第三层:位置混淆 + c = c ^ ((i * 31 + 17) % 256) + + # 逆向第二层:字节置换的逆运算 + # 7 的模逆元是 183 (因为 7 * 183 = 1281 = 5 * 256 + 1) + c = ((c + 256 - 13) * 183) % 256 + + # 逆向第一层:与主密钥XOR + c = c ^ ENCRYPT_KEY[i % len(ENCRYPT_KEY)] + + decrypted.append(c) + + # 提取校验头和原始数据 + stored_hash = bytes(decrypted[:8]) + original_data = bytes(decrypted[8:]) + + # 验证哈希 + computed_hash = hashlib.sha256(original_data).digest()[:8] + if computed_hash != stored_hash: + print("警告: 哈希校验失败,数据可能已损坏") + return "" + + return original_data.decode('utf-8') + + +def generate_activation_code(uuid: str, password: str) -> str: + """ + 生成激活码 + 格式: encrypt(UUID:password) + """ + plain_text = f"{uuid}:{password}" + return encrypt(plain_text) + + +def verify_activation_code(activation_code: str, expected_uuid: str, expected_password: str) -> bool: + """验证激活码是否有效""" + try: + decrypted = decrypt(activation_code) + + if not decrypted or ':' not in decrypted: + return False + + # 使用 rsplit 从右侧分割,只分割一次 + parts = decrypted.rsplit(':', 1) + uuid = parts[0] + password = parts[1] + + return uuid == expected_uuid and password == expected_password + + except Exception as e: + print(f"验证错误: {e}") + return False + + +def save_to_csv(uuid: str, activation_code: str, customer_name: str = ""): + """ + 将激活记录保存到CSV文件 + """ + import csv + import os + from datetime import datetime + + csv_file = "activation_records.csv" + file_exists = os.path.exists(csv_file) + + # 获取当前时间 + activation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + try: + with open(csv_file, 'a', newline='', encoding='utf-8-sig') as f: + writer = csv.writer(f) + + # 如果文件不存在,先写入表头 + if not file_exists: + writer.writerow(['序号', '设备UUID', '激活码', '激活时间', '客户名称', '备注']) + + # 统计当前记录数作为序号 + record_num = 1 + if file_exists: + with open(csv_file, 'r', encoding='utf-8-sig') as rf: + record_num = sum(1 for _ in rf) # 包含表头的行数就是新记录的序号 + + # 写入新记录 + writer.writerow([record_num, uuid, activation_code, activation_time, customer_name, '']) + + print(f"激活记录已保存到: {csv_file}") + + except Exception as e: + print(f"保存CSV记录失败: {e}") + + +def main(): + # 检查参数 + if len(sys.argv) < 3: + print("用法: python generate_activation_code.py <设备UUID> <客户密码>") + print("示例: python generate_activation_code.py \"12345678-1234-1234-1234-123456789ABC\" \"888888\"") + return 1 + + uuid = sys.argv[1].strip() + password = sys.argv[2].strip() + + if not uuid: + print("错误: UUID 不能为空!") + return 1 + + if not password: + print("错误: 密码不能为空!") + return 1 + + # 生成激活码 + activation_code = generate_activation_code(uuid, password) + + # 验证 + if not verify_activation_code(activation_code, uuid, password): + print("错误: 激活码验证失败!") + return 1 + + # 保存激活码文件 + filename = f"license_{uuid[:8]}.gal" + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(activation_code) + except Exception as e: + print(f"保存文件失败: {e}") + return 1 + + # 记录到CSV + save_to_csv(uuid, activation_code) + + # 输出结果 + print(f"UUID: {uuid}") + print(f"密码: {password}") + print(f"激活码: {activation_code}") + print(f"文件: {filename}") + print("完成") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/hidreader.cpp b/hidreader.cpp new file mode 100644 index 0000000..db09dde --- /dev/null +++ b/hidreader.cpp @@ -0,0 +1,73 @@ +#include "hidreader.h" +#include + +HIDReader::HIDReader(QObject *parent) + : QThread(parent), quit(false), device(nullptr) { +} + +HIDReader::~HIDReader() { + stopReading(); + wait(); + if (device) { + hid_close(device); + } + hid_exit(); +} + +void HIDReader::startReading() { + QMutexLocker locker(&mutex); + if (!isRunning()) { + start(); + } else { + condition.wakeOne(); + } +} + +void HIDReader::stopReading() { + QMutexLocker locker(&mutex); + quit = true; + condition.wakeOne(); +} + + +void HIDReader::run() { + qDebug() << "Initializing HID..."; + if (hid_init() != 0) { + qWarning() << "Failed to initialize HID library"; + return; + } + + qDebug() << "Opening HID device..."; + device = hid_open(0x046a, 0x00cb, nullptr); + if (!device) { + qWarning() << "Unable to open device"; + return; + } + qDebug() << "HID device opened successfully"; + + while (!quit) { + unsigned char buf[256]; + qDebug() << "Reading from HID device..."; + int res = hid_read(device, buf, sizeof(buf)); + if (res > 0) { + qDebug() << "Data read from HID device:" << QByteArray(reinterpret_cast(buf), res).toHex(); + QByteArray data(reinterpret_cast(buf), res); + emit dataRead(data); + } else if (res < 0) { + const wchar_t* error = hid_error(device); + if (error) { + qWarning() << "Error reading from HID device:" << QString::fromWCharArray(error); + } else { + qWarning() << "Unknown error reading from HID device"; + } + break; + } else { + qDebug() << "No data read from HID device"; + } + } + + qDebug() << "Closing HID device..."; + hid_close(device); + device = nullptr; + qDebug() << "HID device closed"; +} diff --git a/hidreader.h b/hidreader.h new file mode 100644 index 0000000..e33a81b --- /dev/null +++ b/hidreader.h @@ -0,0 +1,34 @@ +#ifndef HIDREADER_H +#define HIDREADER_H + +#include +#include +#include +#include +#include +#include + +class HIDReader : public QThread { + Q_OBJECT + +public: + HIDReader(QObject *parent = nullptr); + ~HIDReader(); + + void startReading(); + void stopReading(); + +signals: + void dataRead(const QByteArray &data); + +protected: + void run() override; + +private: + QMutex mutex; + QWaitCondition condition; + bool quit; + hid_device *device; +}; + +#endif // HIDREADER_H diff --git a/licensemanager.cpp b/licensemanager.cpp new file mode 100644 index 0000000..6c8f833 --- /dev/null +++ b/licensemanager.cpp @@ -0,0 +1,506 @@ +#include "licensemanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ==================== 静态成员初始化 ==================== +// 加密密钥 +const QByteArray LicenseManager::encryptKey = "GetonAgain2026SecretKey!@#$%"; +// 客户专用密码 - 根据不同客户修改此值 +const QString LicenseManager::customerPassword = "nuobo123"; + +// ==================== LicenseManager 实现 ==================== + +LicenseManager::LicenseManager(QObject *parent) : QObject(parent) +{ +} + +QString LicenseManager::getMachineUUID() +{ + QString uuid; + +#ifdef Q_OS_WIN + // Windows: 通过WMI获取主板UUID + QProcess process; + process.start("wmic", QStringList() << "csproduct" << "get" << "UUID"); + process.waitForFinished(5000); + QString output = QString::fromLocal8Bit(process.readAllStandardOutput()); + + // 解析输出,获取UUID + QStringList lines = output.split('\n', Qt::SkipEmptyParts); + for (const QString &line : lines) + { + QString trimmed = line.trimmed(); + if (!trimmed.isEmpty() && trimmed != "UUID" && trimmed.contains('-')) + { + uuid = trimmed; + break; + } + } +#else + // Linux/Mac: 读取machine-id + QFile file("/etc/machine-id"); + if (file.open(QIODevice::ReadOnly)) + { + uuid = QString::fromUtf8(file.readAll()).trimmed(); + file.close(); + } +#endif + + if (uuid.isEmpty()) + { + // 备用方案:使用机器名+用户名的哈希 + QString fallback = QSysInfo::machineHostName() + QDir::homePath(); + uuid = QString::fromLatin1(QCryptographicHash::hash(fallback.toUtf8(), QCryptographicHash::Md5).toHex()); + } + + return uuid; +} + +QString LicenseManager::getLicenseFilePath() +{ + // 激活文件放在程序目录下 + return QCoreApplication::applicationDirPath() + "/license.gal"; +} + +QString LicenseManager::encrypt(const QString &plainText) +{ + QByteArray data = plainText.toUtf8(); + + // 第一步:计算明文的SHA256哈希(用于完整性校验) + QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256); + + // 第二步:构造加密数据 = 哈希前8字节 + 原始数据 + QByteArray toEncrypt; + toEncrypt.append(hash.left(8)); // 8字节校验头 + toEncrypt.append(data); + + // 第三步:多层XOR加密 + QByteArray encrypted; + for (int i = 0; i < toEncrypt.size(); ++i) + { + unsigned char c = static_cast(toEncrypt[i]); + + // 第一层:与主密钥XOR + c = c ^ static_cast(encryptKey[i % encryptKey.size()]); + + // 第二层:字节置换(非线性变换) + c = static_cast((c * 7 + 13) % 256); + + // 第三层:与位置相关的混淆 + c = c ^ static_cast((i * 31 + 17) % 256); + + encrypted.append(static_cast(c)); + } + + // 第四步:Base64编码 + return QString::fromLatin1(encrypted.toBase64()); +} + +QString LicenseManager::decrypt(const QString &encryptedText) +{ + // Base64解码 + QByteArray encrypted = QByteArray::fromBase64(encryptedText.toLatin1()); + + if (encrypted.size() < 9) // 至少8字节校验头 + 1字节数据 + { + return QString(); + } + + // 逆向解密 + QByteArray decrypted; + for (int i = 0; i < encrypted.size(); ++i) + { + unsigned char c = static_cast(encrypted[i]); + + // 逆向第三层:位置混淆 + c = c ^ static_cast((i * 31 + 17) % 256); + + // 逆向第二层:字节置换的逆运算 + // 原式: c' = (c * 7 + 13) % 256 + // 需要找到 7 在 mod 256 下的逆元 + // 7 * 183 = 1281 = 5 * 256 + 1, 所以 7^(-1) ≡ 183 (mod 256) + c = static_cast(((c + 256 - 13) * 183) % 256); + + // 逆向第一层:与主密钥XOR + c = c ^ static_cast(encryptKey[i % encryptKey.size()]); + + decrypted.append(static_cast(c)); + } + + // 提取校验头和原始数据 + QByteArray storedHash = decrypted.left(8); + QByteArray originalData = decrypted.mid(8); + + // 验证哈希 + QByteArray computedHash = QCryptographicHash::hash(originalData, QCryptographicHash::Sha256); + if (computedHash.left(8) != storedHash) + { + qDebug() << "License integrity check failed - data may be corrupted or tampered"; + return QString(); // 返回空表示解密失败 + } + + return QString::fromUtf8(originalData); +} + +QString LicenseManager::generateActivationCode(const QString &uuid) +{ + // 生成激活码格式: encrypt(UUID:password) + // 这个方法供管理员/供应商使用,为特定设备生成激活码 + QString plainText = uuid + ":" + customerPassword; + return encrypt(plainText); +} + +bool LicenseManager::readAndVerifyLicense() +{ + QString filePath = getLicenseFilePath(); + QFile file(filePath); + + if (!file.exists()) + { + qDebug() << "License file not found:" << filePath; + return false; + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + qDebug() << "Cannot open license file"; + return false; + } + + QTextStream in(&file); + QString encryptedContent = in.readAll().trimmed(); + file.close(); + + if (encryptedContent.isEmpty()) + { + qDebug() << "License file is empty"; + return false; + } + + // 解密 + QString decrypted = decrypt(encryptedContent); + qDebug() << "Decrypted license content (debug)"; + + // 解析格式: UUID:password + // 使用 lastIndexOf 因为UUID中也可能包含冒号(虽然通常不会) + int separatorIndex = decrypted.lastIndexOf(':'); + if (separatorIndex == -1) + { + qDebug() << "Invalid license format: no separator"; + return false; + } + + QString licenseUUID = decrypted.left(separatorIndex); + QString password = decrypted.mid(separatorIndex + 1); + + // 获取本机真实UUID + QString currentUUID = getMachineUUID(); + + qDebug() << "License UUID:" << licenseUUID; + qDebug() << "Current UUID:" << currentUUID; + + // 验证UUID是否匹配 + if (licenseUUID != currentUUID) + { + qDebug() << "UUID mismatch - license not valid for this machine"; + return false; + } + + // 验证密码是否正确 + if (password != customerPassword) + { + qDebug() << "Password mismatch"; + return false; + } + + qDebug() << "License verification successful"; + return true; +} + +bool LicenseManager::saveLicenseFile(const QString &activationCode) +{ + QString filePath = getLicenseFilePath(); + QFile file(filePath); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + qDebug() << "Cannot save license file to:" << filePath; + return false; + } + + QTextStream out(&file); + out << activationCode; + file.close(); + + qDebug() << "License file saved to:" << filePath; + return true; +} + +bool LicenseManager::showActivationDialog(QWidget *parent) +{ + ActivationDialog dialog(parent); + + while (true) + { + if (dialog.exec() != QDialog::Accepted) + { + return false; // 用户取消 + } + + QString serverAddress = dialog.getServerAddress(); + QString activationCode = dialog.getActivationCode(); + + // 解析服务器地址和端口 + QString host; + int port = 8080; + if (serverAddress.contains(':')) + { + QStringList parts = serverAddress.split(':'); + host = parts[0]; + port = parts[1].toInt(); + } + else + { + host = serverAddress; + } + + // 显示连接中提示 + QMessageBox waitMsg(parent); + waitMsg.setWindowTitle("请稍候"); + waitMsg.setText("正在连接激活服务器..."); + waitMsg.setStandardButtons(QMessageBox::NoButton); + waitMsg.show(); + QApplication::processEvents(); + + // 连接服务器 + QTcpSocket socket; + socket.connectToHost(host, port); + + bool connected = socket.waitForConnected(10000); // 10秒超时 + + if (!connected) + { + waitMsg.close(); + QMessageBox::warning(parent, "连接失败", + QString("无法连接到服务器 %1:%2\n请检查网络连接和服务器地址。").arg(host).arg(port)); + continue; + } + + // 构建激活请求 + QJsonObject requestObj; + requestObj["uuid"] = getMachineUUID(); + requestObj["code"] = activationCode; + requestObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); + QJsonDocument doc(requestObj); + QByteArray requestData = doc.toJson(QJsonDocument::Compact); + + // 发送请求 + socket.write(requestData); + socket.write("\n"); + socket.flush(); + + // 等待响应 + if (!socket.waitForReadyRead(15000)) // 15秒超时 + { + waitMsg.close(); + socket.close(); + QMessageBox::warning(parent, "激活失败", "服务器响应超时,请重试。"); + continue; + } + + QByteArray response = socket.readAll(); + socket.close(); + waitMsg.close(); + + // 解析响应 + QJsonDocument responseDoc = QJsonDocument::fromJson(response); + if (responseDoc.isNull() || !responseDoc.isObject()) + { + QMessageBox::warning(parent, "激活失败", "服务器响应格式错误。"); + continue; + } + + QJsonObject responseObj = responseDoc.object(); + QString status = responseObj["status"].toString(); + + if (status == "success") + { + QString license = responseObj["license"].toString(); + + // 保存激活文件 + if (saveLicenseFile(license)) + { + // 验证保存的激活文件 + if (readAndVerifyLicense()) + { + QMessageBox::information(parent, "成功", "软件激活成功!"); + return true; + } + } + + QMessageBox::warning(parent, "错误", "保存激活文件失败!"); + } + else + { + QString message = responseObj["message"].toString(); + if (message.isEmpty()) + message = "激活失败,请检查激活码是否正确。"; + + QMessageBox::StandardButton retry = QMessageBox::warning( + parent, "激活失败", message, + QMessageBox::Retry | QMessageBox::Cancel); + + if (retry != QMessageBox::Retry) + { + return false; + } + } + } +} + +// ==================== ActivationDialog 实现 ==================== + +ActivationDialog::ActivationDialog(QWidget *parent) : QDialog(parent) +{ + setWindowTitle("软件激活"); + setFixedSize(420, 240); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + // 确保对话框接收键盘输入 + setAttribute(Qt::WA_InputMethodEnabled, true); + + setupUI(); +} + +QString ActivationDialog::getServerAddress() const +{ + return serverEdit->text().trimmed(); +} + +QString ActivationDialog::getActivationCode() const +{ + return codeEdit->text().trimmed(); +} + +void ActivationDialog::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(25, 20, 25, 20); + mainLayout->setSpacing(12); + + // 标题 + QLabel *titleLabel = new QLabel("软件激活"); + titleLabel->setStyleSheet("font-size: 20px; font-weight: bold; color: #333;"); + titleLabel->setAlignment(Qt::AlignCenter); + mainLayout->addWidget(titleLabel); + + mainLayout->addSpacing(8); + + // 提示信息 + QLabel *infoLabel = new QLabel("请输入激活服务器地址和激活码"); + infoLabel->setStyleSheet("font-size: 12px; color: #666;"); + infoLabel->setAlignment(Qt::AlignCenter); + mainLayout->addWidget(infoLabel); + + mainLayout->addSpacing(8); + + // 服务器地址输入 - 使用 QFormLayout 风格 + QHBoxLayout *serverLayout = new QHBoxLayout(); + serverLayout->setSpacing(10); + QLabel *serverLabel = new QLabel("服务器地址:"); + serverLabel->setFixedWidth(80); + serverLabel->setStyleSheet("font-size: 13px;"); + + serverEdit = new QLineEdit(); + serverEdit->setMinimumHeight(32); + serverEdit->setPlaceholderText("例如: 192.168.1.100:8080"); + serverEdit->setStyleSheet("font-size: 13px; padding: 4px 8px;"); + + serverLayout->addWidget(serverLabel); + serverLayout->addWidget(serverEdit, 1); + mainLayout->addLayout(serverLayout); + + // 激活码输入 + QHBoxLayout *codeLayout = new QHBoxLayout(); + codeLayout->setSpacing(10); + QLabel *codeLabel = new QLabel("激 活 码:"); + codeLabel->setFixedWidth(80); + codeLabel->setStyleSheet("font-size: 13px;"); + + codeEdit = new QLineEdit(); + codeEdit->setMinimumHeight(32); + codeEdit->setPlaceholderText("例如: RB2026-XXXX-XXXX-XXXX"); + codeEdit->setStyleSheet("font-size: 13px; padding: 4px 8px;"); + + codeLayout->addWidget(codeLabel); + codeLayout->addWidget(codeEdit, 1); + mainLayout->addLayout(codeLayout); + + mainLayout->addSpacing(15); + + // 按钮 + QHBoxLayout *btnLayout = new QHBoxLayout(); + btnLayout->addStretch(); + + confirmBtn = new QPushButton("激 活"); + confirmBtn->setFixedSize(100, 36); + confirmBtn->setStyleSheet( + "QPushButton { background-color: #4CAF50; color: white; font-size: 14px; font-weight: bold; border-radius: 4px; }" + "QPushButton:hover { background-color: #45a049; }" + "QPushButton:pressed { background-color: #3d8b40; }"); + connect(confirmBtn, &QPushButton::clicked, this, &ActivationDialog::onConfirmClicked); + + cancelBtn = new QPushButton("退 出"); + cancelBtn->setFixedSize(100, 36); + cancelBtn->setStyleSheet( + "QPushButton { background-color: #f44336; color: white; font-size: 14px; font-weight: bold; border-radius: 4px; }" + "QPushButton:hover { background-color: #da3c30; }" + "QPushButton:pressed { background-color: #c62828; }"); + connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); + + btnLayout->addWidget(confirmBtn); + btnLayout->addSpacing(20); + btnLayout->addWidget(cancelBtn); + btnLayout->addStretch(); + mainLayout->addLayout(btnLayout); + + // 确保可以Tab切换 + setTabOrder(serverEdit, codeEdit); + setTabOrder(codeEdit, confirmBtn); + setTabOrder(confirmBtn, cancelBtn); +} + +void ActivationDialog::onConfirmClicked() +{ + if (serverEdit->text().trimmed().isEmpty()) + { + QMessageBox::warning(this, "提示", "请输入服务器地址!"); + serverEdit->setFocus(); + return; + } + + if (codeEdit->text().trimmed().isEmpty()) + { + QMessageBox::warning(this, "提示", "请输入激活码!"); + codeEdit->setFocus(); + return; + } + + accept(); +} diff --git a/licensemanager.h b/licensemanager.h new file mode 100644 index 0000000..bd744d3 --- /dev/null +++ b/licensemanager.h @@ -0,0 +1,93 @@ +#ifndef LICENSEMANAGER_H +#define LICENSEMANAGER_H + +#include +#include +#include +#include +#include +#include + +// ==================== 激活系统设计说明 ==================== +// 激活文件后缀: .gal (Geton Again License) +// 激活文件内容: 一行加密字符串 +// 加密前格式: "UUID:数字密码" +// +// 加密方式(多层混淆): +// 1. SHA256哈希校验头(8字节) +// 2. XOR密钥加密 +// 3. 非线性字节置换 (c * 7 + 13) % 256 +// 4. 位置相关混淆 +// 5. Base64编码 +// +// 验证流程: +// 1. 读取激活文件,多层解密 +// 2. 校验SHA256哈希确保完整性 +// 3. 解析出UUID和密码 +// 4. 对比UUID与本机真实UUID +// 5. 验证密码是否等于程序内置密码 +// =========================================================== + +class LicenseManager : public QObject +{ + Q_OBJECT + +public: + explicit LicenseManager(QObject *parent = nullptr); + + // 获取本机主板UUID + static QString getMachineUUID(); + + // 获取激活文件路径 + static QString getLicenseFilePath(); + + // 加密解密方法 + static QString encrypt(const QString &plainText); + static QString decrypt(const QString &encryptedText); + + // 生成激活码(供管理员/供应商使用,根据客户设备UUID生成) + static QString generateActivationCode(const QString &uuid); + + // 读取并验证激活文件 + static bool readAndVerifyLicense(); + + // 保存激活文件 + static bool saveLicenseFile(const QString &activationCode); + + // 显示激活对话框 + static bool showActivationDialog(QWidget *parent = nullptr); + +private: + // 加密密钥 (XOR key) + static const QByteArray encryptKey; + + // ========== 客户专用密码(根据客户修改此值) ========== + // 不同客户使用不同的密码,写死在程序中 + static const QString customerPassword; + // ===================================================== +}; + +// 激活对话框 +class ActivationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ActivationDialog(QWidget *parent = nullptr); + + QString getServerAddress() const; + QString getActivationCode() const; + +private slots: + void onConfirmClicked(); + +private: + void setupUI(); + + QLineEdit *serverEdit; + QLineEdit *codeEdit; + QPushButton *confirmBtn; + QPushButton *cancelBtn; +}; + +#endif // LICENSEMANAGER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..7638060 --- /dev/null +++ b/main.cpp @@ -0,0 +1,35 @@ +#include "mainwindow.h" +#include "licensemanager.h" + +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + // 激活检测 + if (!LicenseManager::readAndVerifyLicense()) + { + // 激活文件不存在或无效,显示激活对话框 + if (!LicenseManager::showActivationDialog(nullptr)) + { + // 用户取消激活或激活失败 + QMessageBox::critical(nullptr, "激活失败", + "软件未激活,程序即将退出。\n请联系供应商获取激活码。"); + return 0; + } + + // 再次验证激活 + if (!LicenseManager::readAndVerifyLicense()) + { + QMessageBox::critical(nullptr, "激活失败", + "激活验证失败,程序即将退出。"); + return 0; + } + } + + MainWindow w; + w.showMaximized(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..97da57e --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,1937 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include +#include "SerialConfig.h" +#include "serialportmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include "databaseutils.h" +#include +#include "ExportThread.h" +#include +#include +#include "passworddialog.h" +#include + +using namespace QtCharts; +QT_CHARTS_USE_NAMESPACE + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), serialPortManager(new SerialPortManager(this)), serialConfigDialog(new SerialConfig(this)), player(new QMediaPlayer(this)) +// , hidReader(new HIDReader(this)) +{ + ui->setupUi(this); + + // 设置窗口最小大小和默认大小 + this->setMinimumSize(1200, 750); + this->resize(1400, 950); + + ui->label_7->setFixedSize(100, 100); + ui->lcdNumber->setFixedSize(450, 100); + + // 创建一个白色背景的QWidget + QWidget *backgroundWidget = new QWidget; + backgroundWidget->setStyleSheet("background-color: white; border-radius: 20px;"); + + // 创建水平布局 + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setSpacing(40); + // 添加第一个控件(label_7) + hLayout->addWidget(ui->label_7); + + // 创建并添加第二个垂直布局(link_button 和 link_label),并设置最大高度为65 + QVBoxLayout *vLayout1 = new QVBoxLayout; + vLayout1->setSpacing(2); + vLayout1->setContentsMargins(0, 0, 0, 0); + ui->link_button->setMinimumHeight(80); + ui->link_button->setMinimumWidth(80); + ui->link_button->setMaximumHeight(80); + ui->link_button->setMaximumWidth(80); + ui->link_label->setMinimumHeight(25); + ui->link_label->setMinimumWidth(80); + ui->link_label->setMaximumHeight(25); + vLayout1->addWidget(ui->link_button, 0, Qt::AlignHCenter); + vLayout1->addWidget(ui->link_label, 0, Qt::AlignHCenter); + hLayout->addLayout(vLayout1); + + // 创建并添加第三个垂直布局(auto_button 和 auto_label),并设置最大高度为65 + QVBoxLayout *vLayout2 = new QVBoxLayout; + vLayout2->setSpacing(2); + vLayout2->setContentsMargins(0, 0, 0, 0); + ui->auto_button->setMinimumHeight(80); + ui->auto_button->setMinimumWidth(80); + ui->auto_button->setMaximumHeight(80); + ui->auto_button->setMaximumWidth(80); + ui->auto_label->setMinimumHeight(25); + ui->auto_label->setMinimumWidth(80); + ui->auto_label->setMaximumHeight(25); + vLayout2->addWidget(ui->auto_button, 0, Qt::AlignHCenter); + vLayout2->addWidget(ui->auto_label, 0, Qt::AlignHCenter); + hLayout->addLayout(vLayout2); + + // 创建并添加第四个垂直布局(manual_button 和 manual_label),并设置最大高度为65 + QVBoxLayout *vLayout3 = new QVBoxLayout; + vLayout3->setSpacing(2); + vLayout3->setContentsMargins(0, 0, 0, 0); + ui->manual_button->setMinimumHeight(80); + ui->manual_button->setMinimumWidth(80); + ui->manual_button->setMaximumHeight(80); + ui->manual_button->setMaximumWidth(80); + ui->manual_label->setMinimumHeight(25); + ui->manual_label->setMinimumWidth(80); + ui->manual_label->setMaximumHeight(25); + vLayout3->addWidget(ui->manual_button, 0, Qt::AlignHCenter); + vLayout3->addWidget(ui->manual_label, 0, Qt::AlignHCenter); + hLayout->addLayout(vLayout3); + + // 创建并添加第五个垂直布局(excel_b 和 label_5),并设置最大高度为65 + QVBoxLayout *vLayout4 = new QVBoxLayout; + vLayout4->setSpacing(2); + vLayout4->setContentsMargins(0, 0, 0, 0); + ui->excel_b->setMinimumHeight(80); + ui->excel_b->setMinimumWidth(80); + ui->excel_b->setMaximumHeight(80); + ui->excel_b->setMaximumWidth(80); + ui->label_5->setMinimumHeight(25); + ui->label_5->setMinimumWidth(80); + ui->label_5->setMaximumHeight(25); + vLayout4->addWidget(ui->excel_b, 0, Qt::AlignHCenter); + vLayout4->addWidget(ui->label_5, 0, Qt::AlignHCenter); + hLayout->addLayout(vLayout4); + + // 添加第六个控件(lcdNumber) + hLayout->addWidget(ui->lcdNumber); + + // 创建并添加第七个垂直布局(tishi_label 和 info_label),并设置最大高度为65 + QVBoxLayout *vLayout5 = new QVBoxLayout; + // ui->tishi_label->setMaximumHeight(65); + // ui->info_label->setMaximumHeight(65); + ui->tishi_label->setMaximumHeight(40); + ui->tishi_label->setMaximumWidth(140); + ui->tishi_label->setMinimumHeight(40); + ui->tishi_label->setMinimumWidth(140); + ui->info_label->setMaximumHeight(40); + ui->info_label->setMaximumWidth(140); + ui->info_label->setMinimumHeight(40); + ui->info_label->setMinimumWidth(140); + vLayout5->addWidget(ui->tishi_label); + vLayout5->addWidget(ui->info_label); + hLayout->addLayout(vLayout5); + + // 添加第八个控件(label_img) + // hLayout->addWidget(ui->label_img); + + // 将水平布局设置为一个QWidget的布局 + QWidget *hLayoutWidget = new QWidget; + hLayoutWidget->setLayout(hLayout); + hLayoutWidget->setFixedHeight(140); + hLayoutWidget->setMinimumWidth(1280); // 最小宽度 + + // 创建垂直布局并添加顶部和底部的垂直Spacer + QVBoxLayout *backgroundLayout = new QVBoxLayout; + backgroundLayout->setContentsMargins(0, 0, 0, 0); + backgroundLayout->addSpacerItem(new QSpacerItem(20, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + backgroundLayout->addWidget(hLayoutWidget, 0, Qt::AlignLeft); // 左对齐 + backgroundLayout->addSpacerItem(new QSpacerItem(20, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + + // 将背景布局设置为背景小部件的布局 + backgroundWidget->setLayout(backgroundLayout); + + // 创建垂直布局,将背景小部件和tabWidget添加进去 + QVBoxLayout *vLayoutMain = new QVBoxLayout; + vLayoutMain->addWidget(backgroundWidget); + vLayoutMain->addWidget(ui->tabWidget, 1); // 让tabWidget占据剩余空间 + + // 创建一个容器窗口并设置布局 + QWidget *container = new QWidget(this); + container->setLayout(vLayoutMain); + setCentralWidget(container); + + // 为每个tab页设置左上对齐的布局(跳过总览页面tab_1,因为它有自己的图表布局) + for (int i = 0; i < ui->tabWidget->count(); ++i) + { + QWidget *tab = ui->tabWidget->widget(i); + // 跳过总览页面(tab_1),它在disp_IIOT()中有自己的布局 + if (tab && tab != ui->tab_1) + { + // 获取tab内所有直接子控件 + QList children = tab->findChildren(QString(), Qt::FindDirectChildrenOnly); + + // 创建一个固定大小的容器来包含所有控件 + QWidget *contentWidget = new QWidget(tab); + contentWidget->setFixedSize(1300, 750); + + // 将所有子控件移到contentWidget中 + for (QWidget *child : children) + { + child->setParent(contentWidget); + } + + // 为tab设置布局,让contentWidget左上对齐 + QHBoxLayout *tabHLayout = new QHBoxLayout(tab); + tabHLayout->setContentsMargins(0, 0, 0, 0); + tabHLayout->addWidget(contentWidget, 0, Qt::AlignTop | Qt::AlignLeft); + tabHLayout->addStretch(); // 右边填充空白 + + QVBoxLayout *wrapperLayout = new QVBoxLayout(); + wrapperLayout->addLayout(tabHLayout); + wrapperLayout->addStretch(); // 下边填充空白 + + // 清除旧布局,设置新布局 + delete tab->layout(); + tab->setLayout(wrapperLayout); + } + } + + // 确保控件自适应大小 + container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + ui->label_16->setStyleSheet("QLabel { color : #02E5FF; }"); + + QMessageBox *msgBox = new QMessageBox(this); + msgBox->setIcon(QMessageBox::Warning); + msgBox->setWindowTitle("初始化"); + msgBox->setText("自动连接中..."); + msgBox->setStandardButtons(QMessageBox::NoButton); + + msgBox->show(); + QTimer::singleShot(1000, msgBox, &QMessageBox::accept); + QCoreApplication::processEvents(); + + ui->lcdNumber->setDigitCount(8); + ui->lcdNumber->display(888.888); + + // ui->label_bg->lower(); + + disp_canshu(); + + daily_date = DatabaseUtils::get_today_daliy_t(daily_cnt, daily_cnt_manual, + daily_qualified_cnt, daily_cnt_over, + daily_cnt_down, daily_cnt_max, + daily_cnt_min); + // qDebug() << "daily_date" <setConnectionMode(SerialPortManager::Auto); + bool serial_status = serialPortManager->connectSerialPort(); + if (serial_status) + { + // QMessageBox::information(this, "连接成功", "自动连接"); + ui->link_label->setText("已连接"); + ui->link_label->setStyleSheet("QLabel { color : green; }"); + ui->set_tx_lineEdit_1->setText("01"); + ui->set_tx_lineEdit_2->setText("9600"); + playConnState_ok(); + } + else + { + playConnState_no(); + QMessageBox::warning(this, "连接失败", "请手动连接"); + } + + connect(ui->link_button, &QPushButton::clicked, this, &MainWindow::onLinkButtonClicked); + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &MainWindow::auto_query); + + timer2 = new QTimer(this); + updateCharts(); + connect(timer2, &QTimer::timeout, this, &MainWindow::updateCharts); + timer2->start(1800); + + // connect(hidReader, &HIDReader::dataRead, this, &MainWindow::handleDataRead); + // hidReader->startReading(); + + this->setFocus(); + + connect(ui->xc_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->pwd_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->user_data_button, &QPushButton::clicked, this, &MainWindow::on_user_data_button_clicked_func); + connect(ui->name_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->auto_value_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->res_max_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->delete_1_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + connect(ui->delete_2_button, &QPushButton::clicked, this, &MainWindow::onShowPasswordDialog); + + int rowCount = ui->tableWidget->rowCount(); // 获取行数 + int columnCount = ui->tableWidget->columnCount(); // 获取列数 + + for (int row = 0; row < rowCount; ++row) + { + for (int col = 0; col < columnCount; ++col) + { + // 获取当前单元格的 item,如果不存在则创建一个 + QTableWidgetItem *item = ui->tableWidget->item(row, col); + if (!item) + { + item = new QTableWidgetItem(""); + ui->tableWidget->setItem(row, col, item); + } + // 设置单元格为禁用状态 + item->setFlags(Qt::NoItemFlags); + } + } + + ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // 禁用选择和焦点 + ui->tableWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui->tableWidget->setFocusPolicy(Qt::NoFocus); + + // 设置扫码输入框为只读,防止直接输入,只通过扫码填充 + ui->user_data_lineEdit_2->setReadOnly(true); + ui->user_data_lineEdit_3->setReadOnly(true); + ui->user_data_lineEdit_2->setFocusPolicy(Qt::NoFocus); + ui->user_data_lineEdit_3->setFocusPolicy(Qt::NoFocus); +} + +MainWindow::~MainWindow() +{ + daily_qualified_l = daily_qualified_cnt / (daily_cnt * 1.0); + DatabaseUtils::insertOrUpdateDailyData(daily_cnt, daily_cnt_manual, daily_qualified_cnt, daily_cnt_over, daily_cnt_down, daily_cnt_max, daily_cnt_min, daily_qualified_l); + DatabaseUtils::closeDatabase(); + // hidReader->stopReading(); + delete timer; + delete ui; +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); +} + +void MainWindow::handleDataRead(const QByteArray &data) +{ + QString text = QString::fromUtf8(data); + ui->user_data_lineEdit_2->setText(text); + // ui->textEdit->append(text); +} + +void MainWindow::on_link_button_clicked() +{ + serialConfigDialog->scanSerialPorts(); +} + +void MainWindow::onLinkButtonClicked() +{ + if (serialConfigDialog->exec() == QDialog::Accepted) + { + QString portName = serialConfigDialog->getSerialPort(); + int baudRate = serialConfigDialog->getBaudRate().toInt(); + int dataBits = serialConfigDialog->getDataBits().toInt(); + int stopBits = serialConfigDialog->getStopBits().toInt(); + int parity = serialConfigDialog->getParity().toInt(); + + serialPortManager->configureSerialPort(portName, baudRate, dataBits, stopBits, parity); + serialPortManager->setConnectionMode(SerialPortManager::Manual); + if (serialPortManager->connectSerialPort()) + { + serialPort = serialPortManager->getSerialPort(); + QMessageBox::information(this, "连接成功", "已成功连接到串口 " + serialPort->portName()); + ui->link_label->setText("已连接"); + ui->link_label->setStyleSheet("QLabel { color : green; }"); + ui->set_tx_lineEdit_1->setText("01"); + ui->set_tx_lineEdit_2->setText("9600"); + } + else + { + ui->link_label->setText("连接"); + ui->link_label->setStyleSheet("QLabel { color : black; }"); + QMessageBox::warning(this, "连接失败", "未找到合适的串口连接。"); + ui->set_tx_lineEdit_1->setText(""); + ui->set_tx_lineEdit_2->setText(""); + } + } +} + +void MainWindow::auto_query() +{ + if (auto_mode) + { + get_one_data(); + } +} + +void MainWindow::on_auto_button_clicked() +{ + if (auto_mode) + { + ui->auto_label->setText("自动关闭"); + auto_mode = false; + timer->stop(); + } + else + { + ui->auto_label->setText("自动开启"); + auto_mode = true; + get_one_data(); + timer->start(auto_time * 1000); + } +} + +void MainWindow::on_manual_button_clicked() +{ + // playQualified(); + if (auto_mode) + { + ui->auto_label->setText("自动关闭"); + auto_mode = false; + timer->stop(); + } + get_one_data(); +} + +void MainWindow::get_one_data() +{ + + ui->tishi_label->setText("获取中..."); + ui->tishi_label->setStyleSheet("QLabel { color : black; }"); + + double zz_disp = serialPortManager->sendModbus_receive("010400000001", 7); + // qDebug() << "zz_disp:" <tishi_label->setText("超出设备量程"); + ui->tishi_label->setStyleSheet("QLabel { color : red; }"); + ui->info_label->setText(""); + ui->lcdNumber->display("---.---"); + // qDebug() << "00000"; + return; + } + else if (zz_disp == -2) + { + // play_no_conn_res(); + ui->tishi_label->setText("未连接电阻"); + ui->tishi_label->setStyleSheet("QLabel { color : black; }"); + ui->info_label->setText(""); + ui->lcdNumber->display("---.---"); + // qDebug() << "00000"; + return; + } + else if (zz_disp == -1) + { + + ui->tishi_label->setText("获取异常"); + ui->tishi_label->setStyleSheet("QLabel { color : black; }"); + ui->info_label->setText(""); + ui->lcdNumber->display("---.---"); + // qDebug() << "00000"; + // if(auto_mode == false){ + null_cnt++; + if (null_cnt > 2) + { + // play_get_err(); + ui->info_label->setText("请检查连接"); + ui->info_label->setStyleSheet("QLabel { color : red; }"); + null_cnt = 0; + } + // } + return; + } + + // 检测两个扫码值是否都为空 + if (sm_num.isEmpty() || hot_num.isEmpty()) + { + if (scanTipLabel == nullptr) + { + scanTipLabel = new QLabel(this); + scanTipLabel->setText("编码缺失,请先扫码!"); + scanTipLabel->setStyleSheet( + "QLabel { " + "background-color: #FFFBE6; " + "border: 2px solid #FFD666; " + "border-radius: 8px; " + "padding: 15px 25px; " + "font-size: 16px; " + "font-weight: bold; " + "color: #D48806; " + "}"); + scanTipLabel->setAlignment(Qt::AlignCenter); + scanTipLabel->adjustSize(); + // 设置为不获取焦点 + scanTipLabel->setFocusPolicy(Qt::NoFocus); + scanTipLabel->setAttribute(Qt::WA_ShowWithoutActivating); + } + // 居中显示 + int x = (this->width() - scanTipLabel->width()) / 2; + int y = (this->height() - scanTipLabel->height()) / 2; + scanTipLabel->move(x, y); + scanTipLabel->show(); + scanTipLabel->raise(); + return; + } + + // qDebug() << "zz_disp:" <lcdNumber->display(zz_disp); + + if (daily_one) + { + daily_cnt_min = zz_disp; + daily_cnt_max = zz_disp; + daily_one = false; + } + if (auto_mode) + { + auto_mode_flag = "自动获取"; + } + else + { + auto_mode_flag = "手动获取"; + } + + if (zz_disp > max_res) + { + playUnqualified(); + daily_cnt_over++; + up_cnt++; + DatabaseUtils::update_Info_t("up_cnt", up_cnt); + if (DatabaseUtils::insert_ResData(chexing, sm_num, hot_num, zz_disp, detect, "异常:过大", auto_mode_flag, name)) + { + sm_num = ""; + hot_num = ""; + ui->user_data_lineEdit_2->clear(); + ui->user_data_lineEdit_3->clear(); + } + ui->info_label->setText("阻值过大"); + ui->info_label->setStyleSheet("QLabel { color : red; }"); + } + else if (zz_disp < min_res) + { + playUnqualified(); + down_cnt++; + daily_cnt_down++; + DatabaseUtils::update_Info_t("down_cnt", down_cnt); + if (DatabaseUtils::insert_ResData(chexing, sm_num, hot_num, zz_disp, detect, "异常:过小", auto_mode_flag, name)) + { + sm_num = ""; + hot_num = ""; + ui->user_data_lineEdit_2->clear(); + ui->user_data_lineEdit_3->clear(); + } + ui->info_label->setText("阻值过小"); + ui->info_label->setStyleSheet("QLabel { color : red; }"); + } + else + { + playQualified(); + qualified_cnt++; + daily_qualified_cnt++; + DatabaseUtils::update_Info_t("qualified_cnt", qualified_cnt); + if (DatabaseUtils::insert_ResData(chexing, sm_num, hot_num, zz_disp, detect, "合格", auto_mode_flag, name)) + { + sm_num = ""; + hot_num = ""; + ui->user_data_lineEdit_2->clear(); + ui->user_data_lineEdit_3->clear(); + } + ui->info_label->setText("阻值正常"); + ui->info_label->setStyleSheet("QLabel { color : green; }"); + } + + if (zz_disp > daily_cnt_max) + { + daily_cnt_max = zz_disp; + } + if (zz_disp < daily_cnt_min) + { + daily_cnt_min = zz_disp; + } + + all_count++; + manual_cnt++; + daily_cnt++; + daily_cnt_manual++; + + DatabaseUtils::update_Info_t("all_count", all_count); + DatabaseUtils::update_Info_t("manual_cnt", manual_cnt); + ui->tishi_label->setText(QString("今日检测:%1 ").arg(daily_cnt)); + ui->tishi_label->setStyleSheet("QLabel { color : green; }"); +} + +/** + 满量程对应值0103 1001 0002 + 零值对应值 1002 + + +输入满量程对应值 1001 +输入零值对应值 1002 +显示值的小数位数 1003 +J1报警模式 1004 +J1报警值或J1区间起点值 1005 +J1回差值或J1区间终点值 1006 +J2报警模式 1007 +J2报警值或J2区间起点值 1008 +J2回差值或J2区间终点值 1009 +变送输出类型 100A +输出高限对应值 100B +输出低限对应值 100C +/从机地址 100D +/通讯波特率 100E + +数字滤波系数 2000 +清零值/偏移值 2001 +测量线制 2005 +参数显示/隐藏 2006 + + + */ + +void MainWindow::onExportSucceeded() +{ + QMessageBox::information(this, tr("导出"), tr("导出表格成功")); +} + +void MainWindow::onExportFailed(const QString &error) +{ + QMessageBox::warning(this, tr("导出失败"), error); +} + +void MainWindow::on_excel_b_clicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save Excel File"), "", tr("Excel Files (*.xlsx *.xls)")); + if (fileName.isEmpty()) + { + return; + } + + ExportThread *thread = new ExportThread(fileName, all_count, auto_cnt, manual_cnt, qualified_cnt, up_cnt, down_cnt, this); + connect(thread, &ExportThread::exportSucceeded, this, &MainWindow::onExportSucceeded); + connect(thread, &ExportThread::exportFailed, this, &MainWindow::onExportFailed); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + + // if (totalRows == 0) { + // QMessageBox::warning(this, tr("Export to Excel"), tr("The table is empty. There is no data to export.")); + // return; + // } + + // QString fileName = QFileDialog::getSaveFileName(this, tr("Save Excel File"), "", tr("Excel Files (*.xlsx *.xls)")); + // if (fileName.isEmpty()) { + // qDebug() << "No file selected"; + // return; + // } + + // QMessageBox::information(this, tr("导出"), tr("导出表格中......")); + //// QMessageBox *msgBox = new QMessageBox(this); + //// msgBox->setIcon(QMessageBox::Warning); + //// msgBox->setWindowTitle("导出"); + //// msgBox->setText("导出表格中..."); + //// msgBox->setStandardButtons(QMessageBox::NoButton); + //// msgBox->show(); + //// QTimer::singleShot(1000, msgBox, &QMessageBox::accept); + //// QCoreApplication::processEvents(); + + // QVector allData = DatabaseUtils::getAllData(); + + // if (allData.isEmpty()) { + // qDebug() << "No data retrieved from database"; + // return; + // } + + // QAxObject *excel = new QAxObject("Excel.Application", this); + // if (!excel->isNull()) { + // excel->setProperty("Visible", false); + // } else { + // qDebug() << "Failed to create Excel application"; + // QMessageBox::warning(this, tr("Excel Error"), tr("Failed to create Excel application.")); + // return; + // } + + // QAxObject *workbooks = excel->querySubObject("Workbooks"); + // QAxObject *workbook = workbooks->querySubObject("Add"); + // QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1); + + // int columnCount = allData.first().size(); + + // for (int col = 0; col < columnCount; ++col) { + // QString columnName = QString("Column %1").arg(col + 1); + // QAxObject *cell = worksheet->querySubObject("Cells(int,int)", 1, col + 1); + // cell->setProperty("Value", columnName); + // } + + // for (int row = 0; row < allData.size(); ++row) { + // const QVariantList &rowData = allData[row]; + // for (int col = 0; col < rowData.size(); ++col) { + // QAxObject *cell = worksheet->querySubObject("Cells(int,int)", row + 2, col + 1); + // QVariant value = rowData[col]; + // cell->setProperty("Value2", value); + + //// qDebug() << "Writing to Excel: Row" << row + 2 << "Col" << col + 1 << value; + // } + // } + + // workbook->dynamicCall("SaveAs(const QString&)", QDir::toNativeSeparators(fileName)); + // workbook->dynamicCall("Close()"); + // excel->dynamicCall("Quit()"); + + // delete excel; + // QMessageBox::information(this, tr("导出"), tr("导出表格成功")); +} + +void MainWindow::on_user_data_button_clicked_func() +{ + QString userInput1 = ui->user_data_lineEdit_1->text(); + bool save_flag = DatabaseUtils::update_Info_t("chexing", userInput1); + chexing = userInput1; + + if (save_flag == false) + { + QMessageBox::warning(this, "保存失败", "请重新设置"); + } + else + { + QMessageBox::warning(this, "保存成功", "已修改"); + } +} + +void MainWindow::on_name_button_clicked_func() +{ + QString userInput1 = ui->name_lineEdit->text(); + + bool save_flag = DatabaseUtils::update_Info_t("name", userInput1); + name = userInput1; + + if (save_flag == false) + { + QMessageBox::warning(this, "保存失败", "请重新设置"); + } + else + { + QMessageBox::warning(this, "保存成功", "已修改"); + } +} + +void MainWindow::on_res_max_button_clicked_func() +{ + QString userInput1 = ui->res_max_lineEdit_1->text(); + QString userInput2 = ui->res_max_lineEdit_2->text(); + + double res = userInput1.toDouble(); + if (res >= 10) + { + QMessageBox::warning(this, "保存失败", "输入值不在范围"); + return; + } + double sd = userInput2.toDouble(); + double sd1 = sd; + + sd = res * (sd / 100); + max_res = res + sd; + if (max_res >= 10) + { + QMessageBox::warning(this, "保存失败", "最大值无效"); + return; + } + min_res = res - sd; + if (min_res < 0) + { + QMessageBox::warning(this, "保存失败", "最小值无效"); + return; + } + // qDebug()<<"res" << res; + // qDebug()<<"sd" << sd; + // qDebug()<<"max_res" << max_res; + // qDebug()<<"min_res" << min_res; + + detect = QString("%1Ω ±%2%").arg(res).arg(sd1); // 使用sd1(原始百分比值)而不是sd(计算后的值) + + bool save_flag = DatabaseUtils::update_Info_t("res_value", res); + save_flag = DatabaseUtils::update_Info_t("res_sd", sd1); + + save_flag = DatabaseUtils::update_Info_t("max_res", max_res); + save_flag = DatabaseUtils::update_Info_t("min_res", min_res); + + if (save_flag == false) + { + QMessageBox::warning(this, "保存失败", "请重新设置"); + } + else + { + QMessageBox::warning(this, "保存成功", "已修改"); + } +} + +void MainWindow::on_auto_value_button_clicked_func() +{ + QString userInput1 = ui->auto_value_lineEdit_1->text(); + bool save_flag; + if (userInput1.toInt() < 1) + { + // save_flag = DatabaseUtils::update_Info_t("auto_time",10); + save_flag = false; + } + else + { + auto_time = userInput1.toInt(); + save_flag = DatabaseUtils::update_Info_t("auto_time", auto_time); + ui->auto_value_lineEdit_1->setText(QString::number(auto_time)); + } + + if (save_flag == false) + { + QMessageBox::warning(this, "保存失败", "输入值不在范围"); + } + else + { + QMessageBox::warning(this, "保存成功", "已修改"); + } +} + +// void MainWindow::on_set_tx_button_clicked() +//{ + +// QString userInput1 = ui->set_tx_lineEdit_1->text(); +// QString userInput2 = ui->set_tx_lineEdit_2->text(); + +//} + +void delay(int milliseconds) +{ + QEventLoop loop; + QTimer::singleShot(milliseconds, &loop, SLOT(quit())); + loop.exec(); +} + +void MainWindow::on_xc_button_clicked_func() +{ + if (auto_mode) + { + ui->auto_label->setText("自动关闭"); + auto_mode = false; + timer->stop(); + } + QString userInput1 = ui->xc_lineEdit->text(); + bool ok; + xc_sd = userInput1.toDouble(&ok); + + if (ok == false) + { + QString warningMessage = QString("字符转换问题:非数字").arg(xc_sd); + QMessageBox::warning(this, "保存失败", warningMessage); + return; + } + + if (xc_sd < 3 && ok && xc_sd >= 0) + { + ok = DatabaseUtils::update_Info_t("xc_sd", xc_sd); + if (ok == false) + { + QString warningMessage = QString("数据库问题:无法保存").arg(xc_sd); + QMessageBox::warning(this, "保存失败", warningMessage); + return; + } + } + else + { + ok = false; + QString warningMessage = QString("输入值 %1 不在范围").arg(xc_sd); + QMessageBox::warning(this, "保存失败", warningMessage); + return; + } + + if (ok == true) + { + QString warningMessage = QString("已修改 %1 ").arg(xc_sd); + QMessageBox::warning(this, "保存成功", warningMessage); + } +} + +// void MainWindow::on_bj_2_button_clicked() +//{ +// if(auto_mode){ +// ui->auto_label->setText("自动关闭"); +// auto_mode = false; +// timer->stop(); +// } +// QString userInput1 = ui->bj_2_lineEdit_0->text(); +// QString userInput2 = ui->bj_2_lineEdit_1->text(); +// QString userInput3 = ui->bj_2_lineEdit_2->text(); + +// QString command1; +// if (userInput1 == "0") { +// command1 = "010610070000"; +// } else if (userInput1 == "1") { +// command1 = "010610070001"; +// } else if (userInput1 == "2") { +// command1 = "010610070002"; +// } else if (userInput1 == "3") { +// command1 = "010610070003"; +// } else if (userInput1 == "4") { +// command1 = "010610070004"; +// } else { +// ui->bj_2_lineEdit_0->setText("输入无效: 请输入0到4之间的数字"); +// ui->bj_2_lineEdit_0->setStyleSheet("QLabel { color : red; }"); +// return; +// } +// bool rec_flag = serialPortManager->sendModbus_receive_2(command1, 8); +// delay(10); + +// bool ok; +// int value2 = userInput2.toInt(&ok); +// if (!ok) { +// ui->bj_2_lineEdit_1->setText("输入无效: 请输入有效的数字"); +// ui->bj_2_lineEdit_1->setStyleSheet("QLabel { color : red; }"); +// return; +// } + +// QString hexValue2 = QString("%1").arg(value2, 4, 16, QLatin1Char('0')).toUpper(); +// QString command2 = "01061008" + hexValue2; +// rec_flag = serialPortManager->sendModbus_receive_2(command2, 8); +// delay(10); + +// int value3 = userInput3.toInt(&ok); +// if (!ok) { +// ui->bj_2_lineEdit_2->setText("输入无效: 请输入有效的数字"); +// ui->bj_2_lineEdit_2->setStyleSheet("QLabel { color : red; }"); +// return; +// } +// QString hexValue3 = QString("%1").arg(value3, 4, 16, QLatin1Char('0')).toUpper(); +// QString command3 = "01061009" + hexValue3; +// rec_flag = serialPortManager->sendModbus_receive_2(command3, 8); +// if(rec_flag == false){ +// QMessageBox::warning(this, "设置失败", "请重新设置"); +// } else { +// QMessageBox::warning(this, "设置成功", "已修改"); +// } +// } + +void MainWindow::get_device_bj_info() +{ + + // serialPortManager->sendModbus_receive_2("010310040002", 9); + // ui->bj_1_lineEdit_0->setText(""); + // serialPortManager->sendModbus_receive_2("010310050002", 9); + // ui->bj_1_lineEdit_1->setText(""); + // serialPortManager->sendModbus_receive_2("010310060002", 9); + // ui->bj_1_lineEdit_2->setText(""); + // serialPortManager->sendModbus_receive_2("010310070002", 9); + + // ui->bj_2_lineEdit_0->setText(""); + // serialPortManager->sendModbus_receive_2("010310080002", 9); + // ui->bj_2_lineEdit_1->setText(""); + // serialPortManager->sendModbus_receive_2("010310090002", 9); + // ui->bj_2_lineEdit_2->setText(""); +} + +void MainWindow::on_nextPageButton_clicked() +{ + + totalRows = DatabaseUtils::getResTotalRows(); + + uppage = false; + + if ((currentPage + 1) * rowsPerPage < totalRows) + { + currentPage++; + } + else + { + currentPage = 0; // 第一页 + } + // qDebug() << "totalRows:" << totalRows; + // qDebug() << "Next page:" << currentPage; + updateTableWidget(DatabaseUtils::getData(rowsPerPage, currentPage * rowsPerPage)); +} + +void MainWindow::on_prevPageButton_clicked() +{ + + totalRows = DatabaseUtils::getResTotalRows(); + + uppage = false; + + if (currentPage > 0) + { + currentPage--; + } + else + { + currentPage = (totalRows + rowsPerPage - 1) / rowsPerPage - 1; // 最后一页 + } + // qDebug() << "totalRows:" << totalRows; + // qDebug() << "Previous page:" << currentPage; + updateTableWidget(DatabaseUtils::getData(rowsPerPage, currentPage * rowsPerPage)); +} + +void MainWindow::updateTableWidget(const QVector &data) +{ + ui->tableWidget->setRowCount(rowsPerPage); + + for (int row = 0; row < data.size(); ++row) + { + const QVariantList &rowData = data[row]; + for (int col = 0; col < rowData.size(); ++col) + { + QTableWidgetItem *item = new QTableWidgetItem(rowData[col].toString()); + item->setFlags(Qt::NoItemFlags); + item->setTextAlignment(Qt::AlignCenter); // 文字居中 + ui->tableWidget->setItem(row, col, item); + } + } + + for (int i = data.size(); i < rowsPerPage; ++i) + { + for (int col = 0; col < ui->tableWidget->columnCount(); ++col) + { + ui->tableWidget->setItem(i, col, new QTableWidgetItem("")); + } + } + // ui->user_data_lineEdit_2->setFocus(); + // ui->tableWidget->setSelectionMode(QAbstractItemView::NoSelection); +} + +void MainWindow::updateCharts() +{ + + if (uppage) + { + totalRows = DatabaseUtils::getResTotalRows(); + + if (totalRows > old_totalRows) + { + int cnt = (totalRows + rowsPerPage - 1) / rowsPerPage; + for (int i = currentPage; i < cnt - 1; i++) + { + on_nextPageButton_clicked(); + } + updateTableWidget(DatabaseUtils::getData(rowsPerPage, currentPage * rowsPerPage)); + } + old_totalRows = totalRows; + } + else + { + uppage_cnt++; + if (uppage_cnt >= 3) + { + uppage = true; + } + } + + if (daily_date != nullptr) + { + QString currentDate = QDate::currentDate().toString("yyyy-MM-dd"); + // qDebug() << "daily_date" <replace(0, 0); + set21->replace(1, 0); + set22->replace(2, 0); + axisX2->setRange(0, std::max({daily_qualified_cnt, daily_cnt_over + daily_cnt_down, daily_cnt})); + + daily_date = DatabaseUtils::get_today_daliy_t(daily_cnt, daily_cnt_manual, + daily_qualified_cnt, daily_cnt_over, + daily_cnt_down, daily_cnt_max, + daily_cnt_min); + // qDebug() << "daily_cnt" <(all_count)) * 100 : 0; + double unqualified_percentage = all_count != 0 ? ((up_cnt + down_cnt) / static_cast(all_count)) * 100 : 0; + + QList slices = pieSeries->slices(); + + slices[0]->setLabel(QString("%1").arg(all_count)); + // slices[0]->setLabel(QString("%1 (%2)").arg("总检验次数").arg(all_count)); + slices[1]->setLabel(QString("%1% (%2)").arg(qualified_percentage, 0, 'f', 1).arg(qualified_cnt)); + slices[2]->setLabel(QString("%1% (%2)").arg(unqualified_percentage, 0, 'f', 1).arg((up_cnt + down_cnt))); + + set20->replace(0, daily_qualified_cnt); + set21->replace(1, daily_cnt_over + daily_cnt_down); + set22->replace(2, daily_cnt); + + bool allZero = (daily_qualified_cnt == 0) && (daily_cnt_over + daily_cnt_down == 0) && (daily_cnt == 0); + + if (allZero) + { + axisX2->setRange(0, 1); + // axisX2->setTickCount(3); + } + else + { + int maxValue = std::max({daily_qualified_cnt, daily_cnt_over + daily_cnt_down, daily_cnt}); + axisX2->setRange(0, maxValue); + axisX2->applyNiceNumbers(); + } + // axisX2->setRange(0, std::max({daily_qualified_cnt, daily_cnt_over+daily_cnt_down, daily_cnt})); +} + +void MainWindow::disp_IIOT() +{ + QList dataList = DatabaseUtils::getLastFiveDailyDataSkippingToday(); + const int rowCount = 5; + int dailyInspectionCounts[rowCount] = {0}; + int manualInspectionCounts[rowCount] = {0}; + int qualifieds[rowCount] = {0}; + int overLimits[rowCount] = {0}; + int underLimits[rowCount] = {0}; + double maxValues[rowCount] = {0.0}; + double minValues[rowCount] = {0.0}; + double qualificationRates[rowCount] = {0.0}; + QString dates[rowCount]; + + int rowIndex = 0; + for (const QVariantList &row : dataList) + { + if (rowIndex >= rowCount) + break; + dailyInspectionCounts[rowIndex] = row[1].toInt(); + manualInspectionCounts[rowIndex] = row[2].toInt(); + qualifieds[rowIndex] = row[3].toInt(); + overLimits[rowIndex] = row[4].toInt(); + underLimits[rowIndex] = row[5].toInt(); + maxValues[rowIndex] = row[6].toDouble(); + minValues[rowIndex] = row[7].toDouble(); + qualificationRates[rowIndex] = row[8].toDouble(); + dates[rowIndex] = row[9].toString(); + rowIndex++; + } + // 日检验次数 手动检测次数 合格 过大 过小 最大值 最小值 合格率 日期 + + // for (int i = 0; i < rowCount; ++i) { + // qDebug() << "Daily Inspection Count:" << dailyInspectionCounts[i] + // << "Manual Inspection Count:" << manualInspectionCounts[i] + // << "Qualified:" << qualifieds[i] + // << "Over Limit:" << overLimits[i] + // << "Under Limit:" << underLimits[i] + // << "Max Value:" << maxValues[i] + // << "Min Value:" << minValues[i] + // << "Qualification Rate:" << qualificationRates[i] + // << "Date:" << dates[i]; + // } + + QColor backgroundColor("#1A1445"); + QColor big_textColor(Qt::white); + QColor textColor("#CCCCCC"); + QColor lineColor("#7D7D7D"); + QColor barColor("#02E5FF"); + + QFont boldFont; + boldFont.setBold(true); + + // 折线图 + QLineSeries *lineSeries = new QLineSeries(); + lineSeries->append(1, qualificationRates[4]); + lineSeries->append(2, qualificationRates[3]); + lineSeries->append(3, qualificationRates[2]); + lineSeries->append(4, qualificationRates[1]); + lineSeries->append(5, qualificationRates[0]); + QChart *lineChart = new QChart(); + lineChart->addSeries(lineSeries); + lineChart->setTitle("近五天合格率"); + lineChart->setTitleFont(boldFont); + lineChart->setBackgroundBrush(QBrush(backgroundColor)); + lineChart->setTitleBrush(QBrush(big_textColor)); + QChartView *lineChartView = new QChartView(lineChart); + lineChartView->setRenderHint(QPainter::Antialiasing); + + QValueAxis *axisX = new QValueAxis(); + QValueAxis *axisY = new QValueAxis(); + axisX->setTitleText("天"); + axisY->setTitleText("合格率"); + axisX->setLabelsBrush(QBrush(lineColor)); + axisY->setLabelsBrush(QBrush(lineColor)); + axisX->setTitleBrush(QBrush(barColor)); + axisY->setTitleBrush(QBrush(barColor)); + axisX->setLinePen(QPen(lineColor)); + axisY->setLinePen(QPen(lineColor)); + axisY->setRange(0, 1.0); + axisY->setTickCount(5); + lineChart->addAxis(axisX, Qt::AlignBottom); + lineChart->addAxis(axisY, Qt::AlignLeft); + lineSeries->attachAxis(axisX); + lineSeries->attachAxis(axisY); + + // 柱状图 + QBarSet *set2 = new QBarSet("超出区间"); + QBarSet *set4 = new QBarSet("小于区间"); + *set2 << maxValues[4] << maxValues[3] << maxValues[2] << maxValues[1] << maxValues[0]; + *set4 << minValues[4] << minValues[3] << minValues[2] << minValues[1] << minValues[0]; + set2->setColor(QColor("#FF5733")); + set4->setColor(QColor("#FFBD33")); + + bool allZero = true; + for (int i = 0; i < 5; ++i) + { + if (maxValues[i] != 0 || minValues[i] != 0) + { + allZero = false; + break; + } + } + QBarSeries *barSeries = new QBarSeries(); + barSeries->append(set2); + barSeries->append(set4); + QChart *barChart = new QChart(); + barChart->addSeries(barSeries); + barChart->setTitle("近五天最值"); + barChart->setTitleFont(boldFont); + barChart->setBackgroundBrush(QBrush(backgroundColor)); + barChart->setTitleBrush(QBrush(big_textColor)); + QChartView *barChartView1 = new QChartView(barChart); + barChartView1->setRenderHint(QPainter::Antialiasing); + + QLegend *legend = barChart->legend(); + for (int i = 0; i < legend->markers().size(); ++i) + { + QLegendMarker *marker = legend->markers().at(i); + marker->setLabelBrush(QBrush(textColor)); + } + + QBarCategoryAxis *axisX1 = new QBarCategoryAxis(); + axisX1->append(QStringList() << "1" << "2" << "3" << "4" << "5"); + axisX1->setTitleText("条数"); + axisX1->setLabelsBrush(QBrush(lineColor)); + axisX1->setTitleBrush(QBrush(barColor)); + axisX1->setLinePen(QPen(lineColor)); + QValueAxis *axisY11 = new QValueAxis(); + axisY11->setTitleText("最值"); + axisY11->setLabelsBrush(QBrush(lineColor)); + axisY11->setTitleBrush(QBrush(barColor)); + axisY11->setLinePen(QPen(lineColor)); + if (allZero) + { + axisY11->setRange(0, 5); + } + else + { + double maxValue = *std::max_element(maxValues, maxValues + 5); + double minValue = *std::min_element(minValues, minValues + 5); + axisY11->setRange(minValue, maxValue); + } + barChart->addAxis(axisX1, Qt::AlignBottom); + barChart->addAxis(axisY11, Qt::AlignLeft); + barSeries->attachAxis(axisX1); + barSeries->attachAxis(axisY11); + + // 柱状图 + QBarSet *set10 = new QBarSet("合格"); + QBarSet *set11 = new QBarSet("大于"); + QBarSet *set12 = new QBarSet("小于"); + *set10 << qualifieds[4] << qualifieds[3] << qualifieds[2] << qualifieds[1] << qualifieds[0]; + *set11 << overLimits[4] << overLimits[3] << overLimits[2] << overLimits[1] << overLimits[0]; + *set12 << underLimits[4] << underLimits[3] << underLimits[2] << underLimits[1] << underLimits[0]; + + allZero = true; + for (int i = 0; i < 5; ++i) + { + if (qualifieds[i] != 0 || overLimits[i] != 0 || underLimits[i] != 0) + { + allZero = false; + break; + } + } + set10->setColor(QColor("#33FF57")); + set11->setColor(QColor("#FF5733")); + set12->setColor(QColor("#FFBD33")); + + QBarSeries *barSeries2 = new QBarSeries(); + barSeries2->append(set11); + barSeries2->append(set10); + barSeries2->append(set12); + QChart *barChart2 = new QChart(); + barChart2->addSeries(barSeries2); + barChart2->setTitle("近五天检验值"); + barChart2->setTitleFont(boldFont); + barChart2->setBackgroundBrush(QBrush(backgroundColor)); + barChart2->setTitleBrush(QBrush(big_textColor)); + QChartView *barChartView2 = new QChartView(barChart2); + barChartView2->setRenderHint(QPainter::Antialiasing); + + legend = barChart2->legend(); + for (int i = 0; i < legend->markers().size(); ++i) + { + QLegendMarker *marker = legend->markers().at(i); + marker->setLabelBrush(QBrush(textColor)); + } + + axisX1 = new QBarCategoryAxis(); + axisX1->append(QStringList() << "1" << "2" << "3" << "4" << "5"); + axisX1->setTitleText("天"); + axisX1->setLabelsBrush(QBrush(lineColor)); + axisX1->setTitleBrush(QBrush(barColor)); + axisX1->setLinePen(QPen(lineColor)); + axisY = new QValueAxis(); + axisY->setTitleText("检验值"); + axisY->setLabelsBrush(QBrush(lineColor)); + axisY->setTitleBrush(QBrush(barColor)); + axisY->setLinePen(QPen(lineColor)); + if (allZero) + { + axisY->setRange(0, 5); + } + barChart2->addAxis(axisX1, Qt::AlignBottom); + barChart2->addAxis(axisY, Qt::AlignLeft); + barSeries2->attachAxis(axisX); + barSeries2->attachAxis(axisY); + + // 饼状图 + pieSeries = new QPieSeries(); + pieSeries->append("总检验次数", 3); + pieSeries->append("良品", 2); + pieSeries->append("不合格", 1); + + QList slices = pieSeries->slices(); + + slices[0]->setColor(QColor("#02C1D7")); + slices[0]->setBorderColor(QColor("#1A1445")); + slices[0]->setBorderWidth(2); + slices[0]->setLabel(QString("%1 (%2)").arg(slices[0]->label()).arg(all_count)); + slices[0]->setLabelVisible(true); + slices[0]->setLabelBrush(QBrush(QColor(barColor))); + + slices[1]->setColor(QColor("#90EE90")); + slices[1]->setBorderColor(QColor("#1A1445")); + slices[1]->setBorderWidth(2); + double qualified_percentage = all_count != 0 ? (qualified_cnt / static_cast(all_count)) * 100 : 0; + slices[1]->setLabel(QString("%1% (%2)").arg(qualified_percentage, 0, 'f', 1).arg(qualified_cnt)); + slices[1]->setLabelVisible(true); + slices[1]->setLabelBrush(QBrush(QColor(barColor))); + + slices[2]->setColor(QColor("#FF6347")); + slices[2]->setBorderColor(QColor("#1A1445")); + slices[2]->setBorderWidth(2); + double unqualified_percentage = all_count != 0 ? ((up_cnt + down_cnt) / static_cast(all_count)) * 100 : 0; + slices[2]->setLabel(QString("%1% (%2)").arg(unqualified_percentage, 0, 'f', 1).arg((up_cnt + down_cnt))); + slices[2]->setLabelVisible(true); + slices[2]->setLabelBrush(QBrush(QColor(barColor))); + + QChart *pieChart = new QChart(); + pieChart->addSeries(pieSeries); + pieChart->setTitle("检验总览"); + pieChart->setTitleFont(boldFont); + pieChart->setBackgroundBrush(QBrush(backgroundColor)); + pieChart->setTitleBrush(QBrush(big_textColor)); + QChartView *pieChartView = new QChartView(pieChart); + pieChartView->setRenderHint(QPainter::Antialiasing); + + legend = pieChart->legend(); + for (int i = 0; i < legend->markers().size(); ++i) + { + QLegendMarker *marker = legend->markers().at(i); + if (i == 0) + { + marker->setLabel("总检验次数"); + } + else if (i == 1) + { + marker->setLabel("良品"); + } + else if (i == 2) + { + marker->setLabel("不合格"); + } + marker->setLabelBrush(QBrush(textColor)); + } + + // 条形图 + set20 = new QBarSet("良品"); + set21 = new QBarSet("不合格"); + set22 = new QBarSet("日检验次"); + + *set20 << 0 << 0 << 0; + *set21 << 0 << 0 << 0; + *set22 << 0 << 0 << 0; + + set20->setColor(QColor("#33FF57")); + set21->setColor(QColor("#FF5733")); + set22->setColor(QColor(barColor)); + + QHorizontalBarSeries *hBarSeries = new QHorizontalBarSeries(); + hBarSeries->append(set20); + hBarSeries->append(set21); + hBarSeries->append(set22); + + hBarSeries->setLabelsVisible(true); + hBarSeries->setLabelsFormat("@value"); + QFont labelFont; + labelFont.setPointSize(12); + labelFont.setBold(true); + set20->setLabelFont(labelFont); + set21->setLabelFont(labelFont); + set22->setLabelFont(labelFont); + QBrush labelBrush(QColor("#FFFFFF")); + set20->setLabelBrush(labelBrush); + set21->setLabelBrush(labelBrush); + set22->setLabelBrush(labelBrush); + + QChart *hBarChart = new QChart(); + hBarChart->addSeries(hBarSeries); + hBarChart->setTitle("今日检验情况"); + hBarChart->setTitleFont(boldFont); + hBarChart->setBackgroundBrush(QBrush(backgroundColor)); + hBarChart->setTitleBrush(QBrush(big_textColor)); + + QChartView *hBarChartView = new QChartView(hBarChart); + hBarChartView->setRenderHint(QPainter::Antialiasing); + + legend = hBarChart->legend(); + for (int i = 0; i < legend->markers().size(); ++i) + { + QLegendMarker *marker = legend->markers().at(i); + marker->setLabelBrush(QBrush(textColor)); + } + + axisX2 = new QValueAxis(); + axisX2->setTitleText("数量"); + axisX2->setLabelsBrush(QBrush(lineColor)); + axisX2->setTitleBrush(QBrush(barColor)); + axisX2->setLinePen(QPen(lineColor)); + allZero = true; + for (int i = 0; i < 3; ++i) + { + if ((*set20)[i] != 0 || (*set21)[i] != 0 || (*set22)[i] != 0) + { + allZero = false; + break; + } + } + if (allZero) + { + axisX2->setRange(0, 3); + axisX2->setTickCount(3); + } + hBarChart->addAxis(axisX2, Qt::AlignBottom); + hBarSeries->attachAxis(axisX2); + QCategoryAxis *axisY1 = new QCategoryAxis(); + axisY1->append(" ", 1); + axisY1->append(" ", 2); + axisY1->setLabelsBrush(QBrush(lineColor)); + axisY1->setLinePen(QPen(lineColor)); + // axisY1->setGridLineVisible(false); + hBarChart->addAxis(axisY1, Qt::AlignLeft); + hBarSeries->attachAxis(axisY1); + + // 曲线图 + QSplineSeries *splineSeries = new QSplineSeries(); + splineSeries->append(1, manualInspectionCounts[4]); + splineSeries->append(2, manualInspectionCounts[3]); + splineSeries->append(3, manualInspectionCounts[2]); + splineSeries->append(4, manualInspectionCounts[1]); + splineSeries->append(5, manualInspectionCounts[0]); + + QChart *splineChart = new QChart(); + splineChart->addSeries(splineSeries); + splineChart->setTitle("近五天手动检测次数"); + splineChart->setTitleFont(boldFont); + splineChart->setBackgroundBrush(QBrush(backgroundColor)); + splineChart->setTitleBrush(QBrush(big_textColor)); + QChartView *splineChartView = new QChartView(splineChart); + splineChartView->setRenderHint(QPainter::Antialiasing); + + axisX = new QValueAxis(); + axisY = new QValueAxis(); + axisX->setTitleText("天"); + axisY->setTitleText("次数"); + axisX->setLabelsBrush(QBrush(lineColor)); + axisY->setLabelsBrush(QBrush(lineColor)); + axisX->setTitleBrush(QBrush(barColor)); + axisY->setTitleBrush(QBrush(barColor)); + axisX->setLinePen(QPen(lineColor)); + axisY->setLinePen(QPen(lineColor)); + allZero = true; + for (int i = 0; i < 5; ++i) + { + if (manualInspectionCounts[i] != 0) + { + allZero = false; + break; + } + } + + if (allZero) + { + axisY->setRange(0, 5); + } + splineChart->addAxis(axisX, Qt::AlignBottom); + splineChart->addAxis(axisY, Qt::AlignLeft); + splineSeries->attachAxis(axisX); + splineSeries->attachAxis(axisY); + + QHBoxLayout *topLayout = new QHBoxLayout(); + topLayout->addWidget(barChartView1); + topLayout->addWidget(pieChartView); + topLayout->addWidget(barChartView2); + QHBoxLayout *bottomLayout = new QHBoxLayout(); + bottomLayout->addWidget(lineChartView); + bottomLayout->addWidget(hBarChartView); + bottomLayout->addWidget(splineChartView); + QVBoxLayout *mainLayout = new QVBoxLayout(ui->tab_1); + mainLayout->addLayout(topLayout); + mainLayout->addLayout(bottomLayout); +} + +void MainWindow::disp_canshu() +{ + QPalette nextButtonPalette = ui->nextPageButton->palette(); + nextButtonPalette.setColor(QPalette::ButtonText, Qt::white); + ui->nextPageButton->setPalette(nextButtonPalette); + ui->prevPageButton->setPalette(nextButtonPalette); + + // 设置表格列宽自适应,最后一列(日期)设置为适应内容 + ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + // 最后一列设置为适应内容,确保日期显示完整 + int lastCol = ui->tableWidget->columnCount() - 1; + ui->tableWidget->horizontalHeader()->setSectionResizeMode(lastCol, QHeaderView::ResizeToContents); + ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + // for (int row = 0; row < 16; ++row) { + // for (int col = 0; col < 4; ++col) { + // QTableWidgetItem *item = new QTableWidgetItem(QString("Row %1, Col %2").arg(row + 1).arg(col + 1)); + // ui->tableWidget->setItem(row, col, item); + // } + // } + + // DatabaseUtils::insertTestData(100); + + totalRows = DatabaseUtils::getResTotalRows(); + // qDebug() << "Total rows:" << totalRows; + updateTableWidget(DatabaseUtils::getData(rowsPerPage, currentPage * rowsPerPage)); + + QMap infoData = DatabaseUtils::read_all_Info_t(); + // for (auto it = infoData.begin(); it != infoData.end(); ++it) { + // qDebug() << it.key() << ":" << it.value(); + // } + all_count = infoData.value("all_count").toInt(); + auto_cnt = infoData.value("auto_cnt").toInt(); + manual_cnt = infoData.value("manual_cnt").toInt(); + qualified_cnt = infoData.value("qualified_cnt").toInt(); + up_cnt = infoData.value("up_cnt").toInt(); + down_cnt = infoData.value("down_cnt").toInt(); + res_value = infoData.value("res_value").toDouble(); + res_sd = infoData.value("res_sd").toDouble(); + max_res = infoData.value("max_res").toDouble(); + min_res = infoData.value("min_res").toDouble(); + xc_sd = infoData.value("xc_sd").toDouble(); + auto_time = infoData.value("auto_time").toInt(); + name = infoData.value("name").toString(); + chexing = infoData.value("chexing").toString(); + pwd = infoData.value("pwd").toString(); + + // qDebug() << "all_count" << all_count; + + detect = QString("%1Ω ±%2%").arg(res_value).arg(res_sd); + // qDebug() << "Detect string:" << detect; + + ui->user_data_lineEdit_1->setText(chexing); + ui->name_lineEdit->setText(name); + ui->res_max_lineEdit_1->setText(QString::number(res_value)); + ui->res_max_lineEdit_2->setText(QString::number(res_sd)); + ui->auto_value_lineEdit_1->setText(QString::number(auto_time)); + ui->xc_lineEdit->setText(QString::number(xc_sd)); + // ui->set_tx_lineEdit_1->text(); + // ui->bj_1_lineEdit_0->text(); +} + +void MainWindow::on_pushButton_2_clicked() +{ + QString filePath = QFileDialog::getSaveFileName(this, tr("Save PDF"), "", tr("PDF Files (*.pdf)")); + + if (!filePath.isEmpty()) + { + QFile resourceFile(":/res/user.pdf"); + if (resourceFile.open(QIODevice::ReadOnly)) + { + + QFile outputFile(filePath); + if (outputFile.open(QIODevice::WriteOnly)) + { + outputFile.write(resourceFile.readAll()); + outputFile.close(); + + QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); + } + else + { + QMessageBox::warning(this, tr("Error"), tr("Cannot open output file for writing.")); + } + resourceFile.close(); + } + else + { + QMessageBox::warning(this, tr("Error"), tr("Cannot open resource file.")); + } + } +} + +void MainWindow::playQualified() +{ + player->setMedia(QUrl("qrc:/res/ok.wav")); + player->setVolume(100); + player->play(); +} + +void MainWindow::playUnqualified() +{ + player->setMedia(QUrl("qrc:/res/no.wav")); + player->setVolume(100); + player->play(); +} + +void MainWindow::playConnState_ok() +{ + player->setMedia(QUrl("qrc:/res/auto_conn_ok.wav")); + player->setVolume(100); + player->play(); +} +void MainWindow::playConnState_no() +{ + player->setMedia(QUrl("qrc:/res/auto_conn_no.wav")); + player->setVolume(100); + player->play(); +} + +void MainWindow::play_get_err() +{ + player->setMedia(QUrl("qrc:/res/get_err.wav")); + player->setVolume(100); + player->play(); +} + +void MainWindow::play_no_conn_res() +{ + player->setMedia(QUrl("qrc:/res/no_conn_res.wav")); + player->setVolume(100); + player->play(); +} + +void MainWindow::dispscan(const QString barcode) +{ + // 有扫码数据时关闭提示标签 + if (scanTipLabel != nullptr) + { + scanTipLabel->close(); + scanTipLabel->deleteLater(); + scanTipLabel = nullptr; + } + + // 扫码逻辑: + // 1. 如果两个框都有值,说明开始新一轮,先清空再从护面开始 + // 2. 如果护面框为空,输入到护面框 + // 3. 如果护面框有值但加热框为空,输入到加热框 + + bool sm_has_value = !ui->user_data_lineEdit_2->text().isEmpty(); + bool hot_has_value = !ui->user_data_lineEdit_3->text().isEmpty(); + + if (sm_has_value && hot_has_value) + { + // 两个框都有值,开始新一轮,先清空 + ui->user_data_lineEdit_2->clear(); + ui->user_data_lineEdit_3->clear(); + sm_num = ""; + hot_num = ""; + // 然后输入到护面框 + sm_num = barcode; + ui->user_data_lineEdit_2->setText(barcode); + ui->tishi_label->setText("护面扫码成功"); + } + else if (!sm_has_value) + { + // 护面框为空,输入到护面框 + sm_num = barcode; + ui->user_data_lineEdit_2->setText(barcode); + ui->tishi_label->setText("护面扫码成功"); + } + else + { + // 护面框有值,加热框为空,输入到加热框 + hot_num = barcode; + ui->user_data_lineEdit_3->setText(barcode); + ui->tishi_label->setText("加热垫扫码成功"); + } + + QMediaPlayer *player1 = new QMediaPlayer(this); + + // 设置媒体文件 + player1->setMedia(QUrl("qrc:/res/sm.wav")); + player1->setVolume(100); + + // 连接信号,当播放结束时释放 player1 + connect(player1, &QMediaPlayer::stateChanged, this, [player1](QMediaPlayer::State state) + { + if (state == QMediaPlayer::StoppedState) { + player1->deleteLater(); // 延迟释放 + } }); + + // 播放音频 + player1->play(); +} + +void MainWindow::on_delete_1_button_clicked_func() +{ + + if (auto_mode) + { + ui->auto_label->setText("自动关闭"); + auto_mode = false; + timer->stop(); + } + QString userInput1 = ui->delete_res_lineEdit_0->text(); + + bool ok; + + int value1 = userInput1.toInt(&ok); + if (!ok) + { + ui->delete_res_lineEdit_0->setText("输入无效: 请输入有效的数字"); + ui->delete_res_lineEdit_0->setStyleSheet("QLabel { color : red; }"); + return; + } + + int rec_flag = DatabaseUtils::delete_ResData(value1); + + if (rec_flag == 0) + { + ui->delete_res_lineEdit_0->setText(""); + QMessageBox::warning(this, "设置失败", "ID不存在"); + } + else if (rec_flag == 1) + { + on_nextPageButton_clicked(); + on_prevPageButton_clicked(); + ui->delete_res_lineEdit_0->setText(""); + QMessageBox::warning(this, "设置成功", "已修改"); + } + else + { + ui->delete_res_lineEdit_0->setText(""); + QMessageBox::warning(this, "设置失败", "请重新设置"); + } + + // if(auto_mode){ + // ui->auto_label->setText("自动关闭"); + // auto_mode = false; + // timer->stop(); + // } + // QString userInput1 = ui->delete_res_lineEdit_2->text(); + // QString userInput2 = ui->delete_res_lineEdit_2->text(); + // QString userInput3 = ui->delete_res_lineEdit_2->text(); + + // bool ok; + + // int value2 = userInput2.toInt(&ok); + // if (!ok) { + // ui->delete_res_lineEdit_2->setText("输入无效: 请输入有效的数字"); + // ui->delete_res_lineEdit_2->setStyleSheet("QLabel { color : red; }"); + // return; + // } + + // int value3 = userInput3.toInt(&ok); + // if (!ok) { + // ui->delete_res_lineEdit_2->setText("输入无效: 请输入有效的数字"); + // ui->delete_res_lineEdit_2->setStyleSheet("QLabel { color : red; }"); + // return; + // } + + // QString hexValue2 = QString("%1").arg(value2, 4, 16, QLatin1Char('0')).toUpper(); + // QString hexValue3 = QString("%1").arg(value3, 4, 16, QLatin1Char('0')).toUpper(); + + // QString command1; + // if (userInput1 == "0") { + // command1 = "011003ED0003060000"; + // } else if (userInput1 == "1") { + // command1 = "011003ED0003060001"; + // } else if (userInput1 == "2") { + // command1 = "011003ED0003060002"; + // } else if (userInput1 == "3") { + // command1 = "011003ED0003060003"; + // } else { + // ui->delete_res_lineEdit_2->setText("输入无效: 请输入0到4之间的数字"); + // ui->delete_res_lineEdit_2->setStyleSheet("QLabel { color : red; }"); + // return; + // } + + // command1 += hexValue2; + // command1 += hexValue3; + + // bool rec_flag = serialPortManager->sendModbus_receive_2(command1, 6); + // delay(10); + + // if(rec_flag == false){ + // QMessageBox::warning(this, "设置失败", "请重新设置"); + // } else { + // QMessageBox::warning(this, "设置成功", "已修改"); +} + +void MainWindow::on_delete_2_button_clicked_func() +{ + if (auto_mode) + { + ui->auto_label->setText("自动关闭"); + auto_mode = false; + timer->stop(); + } + + QString userInput2 = ui->delete_res_lineEdit_1->text(); + QString userInput3 = ui->delete_res_lineEdit_2->text(); + + bool ok; + + int value2 = userInput2.toInt(&ok); + if (!ok) + { + ui->delete_res_lineEdit_1->setText("输入无效: 请输入有效的数字"); + ui->delete_res_lineEdit_1->setStyleSheet("QLabel { color : red; }"); + return; + } + + int value3 = userInput3.toInt(&ok); + if (!ok) + { + ui->delete_res_lineEdit_2->setText("输入无效: 请输入有效的数字"); + ui->delete_res_lineEdit_2->setStyleSheet("QLabel { color : red; }"); + return; + } + + int rec_flag = -1; + int flag = 1; + int temp = -1; + for (int i = value2; i <= value3; i++) + { + temp = DatabaseUtils::delete_ResData(i); + if (temp == 1) + { + rec_flag = 1; + flag = 0; + } + else if ((temp == 0) && flag) + { + rec_flag = 0; + } + } + + if (rec_flag == 0) + { + ui->delete_res_lineEdit_1->setText(""); + ui->delete_res_lineEdit_2->setText(""); + QMessageBox::warning(this, "设置失败", "ID都不存在"); + } + else if (rec_flag == 1) + { + on_nextPageButton_clicked(); + on_prevPageButton_clicked(); + ui->delete_res_lineEdit_1->setText(""); + ui->delete_res_lineEdit_2->setText(""); + QMessageBox::warning(this, "设置成功", "已修改"); + } + else + { + ui->delete_res_lineEdit_1->setText(""); + ui->delete_res_lineEdit_2->setText(""); + QMessageBox::warning(this, "设置失败", "请重新设置"); + } +} + +void MainWindow::on_pwd_button_clicked_func() +{ + QString userInput = ui->pwd_lineEdit->text(); + + if (userInput.length() > 8) + { + ui->pwd_lineEdit->setText(""); + QMessageBox::warning(this, "设置失败", "密码长度不能超过8个字符,请重新输入"); + return; + } + + bool save_flag = DatabaseUtils::update_Info_t("pwd", userInput); + pwd = userInput; + + if (save_flag == false) + { + ui->pwd_lineEdit->setText(""); + QMessageBox::warning(this, "设置失败", "请重新设置"); + } + else + { + ui->pwd_lineEdit->setText(""); + QMessageBox::warning(this, "设置成功", "请记住密码"); + } +} + +void MainWindow::onShowPasswordDialog() +{ + PasswordDialog *passwordDialog = new PasswordDialog(this); + // passwordDialog->setWindowModality(Qt::ApplicationModal); // 设置为模态对话框 + passwordDialog->show(); + + // connect(passwordDialog, &PasswordDialog::accepted, this, &MainWindow::onPasswordDialogAccepted); + + // 获取发送信号的对象 + QObject *senderObj = sender(); + senderButton = qobject_cast(senderObj); + + // 连接对话框的 accepted 信号到 的槽函数 + connect(passwordDialog, &PasswordDialog::accepted, this, &MainWindow::onPasswordAccepted); +} + +void MainWindow::onPasswordAccepted() +{ + PasswordDialog *passwordDialog = qobject_cast(sender()); + if (passwordDialog) + { + QString password = passwordDialog->getPassword(); + qDebug() << "password:" << password; + + if (password == pwd) + { + + if (senderButton) + { + if (senderButton == ui->xc_button) + { + on_xc_button_clicked_func(); + } + else if (senderButton == ui->pwd_button) + { + on_pwd_button_clicked_func(); + } + else if (senderButton == ui->name_button) + { + on_name_button_clicked_func(); + } + else if (senderButton == ui->auto_value_button) + { + on_auto_value_button_clicked_func(); + } + else if (senderButton == ui->res_max_button) + { + on_res_max_button_clicked_func(); + } + else if (senderButton == ui->delete_1_button) + { + on_delete_1_button_clicked_func(); + } + else if (senderButton == ui->delete_2_button) + { + on_delete_2_button_clicked_func(); + } + } + senderButton = NULL; + } + else + { + senderButton = NULL; + QMessageBox::warning(this, "密码错误", "请检查密码"); + } + } +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..040ef9b --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,192 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "SerialConfig.h" +#include "serialportmanager.h" +#include +#include // 引入所有QtCharts绘制的头文件,也可以单独引入某一个头文件 +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + void keyPressEvent(QKeyEvent *event) + { + if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + { + + QString barcode = barStr; + barStr.clear(); + + dispscan(barcode); + qDebug() << "Barcode scanned:" << barcode; + } + else + { + barStr += event->text(); + qDebug() << " scanned:" << barStr; + } + } + void mousePressEvent(QMouseEvent *event) + { + // 关闭扫码提示标签 + if (scanTipLabel != nullptr) + { + scanTipLabel->close(); + scanTipLabel->deleteLater(); + scanTipLabel = nullptr; + } + // 点击空白区域时,让主窗口获得焦点,确保扫码输入正常工作 + this->setFocus(); + QMainWindow::mousePressEvent(event); + } + void resizeEvent(QResizeEvent *event) override; + void dispscan(const QString barcode); + +private slots: + void on_link_button_clicked(); + + void onLinkButtonClicked(); + + void on_manual_button_clicked(); + + void on_auto_button_clicked(); + + void on_excel_b_clicked(); + + void on_user_data_button_clicked_func(); + + void on_res_max_button_clicked_func(); + + void on_auto_value_button_clicked_func(); + + // void on_set_tx_button_clicked(); + + void on_nextPageButton_clicked(); + + void on_prevPageButton_clicked(); + + void auto_query(); + + void updateCharts(); + + void on_name_button_clicked_func(); + + void onExportSucceeded(); + + void onExportFailed(const QString &error); + + void get_device_bj_info(); + + void on_pushButton_2_clicked(); + + void on_xc_button_clicked_func(); + + void playQualified(); + + void playUnqualified(); + + void playConnState_ok(); + + void playConnState_no(); + + void play_get_err(); + + void play_no_conn_res(); + + void handleDataRead(const QByteArray &data); + + void on_delete_1_button_clicked_func(); + + void on_delete_2_button_clicked_func(); + + void on_pwd_button_clicked_func(); + + void onShowPasswordDialog(); + + void onPasswordAccepted(); + +private: + Ui::MainWindow *ui; + SerialPortManager *serialPortManager; + SerialConfig *serialConfigDialog; + QSerialPort *serialPort; + QTimer *timer; + QTimer *timer2; + QLabel *scanTipLabel = nullptr; // 扫码提示标签 + QSize initialSize; // 初始窗口大小 + QSize designSize; // UI设计基准尺寸 + QPieSeries *pieSeries; + QMediaPlayer *player; + QString barStr; + QPushButton *senderButton; + + QBarSet *set20; + QBarSet *set21; + QBarSet *set22; + QValueAxis *axisX2; + + void disp_IIOT(); + void disp_canshu(); + + int null_cnt = 0; + int serial_error = 0; + bool auto_mode = false; // 自动模式开关 + QString auto_mode_flag; + + int all_count; // 总检验次数 + int auto_cnt; // 自动检验次数 + int manual_cnt; // 手动检验次数 + int qualified_cnt; // 合格次数 + int up_cnt; // 超过范围次数 + int down_cnt; // 小于范围次数 + double res_value; // 电阻标准值 + double res_sd; // 电阻误差范围 + double max_res; // 最大电阻范围 + double min_res; // 最小电阻范围 + double xc_sd; // 线材阻值 + int auto_time; // 自动获取时间 + QString name; // 检验员 + QString chexing; // 车型 + QString sm_num; // 护面扫码编号 + QString hot_num; // 加热垫扫码编号 + QString pwd; // 密码 + + QString daily_date; + bool daily_one = true; + int daily_cnt = 0; + int daily_cnt_manual = 0; + int daily_qualified_cnt = 0; + int daily_cnt_over = 0; + int daily_cnt_down = 0; + double daily_cnt_max = 0; + double daily_cnt_min = 0; + double daily_qualified_l = 0; + + QString detect; + int currentPage = 0; + int rowsPerPage = 21; + int totalRows = 0; + int old_totalRows = 0; + bool uppage = true; + int uppage_cnt = 0; + void updateTableWidget(const QVector &data); + void get_one_data(); +}; +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..021ec38 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,2112 @@ + + + MainWindow + + + true + + + + 0 + 0 + 1280 + 1024 + + + + 电阻检测软件 + + + + :/res/tnn_icon.png:/res/tnn_icon.png + + + + + + + + + 40 + 20 + 101 + 101 + + + + + 思源宋体 + + + + border-image: url(:/res/tnn_icon.png); + + + + + + + + + 820 + 20 + 451 + 101 + + + + + 思源宋体 + 12 + + + + 0.000000000000000 + + + 0 + + + + + + 20 + 150 + 1280 + 900 + + + + + 100 + 100 + + + + + 0 + 400 + + + + + 思源宋体 + 10 + true + + + + QTabWidget::pane{ + background: rgba(14,73,122,0.4); + border:none; +} + + + + 1 + + + + + 0 + 100 + + + + + 0 + 700 + + + + background-color: rgb(26, 20, 69); + + + 总览 + + + + + background-color: rgb(255, 255, 255); + + + 历史记录 + + + + + 20 + 10 + 1280 + 710 + + + + Qt::DefaultContextMenu + + + Qt::LeftToRight + + + /* +tabelwidget*/ +QTableWidget{ +color:#DCDCDC; +background:#444444; +border:1px solid #242424; +alternate-background-color:#525252;/*交错颜色*/ +gridline-color:#242424; +} + +/*选中item*/ +QTableWidget::item:selected{ +color:#DCDCDC; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); +} + +/* +悬浮item*/ +QTableWidget::item:hover{ +background:#5B5B5B; +} +/*表头*/ +QHeaderView::section{ +text-align:center; +background:#5E5E5E; +padding:3px; +margin:0px; +color:#DCDCDC; +border:1px solid #242424; +border-left-width:0; +} + + + +/*表右侧的滑条*/ +QScrollBar:vertical{ +background:#484848; +padding:0px; +border-radius:6px; +max-width:12px; +} + +/*滑块*/ +QScrollBar::handle:vertical{ +background:#CCCCCC; +} +/* +滑块悬浮,按下*/ +QScrollBar::handle:hover:vertical,QScrollBar::handle:pressed:vertical{ +background:#A7A7A7; +} +/* +滑块已经划过的区域*/ +QScrollBar::sub-page:vertical{ +background:444444; +} + +/* +滑块还没有划过的区域*/ +QScrollBar::add-page:vertical{ +background:5B5B5B; +} + +/*页面下移的按钮*/ +QScrollBar::add-line:vertical{ +background:none; +} +/*页面上移的按钮*/ +QScrollBar::sub-line:vertical{ +background:none; +} + + + 1 + + + 0 + + + QAbstractItemView::SelectItems + + + Qt::ElideMiddle + + + + 1 + + + + 9 + + + + AlignCenter + + + + + 2 + + + AlignCenter + + + + + 3 + + + AlignCenter + + + + + 4 + + + AlignCenter + + + + + 5 + + + AlignCenter + + + + + 6 + + + AlignCenter + + + + + 7 + + + AlignCenter + + + + + 8 + + + AlignCenter + + + + + 9 + + + AlignCenter + + + + + 10 + + + AlignCenter + + + + + 11 + + + AlignCenter + + + + + 12 + + + AlignCenter + + + + + 13 + + + AlignCenter + + + + + 14 + + + AlignCenter + + + + + 15 + + + AlignCenter + + + + + 16 + + + AlignCenter + + + + + 17 + + + AlignCenter + + + + + 18 + + + AlignCenter + + + + + 19 + + + AlignCenter + + + + + 20 + + + AlignCenter + + + + + 21 + + + AlignCenter + + + + + 序号 + + + + 9 + + + + + + 车型 + + + + + 护面扫码编号 + + + + + 加热垫扫码编号 + + + + + 实测阻值 + + + + + 标准阻值范围 + + + + + 检测结果 + + + + + 手/自 + + + + + 检测员 + + + + + 检测时间 + + + + + + + + AlignCenter + + + NoItemFlags + + + + + + + + + + + + + AlignCenter + + + + + + + 41 + 10 + 21 + 35 + + + + + 16 + true + + + + background-color: rgb(94, 94, 94); + + + > + + + + + + 20 + 10 + 21 + 35 + + + + + 16 + true + + + + background-color: rgb(94, 94, 94); + + + < + + + + + + background-color: rgb(255, 255, 255); + + + 参数修改 + + + + + 430 + 440 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 200 + 120 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 60 + 200 + 131 + 41 + + + + + 思源宋体 + 15 + false + + + + + + + 加热垫编号 + + + + + + 200 + 200 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 30 + 350 + 161 + 51 + + + + + 思源宋体 + 15 + false + + + + + + + 标准阻值范围 + + + + + + 80 + 470 + 81 + 31 + + + + + 思源宋体 + 11 + false + + + + + + + 误差范围 + + + + + + 130 + 410 + 41 + 31 + + + + + 思源宋体 + 11 + false + + + + + + + 阻值 + + + + + + 40 + 120 + 151 + 41 + + + + + 思源宋体 + 15 + false + + + + + + + 护面扫码编号 + + + + + + 200 + 40 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 90 + 40 + 61 + 41 + + + + + 思源宋体 + 15 + false + + + + + + + 车型 + + + + + + 1010 + 40 + 81 + 41 + + + + + 思源宋体 + 15 + + + + + + + 波特率 + + + + + + 880 + 40 + 121 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 740 + 40 + 121 + 41 + + + + + 思源宋体 + 15 + + + + + + + 从机地址 + + + + + + 1100 + 40 + 121 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 430 + 45 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 880 + 360 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 700 + 300 + 201 + 51 + + + + + 思源宋体 + 15 + false + + + + + + + 指定ID范围删除 + + + + + + 730 + 220 + 141 + 41 + + + + + 思源宋体 + 15 + false + + + + + + + 单个ID删除 + + + + + + 880 + 120 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 740 + 545 + 111 + 51 + + + + + 思源宋体 + 15 + false + + + + + + + 密码更改 + + + + + + 880 + 430 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 1421 + 1039 + 111 + 31 + + + + + 思源宋体 + + + + + + + 200 + 550 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 30 + 540 + 161 + 51 + + + + + 思源宋体 + 15 + + + + + + + 自动检测时间 + + + + + + 393 + 550 + 21 + 41 + + + + + 思源宋体 + 11 + + + + + + + + + + + + + 740 + 115 + 121 + 51 + + + + + 思源宋体 + 15 + false + + + + + + + 线材阻值 + + + + + + 880 + 220 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 430 + 555 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 1110 + 125 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + true + + + + 1110 + 225 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 200 + 400 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 460 + 181 + 41 + + + + + 思源宋体 + 13 + + + + Qt::RightToLeft + + + border: 1px solid black;border-radius: 5px; + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 390 + 410 + 31 + 31 + + + + + 思源宋体 + 13 + + + + + + + Ω + + + + + + 390 + 470 + 21 + 31 + + + + + 思源宋体 + 13 + + + + + + + % + + + + + + 170 + 470 + 21 + 31 + + + + + 思源宋体 + 13 + + + + + + + ± + + + + + + 80 + 280 + 91 + 41 + + + + + 思源宋体 + 15 + + + + + + + 检验员 + + + + + + 200 + 280 + 181 + 41 + + + + + 思源宋体 + 13 + + + + border: 1px solid black;border-radius: 5px; + + + + + + 430 + 285 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + true + + + + 1110 + 400 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 880 + 550 + 181 + 41 + + + + + 思源宋体 + + + + border: 1px solid black;border-radius: 5px; + + + + + + 1110 + 555 + 71 + 31 + + + + + 思源宋体 + 11 + true + + + + border: 0px solid black;border-radius: 5px; +background-color: rgb(96, 186, 255); + + + 保存 + + + + + + 820 + 365 + 41 + 31 + + + + + 思源宋体 + 11 + + + + 起点 + + + + + + 820 + 435 + 41 + 31 + + + + + 思源宋体 + 11 + + + + 终点 + + + + + + 1075 + 125 + 31 + 31 + + + + + 思源宋体 + 13 + + + + + + + Ω + + + label_hot_num + user_data_lineEdit_3 + label_42 + set_tx_lineEdit_2 + label_40 + res_max_button + user_data_lineEdit_2 + label_34 + label_35 + label_36 + label_38 + user_data_lineEdit_1 + label_39 + set_tx_lineEdit_1 + user_data_button + delete_res_lineEdit_1 + label_46 + label_47 + xc_lineEdit + label_49 + delete_res_lineEdit_2 + lineEdit_16 + auto_value_lineEdit_1 + label_74 + label_8 + label_51 + delete_res_lineEdit_0 + auto_value_button + xc_button + delete_1_button + res_max_lineEdit_1 + res_max_lineEdit_2 + label_2 + label_3 + label_4 + label_13 + name_lineEdit + name_button + delete_2_button + pwd_lineEdit + pwd_button + label + label_6 + label_9 + + + + background-color: rgb(255, 255, 255); + + + 帮助 + + + + + 850 + 690 + 271 + 71 + + + + + 25 + + + + GetonAgain + + + + + + 330 + 150 + 541 + 351 + + + + + 15 + + + + border: 0px solid + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:15pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'SimSun'; font-size:20pt;"> 电阻检测软件具备图表预览界面,实时更新测试数据,自动连接电阻测试仪,支持手动和自动检测模式,用户可修改检测参数。软件生成详细的Excel表格,支持在界面中预览并可导出保存,方便数据分析和记录。</span></p></body></html> + + + + + + 540 + 540 + 141 + 41 + + + + + 14 + + + + border: 1px solid black;border-radius: 5px; + + + + 获取软件手册 + + + + + + 330 + 25 + 551 + 101 + + + + + + + + + + + true + + + + 1530 + 0 + 281 + 141 + + + + + 100 + 100 + + + + + 100 + 100 + + + + + 思源宋体 + + + + Qt::RightToLeft + + + +image: url(:/res/tnn_icon.png); + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 30 + 65 + 65 + + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 思源宋体 + + + + border: 3px solid #8f8f91; +border-radius: 20px; +background-color: #f0f0f0; +padding: 10px; + + + + + + + :/res/link_b.svg:/res/link_b.svg + + + + 40 + 50 + + + + + + + 170 + 100 + 101 + 20 + + + + + 16777215 + 20 + + + + + 思源宋体 + 11 + true + + + + 连接 + + + Qt::AlignCenter + + + + + + 330 + 30 + 65 + 65 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 思源宋体 + + + + border: 3px solid #8f8f91; +border-radius: 20px; +background-color: #f0f0f0; +padding: 10px; + + + + + + + :/res/auto.svg:/res/auto.svg + + + + 40 + 50 + + + + + + + 280 + 100 + 161 + 20 + + + + + 16777215 + 20 + + + + + 思源宋体 + 11 + true + + + + 自动模式 + + + Qt::AlignCenter + + + + + + 440 + 100 + 141 + 20 + + + + + 16777215 + 20 + + + + + 思源宋体 + 11 + true + + + + 手动获取 + + + Qt::AlignCenter + + + + + + 480 + 30 + 65 + 65 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 思源宋体 + + + + border: 3px solid #8f8f91; +border-radius: 20px; +background-color: #f0f0f0; +padding: 10px; + + + + + + + :/res/one.svg:/res/one.svg + + + + 40 + 50 + + + + + + + 630 + 30 + 65 + 65 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 思源宋体 + + + + border: 3px solid #8f8f91; +border-radius: 20px; +background-color: #f0f0f0; +padding: 10px; + + + + + + + :/res/excel.svg:/res/excel.svg + + + + 40 + 50 + + + + + + + 610 + 100 + 101 + 20 + + + + + 16777215 + 20 + + + + + 思源宋体 + 11 + true + + + + 导出表格 + + + Qt::AlignCenter + + + + + + 1300 + 70 + 171 + 41 + + + + + 思源宋体 + 15 + true + + + + 请获取阻值 + + + + + + 1300 + 30 + 201 + 31 + + + + + 思源宋体 + 12 + + + + + + + tabWidget + label_img + link_button + link_label + auto_button + auto_label + manual_label + manual_button + excel_b + label_5 + label_7 + lcdNumber + info_label + tishi_label + + + + + + + + diff --git a/passworddialog.cpp b/passworddialog.cpp new file mode 100644 index 0000000..a8ae69d --- /dev/null +++ b/passworddialog.cpp @@ -0,0 +1,59 @@ +#include "passworddialog.h" +#include +#include +#include +#include +#include +#include + +PasswordDialog::PasswordDialog(QWidget *parent) : + QDialog(parent), + passwordLineEdit(new QLineEdit(this)), + confirmButton(new QPushButton("确认", this)) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + + QLabel *label = new QLabel("请输入密码:", this); + label->setStyleSheet("QLabel { font-size: 14px; }"); + layout->addWidget(label); + + passwordLineEdit->setEchoMode(QLineEdit::Password); + layout->addWidget(passwordLineEdit); + + confirmButton->setStyleSheet("QPushButton { font-size: 12px; }"); + layout->addWidget(confirmButton); + + connect(confirmButton, &QPushButton::clicked, this, &PasswordDialog::onConfirmButtonClicked); + + setLayout(layout); + + setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); + setAttribute(Qt::WA_DeleteOnClose); + + // 安装事件过滤器 +// passwordLineEdit->installEventFilter(this); + passwordLineEdit->setFont(QFont("Arial")); +} + +QString PasswordDialog::getPassword() const +{ + return passwordLineEdit->text(); +} + +void PasswordDialog::onConfirmButtonClicked() +{ +// qDebug() << "Entered password:" << getPassword(); + accept(); // 关闭对话框并返回 QDialog::Accepted +} + + +//bool PasswordDialog::eventFilter(QObject *obj, QEvent *event) +//{ +// if (obj == passwordLineEdit) { +// if (event->type() == QEvent::FocusIn) { +// // 处理焦点进入事件,确保虚拟键盘能够正常工作 +// qDebug() << "QLineEdit got focus"; +// } +// } +// return QDialog::eventFilter(obj, event); +//} diff --git a/passworddialog.h b/passworddialog.h new file mode 100644 index 0000000..4958536 --- /dev/null +++ b/passworddialog.h @@ -0,0 +1,28 @@ +#ifndef PASSWORDDIALOG_H +#define PASSWORDDIALOG_H + +#include +#include +#include +#include +#include + +class PasswordDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PasswordDialog(QWidget *parent = nullptr); + + QString getPassword() const; + +private slots: + void onConfirmButtonClicked(); + +private: + QLineEdit *passwordLineEdit; + QPushButton *confirmButton; +// bool eventFilter(QObject *obj, QEvent *event); +}; + +#endif // PASSWORDDIALOG_H diff --git a/res.qrc b/res.qrc new file mode 100644 index 0000000..9da37c4 --- /dev/null +++ b/res.qrc @@ -0,0 +1,23 @@ + + + res/tnn_icon.png + res/zdkj.png + res/auto.svg + res/link_b.svg + res/link_g.svg + res/link_r.svg + res/one.svg + res/excel.svg + res/1.png + res/user.pdf + res/ok.wav + res/no.wav + res/auto_conn_no.wav + res/auto_conn_ok.wav + res/auto_off.wav + res/auto_on.wav + res/get_err.wav + res/no_conn_res.wav + res/sm.wav + + diff --git a/res/1.png b/res/1.png new file mode 100644 index 0000000..409e660 Binary files /dev/null and b/res/1.png differ diff --git a/res/auto.svg b/res/auto.svg new file mode 100644 index 0000000..d7caa7e --- /dev/null +++ b/res/auto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/auto_conn_no.wav b/res/auto_conn_no.wav new file mode 100644 index 0000000..e9b4683 Binary files /dev/null and b/res/auto_conn_no.wav differ diff --git a/res/auto_conn_ok.wav b/res/auto_conn_ok.wav new file mode 100644 index 0000000..59cc78d Binary files /dev/null and b/res/auto_conn_ok.wav differ diff --git a/res/auto_off.wav b/res/auto_off.wav new file mode 100644 index 0000000..71065b6 Binary files /dev/null and b/res/auto_off.wav differ diff --git a/res/auto_on.wav b/res/auto_on.wav new file mode 100644 index 0000000..7456e4a Binary files /dev/null and b/res/auto_on.wav differ diff --git a/res/excel.svg b/res/excel.svg new file mode 100644 index 0000000..f6eefa4 --- /dev/null +++ b/res/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/get_err.wav b/res/get_err.wav new file mode 100644 index 0000000..cb0708a Binary files /dev/null and b/res/get_err.wav differ diff --git a/res/icon.ico b/res/icon.ico new file mode 100644 index 0000000..018c1f3 Binary files /dev/null and b/res/icon.ico differ diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 0000000..f3dce02 Binary files /dev/null and b/res/icon.png differ diff --git a/res/link_b.svg b/res/link_b.svg new file mode 100644 index 0000000..3738a11 --- /dev/null +++ b/res/link_b.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/link_g.svg b/res/link_g.svg new file mode 100644 index 0000000..73d78bb --- /dev/null +++ b/res/link_g.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/link_r.svg b/res/link_r.svg new file mode 100644 index 0000000..ee44293 --- /dev/null +++ b/res/link_r.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/no.wav b/res/no.wav new file mode 100644 index 0000000..c55bb4b Binary files /dev/null and b/res/no.wav differ diff --git a/res/no_conn_res.wav b/res/no_conn_res.wav new file mode 100644 index 0000000..1b4220f Binary files /dev/null and b/res/no_conn_res.wav differ diff --git a/res/ok.wav b/res/ok.wav new file mode 100644 index 0000000..0fdc1a4 Binary files /dev/null and b/res/ok.wav differ diff --git a/res/one.svg b/res/one.svg new file mode 100644 index 0000000..7f8d9a6 --- /dev/null +++ b/res/one.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/sm.wav b/res/sm.wav new file mode 100644 index 0000000..1cafbdf Binary files /dev/null and b/res/sm.wav differ diff --git a/res/tnn_icon.png b/res/tnn_icon.png new file mode 100644 index 0000000..f3dce02 Binary files /dev/null and b/res/tnn_icon.png differ diff --git a/res/user.pdf b/res/user.pdf new file mode 100644 index 0000000..e4ddda5 Binary files /dev/null and b/res/user.pdf differ diff --git a/res/zd.jpeg b/res/zd.jpeg new file mode 100644 index 0000000..2b940f7 Binary files /dev/null and b/res/zd.jpeg differ diff --git a/res/zdkj.png b/res/zdkj.png new file mode 100644 index 0000000..922a4be Binary files /dev/null and b/res/zdkj.png differ diff --git a/serialportmanager.cpp b/serialportmanager.cpp new file mode 100644 index 0000000..81da267 --- /dev/null +++ b/serialportmanager.cpp @@ -0,0 +1,369 @@ +#include "serialportmanager.h" +#include +#include +#include +#include +#include +#include +#include + +SerialPortManager::SerialPortManager(QObject *parent) + : QObject(parent), + serialPort(nullptr) { + +} + +SerialPortManager::~SerialPortManager() { + if (serialPort) { + delete serialPort; + } +} + +void SerialPortManager::setConnectionMode(ConnectionMode mode) { + connectionMode = mode; +} + +void SerialPortManager::configureSerialPort(const QString &portName, int baudRate, int dataBits, int stopBits, int parity) { + this->portName = portName; + this->baudRate = baudRate; + this->dataBits = dataBits; + this->stopBits = stopBits; + this->parity = parity; +} + +bool SerialPortManager::connectSerialPort() { + if (serialPort) { + disconnectSerialPort(); + } + + if (connectionMode == Manual) { + serialPort = new QSerialPort(this); + serialPort->setPortName(portName); + serialPort->setBaudRate(baudRate); + serialPort->setDataBits(static_cast(dataBits)); + serialPort->setStopBits(static_cast(stopBits)); + serialPort->setParity(static_cast(parity)); + + } else { + QString portName = autoConnect(command, expectedResponse); + if (portName != "-1") { + serialPort = new QSerialPort(portName, this); + serialPort->setBaudRate(QSerialPort::Baud9600); + serialPort->setDataBits(QSerialPort::Data8); + serialPort->setStopBits(QSerialPort::OneStop); + serialPort->setParity(QSerialPort::NoParity); + } + } + + if (serialPort && serialPort->open(QIODevice::ReadWrite)) { + qDebug() << "open serialPort ok"; + return true; + } else { + if (serialPort) { + delete serialPort; + serialPort = nullptr; + } + qDebug() << "open serialPort fail"; + return false; + } +} + +void SerialPortManager::disconnectSerialPort() { + if (serialPort && serialPort->isOpen()) { + serialPort->close(); + } + if (serialPort) { + delete serialPort; + serialPort = nullptr; + } +} + +QSerialPort* SerialPortManager::getSerialPort() const { + return serialPort; +} + +QString SerialPortManager::autoConnect(const QByteArray &command, const QByteArray &expectedResponse) { + const auto ports = QSerialPortInfo::availablePorts(); + for (const QSerialPortInfo &portInfo : ports) { + QSerialPort port(portInfo); + port.setBaudRate(QSerialPort::Baud9600); + port.setDataBits(QSerialPort::Data8); + port.setStopBits(QSerialPort::OneStop); + port.setParity(QSerialPort::NoParity); + + qDebug() << "Trying port:" << portInfo.portName(); + if (port.open(QIODevice::ReadWrite)) { + if (testPort(port, command, expectedResponse)) { + port.close(); + qDebug() << "Port found:" << portInfo.portName(); + return portInfo.portName(); + } + port.close(); + } else { + qDebug() << "Failed to open port:" << portInfo.portName(); + } + } + return "-1"; +} + + +bool SerialPortManager::testPort(QSerialPort &port, const QByteArray &command, const QByteArray &expectedResponse) { + for (int attempt = 0; attempt < 3; ++attempt) { + port.write(hexStringToByteArray(command)); + if (port.waitForBytesWritten(1000) && port.waitForReadyRead(1000)) { + + QByteArray responseData = port.readAll(); + while (port.waitForReadyRead(100)) { + responseData += port.readAll(); + } + qDebug() << "Attempt" << attempt + 1 << ": Received response:" << responseData.toHex(); + + QString responseHex = responseData.toHex(); + QString expectedResponseHex = expectedResponse.toHex(); + +// qDebug() << "test responseHex " <(hex.mid(i, 2).toInt(&ok, 16))); + } + return byteArray; +} + +uint16_t SerialPortManager::CRC16_Calculate(const QByteArray &data) +{ + uint16_t crc = 0xFFFF; + + for (int pos = 0; pos < data.size(); pos++) { + crc ^= static_cast(data[pos]); + + for (int i = 8; i != 0; i--) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + return crc; +} + +void SerialPortManager::sendModbusCommand(const QString &hexCommand) +{ + if (serialPort && serialPort->isOpen()) + { + QByteArray command = hexStringToByteArray(hexCommand); + uint16_t crc = CRC16_Calculate(command); + + command.append(static_cast(crc & 0xFF)); //低 + command.append(static_cast((crc >> 8) & 0xFF)); //高 + + serialPort->write(command); + } + else + { + qWarning() << "Serial port is not open!"; + } +} + + + +double SerialPortManager::receiveModbusResponse(int RESPONSE_LENGTH) { + if (serialPort && serialPort->isOpen()) { + + QByteArray responseData = serialPort->readAll(); + while (serialPort->waitForReadyRead(100)) { + responseData += serialPort->readAll(); + } + +// qDebug() << "Raw response data:" << responseData.toHex(); + + while (responseData.size() >= RESPONSE_LENGTH) { + QByteArray singleResponse = responseData.left(RESPONSE_LENGTH); + responseData.remove(0, RESPONSE_LENGTH); + +// qDebug() << "Processing single response:" << singleResponse.toHex(); + + // Extract the received CRC + uint16_t receivedCrc = static_cast(singleResponse[singleResponse.size() - 2]) | + (static_cast(singleResponse[singleResponse.size() - 1]) << 8); + + // Remove the CRC from the response data + singleResponse.chop(2); + + // Calculate the CRC of the received data + uint16_t calculatedCrc = CRC16_Calculate(singleResponse); + + qDebug() << "Data without CRC:" << singleResponse.toHex(); + qDebug() << "Received CRC:" << QString::number(receivedCrc, 16).toUpper(); + qDebug() << "Calculated CRC:" << QString::number(calculatedCrc, 16).toUpper(); + + if (receivedCrc == calculatedCrc) { + qDebug() << "Received valid response with correct CRC:" << singleResponse; + + // Extract the specific data part (e.g., 03E80003) + if (singleResponse.size() == 5 ) { + QByteArray specificData = singleResponse.mid(3, 2); // Extract 4 bytes starting from the 4th byte +// qDebug() << "Specific data part:" << specificData.toHex(); + + if(specificData.toHex() == "7fff"){ + return -2; + } else if(specificData.toHex() == "7ffc"){ + return -3; + } + + // Extract the first 2 bytes for the integer part + uint16_t integerPart = (static_cast(specificData[0]) << 8) | + static_cast(specificData[1]); + + // Extract the second 2 bytes for the decimal places + uint16_t decimalPlaces = (static_cast(specificData[2]) << 8) | + static_cast(specificData[3]); + + /** + * @brief value 1000 + */ + + //一位小数除10,三位除1000 + double value = static_cast(integerPart) / 1000; + qDebug() << "Converted value:" << value; + qDebug() << "Converted value:" << QString::number(value, 'f', decimalPlaces); + + return value; // Return the calculated value + } else if (singleResponse.size() == 7 ) { + QByteArray specificData = singleResponse.mid(3, 4); // Extract 4 bytes starting from the 4th byte + qDebug() << "Specific data part:" << specificData.toHex(); + + if(specificData.toHex() == "7fff"){ + return -2; + } else if(specificData.toHex() == "7ffc"){ + return -3; + } + + // Extract the first 2 bytes for the integer part + uint16_t integerPart = (static_cast(specificData[0]) << 8) | + static_cast(specificData[1]); + + // Extract the second 2 bytes for the decimal places + uint16_t decimalPlaces = (static_cast(specificData[2]) << 8) | + static_cast(specificData[3]); + + double value = static_cast(integerPart) / std::pow(10, decimalPlaces); + qDebug() << "Converted value:" << value; + qDebug() << "Converted value:" << QString::number(value, 'f', decimalPlaces); + } else { + qWarning() << "The response is too short to extract the specific data part."; + } + + } else { + qWarning() << "CRC check failed. Received CRC:" << receivedCrc << "Calculated CRC:" << calculatedCrc; + } + } + + if (!responseData.isEmpty()) { + qWarning() << "Remaining data after processing:" << responseData.toHex(); + } + } else { + qWarning() << "receiveModbusResponse"; + } + + return -1; +} + + + + +double SerialPortManager::sendModbus_receive(const QString &hexCommand ,int RESPONSE_LENGTH) { + if (serialPort && serialPort->isOpen()) { + QByteArray command = hexStringToByteArray(hexCommand); + uint16_t crc = CRC16_Calculate(command); + + command.append(static_cast(crc & 0xFF)); //低 + command.append(static_cast((crc >> 8) & 0xFF)); //高 + + serialPort->write(command); + + + if (serialPort->waitForBytesWritten(1000)) { + return receiveModbusResponse(RESPONSE_LENGTH); + } else { + qWarning() << "Failed to write command to serial port."; + } + } else { + qWarning() << "sendModbus_receive Serial port is not open!"; + } + return -1; +} + +bool SerialPortManager::sendModbus_receive_2(const QString &hexCommand, int RESPONSE_LENGTH) { + if (serialPort && serialPort->isOpen()) { + QByteArray command = hexStringToByteArray(hexCommand); + uint16_t crc = CRC16_Calculate(command); + + command.append(static_cast(crc & 0xFF)); //低 + command.append(static_cast((crc >> 8) & 0xFF)); //高 + + serialPort->write(command); + + if (serialPort->waitForBytesWritten(1000)) { + QByteArray responseData = serialPort->readAll(); + while (serialPort->waitForReadyRead(100)) { + responseData += serialPort->readAll(); + } + +// qDebug() << "Raw response data:" << responseData.toHex(); + + while (responseData.size() >= RESPONSE_LENGTH) { + QByteArray singleResponse = responseData.left(RESPONSE_LENGTH); + responseData.remove(0, RESPONSE_LENGTH); + +// qDebug() << "Processing single response:" << singleResponse.toHex(); + + uint16_t receivedCrc = static_cast(singleResponse[singleResponse.size() - 2]) | + (static_cast(singleResponse[singleResponse.size() - 1]) << 8); + + singleResponse.chop(2); + + uint16_t calculatedCrc = CRC16_Calculate(singleResponse); + +// qDebug() << "Data without CRC:" << singleResponse.toHex(); +// qDebug() << "Received CRC:" << QString::number(receivedCrc, 16).toUpper(); +// qDebug() << "Calculated CRC:" << QString::number(calculatedCrc, 16).toUpper(); + + if (receivedCrc == calculatedCrc) { +// qDebug() << "Received valid response with correct CRC:" << singleResponse; + return true; + } else { + qWarning() << "CRC check failed. Received CRC:" << receivedCrc << "Calculated CRC:" << calculatedCrc; + return false; + } + } + + if (!responseData.isEmpty()) { + qWarning() << "Remaining data after processing:" << responseData.toHex(); + } + } else { + qWarning() << "Failed to write command to serial port."; + } + } else { + qWarning() << "Serial port is not open!"; + } + return false; +} diff --git a/serialportmanager.h b/serialportmanager.h new file mode 100644 index 0000000..f9162c0 --- /dev/null +++ b/serialportmanager.h @@ -0,0 +1,47 @@ +#ifndef SERIALPORTMANAGER_H +#define SERIALPORTMANAGER_H + +#include +#include + + +class SerialPortManager : public QObject { + Q_OBJECT + +public: + explicit SerialPortManager(QObject *parent = nullptr); + ~SerialPortManager(); + + enum ConnectionMode { + Manual, + Auto + }; + + void setConnectionMode(ConnectionMode mode); + void configureSerialPort(const QString &portName, int baudRate, int dataBits, int stopBits, int parity); + bool connectSerialPort(); + void disconnectSerialPort(); + QSerialPort* getSerialPort() const; + QByteArray hexStringToByteArray(const QString &hex); + void sendModbusCommand(const QString &hexCommand); + double receiveModbusResponse(int RESPONSE_LENGTH); + double sendModbus_receive(const QString &hexCommand, int RESPONSE_LENGTH); + bool sendModbus_receive_2(const QString &hexCommand, int RESPONSE_LENGTH); + +private: + QSerialPort *serialPort; + ConnectionMode connectionMode; + QString portName; + int baudRate; + int dataBits; + int stopBits; + int parity; + + QString autoConnect(const QByteArray &command, const QByteArray &expectedResponse); + bool testPort(QSerialPort &port, const QByteArray &command, const QByteArray &expectedResponse); + uint16_t CRC16_Calculate(const QByteArray &data); + QByteArray command = "01040000000131CA"; + QByteArray expectedResponse = "010402"; +}; + +#endif // SERIALPORTMANAGER_H diff --git a/用户手册/电阻检测软件 V1.0 用户手册.pdf b/用户手册/电阻检测软件 V1.0 用户手册.pdf new file mode 100644 index 0000000..717b1ed Binary files /dev/null and b/用户手册/电阻检测软件 V1.0 用户手册.pdf differ