QTextDocument 的資料處理

| | 0 Comments| 08:23
Categories:

這篇算是延續前面一篇 QTextEdit 處理圖片,來進一步看一下怎麼去處理 QTextDocument官方文件)的內容。不過老實說,這邊 Heresy 沒有研究得很徹底、搞不好也有錯;如果有發現有錯的話,也麻煩提點一下了。

首先,他主要的結構應該是由 QTextFrame文件)和 QTextBlock文件)組成的樹狀結構,每一個文件有一個 root frame、底下透過 block 和 frame 來組成整份文件;而 frame 底下則可以包含多個 block、但是 block 也可以直接在 root frame 下。

而實際上如果想要走過整個文件,以 QTextDocument 的 API 來看,應該是要透過 begin() / end()、以 QTextBlock 為單元來掃過整個文件,右圖就是官方提供的示意圖。

這邊也可以看到,在這個流程中、基本上完全不會碰到 QTextFrame 的資訊。這也讓 Heresy 有點不太理解 QTextFrame 的存在意義?
老實說,個人覺得 QTextDocument 的設計還滿…詭異的?

而實際上,QTextBlock 也還不是整個文件的最小單位。他代表的意義大致上「段落」(paragraph)、本身是也是由多個 QTextFragment文件)組成的。

所以如果這邊先無視 frame 這一層的話,整個 QTextDocument 的組成是下面這樣的三層式架構:

  • document
    • block
      • fragment

掃過整份文件

總之,如果單純要掃過整個文件的話,最簡單的方法就是直接透過 QTextDocumentbegin() / end() 來做,他的寫法大致上會是:

for (QTextBlock it = pDoc->begin(); it != pDoc->end(); it = it.next())
  qDebug() << it.text();

這邊的 pDoc 就是 QTextDocument 的指標。

而這樣的寫法基本上就可以簡單地把整份文件的「文字部分」依 block 一個一個輸出了。

如果要再更細緻的話,則是可以寫成:

size_t i = 0;
QTextDocument* pDoc = edit->document();
for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next())
{
  qDebug() << "Block #" << ++i;
  for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it)
  {
    QTextFragment fragment = it.fragment();
    if (fragment.isValid())
      qDebug() << "  > " << fragment.text();
  }
}

這樣的話就可以針對每個 fragment 來做拆分了~

至於一個 block 什麼時候會拆分成多個 fragment 呢?主要應該就是格式有變化(字體、顏色等等)的時候,就會拆成不同的 fragment;所以如果整個 block 都是同一個格式的話,那就只會有一個 fragment。


不過,個人覺得他這邊設計比較討厭的,是雖然 QTextDocumentQTextBlock 都是提供 begin() / end() 來操作,但是實際上兩者的操作邏輯、方法不完全一樣、沒有統一;同時,他們也都不符合 STL 的 iterator 的設計、所以無法使用標準的方法來操作。

再來,就是整個 QTextBlock 本身就是設計成唯獨的,所以上面整個流程都只能讀取、不能拿來修改資料… orz


格式(format)

先不管不能修改的問題,前面的例子基本上都只有處理文字的部分而已;而其他東西,主要則是格式。

QTextDocument 的設計裡面,有定義 QTextFormat文件)、以及衍生出來的 QTextCharFormatQTextBlockFormatQTextListFormatQTextTableFormat 等類別、用來紀錄文字以外的資訊。

QTextBlock 來說,會有對應段落格式的 QTextBlockFormat、以及對應文字格式的 QTextCharFormat 可以使用;而 QTextFragment 的話,則只有 QTextCharFormat

不過以圖片來說,實際上相關的資訊是透過 QTextImageFormat文件)這個繼承自 QTextCharFormat 的類別來儲存的,所以實際上要找到文件中的圖片的話,其實是要透過格式來找的!

像是透過下面的程式,就可以找到文件中所有的圖片了~

for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next())
{
  for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it)
  {
    QTextFragment fragment = it.fragment();
    QTextCharFormat charFmt = fragment.charFormat();
    if (charFmt.isImageFormat())
    {
      QTextImageFormat imageFmt = charFmt.toImageFormat();
      qDebug() << imageFmt.name();
    }
  }
}

QTextImageFormatname() 這個函式代表的是這張圖片的資源名稱。

至於其他的格式,也是可以用類似的方法來找、或是讀取對應的資料。只是因為這種寫法對於文件來說是唯獨的,所以雖然大多也都可以做修改、但是沒有辦法對原始文件造成有效的修改就是了。


使用 QTextCursor 修改文件

如果要透過程式修改 QTextDocument 的內容的話,基本上是要透過 QTextCursor文件)這個對應到文字編輯器中的輸入游標的類別來進行的。

他基本上就像編輯器中的輸入游標一樣,會存在於文件中的某個位置,在程式中可以透過 position() 這個函式來取得。

此外,他也還有另一個「anchor」的位置,用來處理文字的選取。當 anchor() 回傳的值和 position() 相同的時候,就代表沒有選取東西,不同的時候,則就有一個選取範圍。

而透過 QTextCursor 的提供的各種功能,就可以拿來調整文件的格式、刪除文件中的特定內容、插入新的內容了~同時,這樣的編輯方法也可以提供「undo」的功能,讓整個使用體驗更好。

在使用的時候,基本上就是先去將 QTextCursor 的位置或是選取區設定到想要的位置、然後再透過各種編輯的函式來針對文章內容作處理了~

比如說下面的程式碼就會在文件的一開始,加入一行「Hello World」:

QTextCursor cursor(pDoc);
cursor.insertText("Hello World\n");

如果想要插入到文章的最後,也可以先調整位置後再執行插入就可以了:

QTextCursor cursor(pDoc);
cursor.movePosition(QTextCursor::End);
cursor.insertText("Hello World\n");

他的 movePosition() 主要是透過 QTextCursor::MoveOperation、針對文件的結構來移動,在使用上應該也算是相當地方變了~

比如說想要選取整個文件的第一個文字的話,就可以寫成:

QTextCursor cursor(pDoc); cursor.insertText(Hello World\n); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

cursor.insertHtml(<font color=red>Hello</font>);

這邊先透過 movePosition() 把游標移到文章的開頭,然後再把游標以「KeepAnchor」 的狀態、移到目前文字的結尾,這樣就可以把第一個單字、也就是「Hello」選起來了~

而此時再透過 insertHtml() 插入一段用 HTML 來描述格式的文字後,就可以把本來的黑色「Hello」刪除、換成紅色的「Hello」了。

除了可以透過 movePosition() 來移動游標外,他也還有提供 setPosition() 這個函式,可以針對實際的位置(整數形式)來移動游標;在使用時可以針對需要來決定要用哪種方法,如果是要針對 fragment 或是 block 的 position() 回報的位置來做設定話,用 setPosition() 應該就會比較合適。

除了直接插入一般的文字外,也可以透過其他的 insertX() 來插入圖片、表格、清單、HTML 等等;同時,也可以插入 QTextDocument 定義的 frame、block、fragment。
此外,這邊也可以透過 setXFormat()mergeXFormat() 這類的函式來調製、設定文件的格式。

比如說,如果想要把整份文件的圖片大小都縮小一半的話,可以寫成下面的樣子:

QTextDocument* pDoc = edit->document();
QTextCursor cursor(pDoc);
for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next())
{
  for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it)
  {
    QTextFragment fragment = it.fragment();
    QTextCharFormat charFmt = fragment.charFormat();
    if (charFmt.isImageFormat())
    {
      QTextImageFormat imageFmt = charFmt.toImageFormat();
      imageFmt.setWidth(imageFmt.width() / 2);
      imageFmt.setHeight(imageFmt.height() / 2);

      cursor.setPosition(fragment.position());
      cursor.setPosition(fragment.position() + fragment.length(),
                         QTextCursor::KeepAnchor);
      cursor.setCharFormat(imageFmt);
    }
  }
}

這篇大概就先這樣了。

實際上,QTextDocument 真的滿複雜、功能也滿多的,所以這邊大致上也就是簡單地把主要功能帶一下而已了~其他的東西還是得去看官方文件的。

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *