すべてのコストでテキスト:RTF

さて、私たちはさまざまなデータ形式からテキストを取得することについての研究を続けています。 少し前まで、 zip形式のxmlベースのファイル (odtとdocx)からテキストを抽出する方法と、今週の初めからpdfからテキストを抽出する方法を学びました。 今日は約束のrtfを続けます。

リッチテキスト形式(別名rtf)は、テキストデータを表すための非常に複雑な形式ではありませんが、かなり忘れられていると思われるかもしれません 。 まあ、テキストを取得するのは比較的簡単ですが、その歴史から:最初のバージョンから現在の1.9.1まで-300ページの公式ドキュメントと膨大な数のアドオンを取得しました。 それらを回避してみましょう...

そして中身は何ですか?


それが起こったので、rtfファイルの中を見て、中身を見てみましょう:



何が見えますか? 私たちのお気に入りの詩「帆」が見えます。 最初はテキスト形式の8ビットデータ形式が表示されます。 これはすでに喜ばしいことです。ソースデータ内のテキストを使用すると、何が起きているかを理解するのがはるかに簡単になります。 それでは、このデータそのものの読み方を理解しましょう。 これを行うために、このトピックに関する少しの理論を説明します。

rtfは、ネストされたセットにグループ化できる制御ワードで構成されていると想定しています。 制御語はバックスラッシュ( \ )で始まり、グループは中括弧( {および} )で囲まれます。

制御ワードは、英語のアルファベットの文字のシーケンス( aからz )で構成され、数値パラメーター(負の場合もある)で補完できます。 または、単語に英数字以外のASCII文字を1つ含めることができます。 これらの規則に当てはまらないものは、制御ワードの一部ではありません。 したがって、 \rtf1\ansi\ansicpg1251の形式のシーケンスは、パラメーター1(形式のメジャーバージョン)のrtf 、 ansi (現在のエンコード)、およびパラメーター1251のansicpg ( ansicpg現在のコードページ番号)の3つの単語に簡単に分割されます。 Windows-1251)。

グループ化されたセットは、制御ワードの範囲を定義します。 したがって、中括弧内に記述されている制御ワードは、その中とすべての子サブセット内でのみ機能します。 現在どの単語が起こっているのかを正しく理解するには、制御単語のスタックを保持する必要があります。 中括弧を開くときは、スタック内に新しい配列要素を作成します。この配列要素にすぐにスタックの前のレイヤーのデータを追加し、ブレースを閉じるときに最上位のレイヤーを削除します。

また、新しいサブグループを作成するのではなく、パラメータゼロを追加することで一部の制御ワードを閉じることができることにも注意してください。 たとえば、次のオプションは同等です。 This is {\b bold} text 、 This is \b bold \b0 text = This is bold textです。

テキストはどこで入手できますか?


私たちは私たちのために新しい形式のデバイスに会いました、今、私たちは自分自身に質問をします、どこでテキストを入手するか。 すべては見た目ほど複雑ではありません-現在のシーケンスが制御ワードとして識別されていない場所でテキストを使用する必要があります。 もちろん、いくつかの例外があります。

まず、rtfファイルのソースエンコーディングがANSIであることに注意する価値があります。したがって、飾り気なく、英語のテキストのみが保存されます。 少なくともロシア語のテキストに興味があり、さらに優れたUnicodeに興味がありますか? 真実は、rtfは古い形式ですが、両方を保存するのに適しているということです。

したがって、rtfでは、ASCIIテーブルの後半、128以上のテーブルを使用する可能性があります。 もちろん、現在のエンコーディング(制御ワード\ansicpg )が与えられます。 これを行うために、RTFに\'hhの形式のシーケンスが導入されました。hhはASCIIテーブルの文字のバイナリ16進コードです。

2番目のより興味深いオプションは、ユニコードでエンコードされたデータです。 それらの場合、形式には数値パラメータABCDを含む簡潔な短いキーワード\uABCDが含まれます。 この場合のABCDは、10進数のUnicode文字コードです。 あなたが気づいたかもしれないように、すべてが再び簡単です。

シンプルですが、それほどではありません。 \ucNには、Unicodeに密接に関連する別の\ucNキーワードがあります。 実際、RTF形式は、このファイルを開く必要のある古いデバイスとの互換性を非常に熱心にサポートしています。 オプションとして、そのようなデバイス(たとえば、Windows 3.11を搭載したコンピューター:)はUnicodeを読み取ることができません。どうすればよいですか? これを行うには、 \uキーワードで暗号化された各ユニコード文字の後に、rtf-viewerが現在のデータを表示または解析できない場合に表示されるゼロから数文字を示すことができます(ドキュメントによると、ビューアが正しく表示できない場合)データ、彼はそれらをスキップする必要があります)。

この点で、ほとんどの最近のエディターは、ユニコード制御ワードの後に​​、現在の文字の代わりに表示したい記号として疑問符を付けます。 ただし、オプションは可能Lab\u915GValue 。例: Lab\u915GValue Unicodeを表示できない場合に表示する必要がある文字数を自問してみましょう。 ここでもすべてはそれほど複雑ではありません-上記のキーワード\ucNはパラメーターNとしてこの値を提供します。 つまり Unicodeデータの前には、 \uc1ようなものが必ず表示されます。これにより、Unicodeの後に1文字スキップするように指示されます。

読んでみよう!


蓄積したデータは、最初のrtfファイルを読み取るのに十分なようです。 行こう:

  1. 関数 rtf_isPlainText ( $ s ) {
  2. $ failAt = array ( "*" 、 "fonttbl" 、 "colortbl" 、 "datastore" 、 "themedata" ) ;
  3. for ( $ i = 0 ; $ i < count ( $ failAt ) ; $ i ++ )
  4. if ( ! empty ( $ s [ $ failAt [ $ i ] ] ) ))は falseを 返し ます 。
  5. trueを 返し ます 。
  6. }
  7. 関数 rtf2text ( $ filename ) {
  8. $ text = file_get_contents ( $ filename ) ;
  9. if (! strlen ( $ text ) )
  10. return "" ;
  11. $ document = "" ;
  12. $ stack = array ( ) ;
  13. $ j = -1 ;
  14. for ( $ i = 0 ; $ i < strlen ( $ text ) ; $ i ++ ) {
  15. $ c = $ text [ $ i ] ;
  16. スイッチ ( $ c ) {
  17. ケース 「 \\ 」 :
  18. $ nc = $ text [ $ i + 1 ] ;
  19. if ( $ nc == '\\' && rtf_isPlainText ( $ stack [ $ j ] ) ) $ document 。= '\\' ;
  20. elseif ( $ nc == '〜' && rtf_isPlainText ( $ stack [ $ j ] ) )) $ document 。= '' ;
  21. elseif ( $ nc == '_' && rtf_isPlainText ( $ stack [ $ j ] ) ) $ document 。= '-' ;
  22. elseif ( $ nc == '*' ) $ stack [ $ j ] [ "*" ] = true ;
  23. elseif ( $ nc == "'" ) {
  24. $ hex = substr ( $ text 、 $ i + 2、2 ) ;
  25. if ( rtf_isPlainText ( $ stack [ $ j ] ) )
  26. $ document 。= html_entity_decode ( "&#" 。 hexdec ( $ hex ) 。 ";" ) ;
  27. $ i + = 2 ;
  28. } elseif ( $ nc > = 'a' && $ nc <= 'z' || $ nc > = 'A' && $ nc <= 'Z' ) {
  29. $ word = "" ;
  30. $ param = null ;
  31. for ( $ k = $ i + 1 、 $ m = 0 ; $ k < strlen ( $ text ) ; $ k ++、 $ m ++ ) {
  32. $ nc = $ text [ $ k ] ;
  33. if ( $ nc > = 'a' && $ nc <= 'z' || $ nc > = 'A' && $ nc <= 'Z' ) {
  34. if ( empty ( $ param ) )
  35. $ワード = $ nc ;
  36. 他に
  37. 休憩 ;
  38. } elseif ( $ nc > = '0' && $ nc <= '9' )
  39. $ param 。= $ nc ;
  40. elseif ( $ nc == '-' ) {
  41. if ( empty ( $ param ) )
  42. $ param 。= $ nc ;
  43. 他に
  44. 休憩 ;
  45. } その他
  46. 休憩 ;
  47. }
  48. $ i + = $ m - 1 ;
  49. $ toText = "" ;
  50. switch ( strtolower ( $ word ) ) {
  51. ケース 「u」 :
  52. $ toText 。= html_entity_decode ( "&#x" 。dechex ( $ param ) 。 ";" ) ;
  53. $ ucDelta = @ $ stack [ $ j ] [ "uc" ] ;
  54. if ( $ ucDelta > 0 )
  55. $ i + = $ ucDelta ;
  56. 休憩 ;
  57. ケース "par" : ケース "page" : ケース "column" : ケース "line" : ケース "lbr" :
  58. $ toText 。= " \ n " ;
  59. 休憩 ;
  60. ケース "emspace" : ケース "enspace" : ケース "qmspace" :
  61. $ toText 。= "" ;
  62. 休憩 ;
  63. case "tab" : $ toText 。= " \ t " ; 休憩 ;
  64. case "chdate" : $ toText 。= date ( "mdY" ) ; 休憩 ;
  65. case "chdpl" : $ toText 。= date ( "l、j F Y" ) ; 休憩 ;
  66. case "chdpa" : $ toText 。= date ( "D、j M Y" ) ; 休憩 ;
  67. case "chtime" : $ toText 。= date ( "H:i:s" ) ; 休憩 ;
  68. case "emdash" : $ toText 。= html_entity_decode ( "&mdash;" ) ; 休憩 ;
  69. case "endash" : $ toText 。= html_entity_decode ( "&ndash;" ) ; 休憩 ;
  70. case "bullet" : $ toText 。= html_entity_decode ( "&#149;" ) ; 休憩 ;
  71. case "lquote" : $ toText 。= html_entity_decode ( "&lsquo;" ) ; 休憩 ;
  72. case "rquote" : $ toText 。= html_entity_decode ( "&rsquo;" ) ; 休憩 ;
  73. case "ldblquote" : $ toText 。= html_entity_decode ( "&laquo;" ) ; 休憩 ;
  74. case "rdblquote" : $ toText 。= html_entity_decode ( "&raquo;" ) ; 休憩 ;
  75. デフォルト :
  76. $ stack [ $ j ] [ strtolower ( $ word ) ] = empty ( $ param ) ? true : $ param ;
  77. 休憩 ;
  78. }
  79. if ( rtf_isPlainText ( $ stack [ $ j ] ) )
  80. $ document 。= $ toText ;
  81. }
  82. $ i ++;
  83. 休憩 ;
  84. ケース "{" :
  85. array_push ( $ stack 、 $ stack [ $ j ++ ] ) ;
  86. 休憩 ;
  87. ケース 「}」 :
  88. array_pop ( $スタック ) ;
  89. $ j- ;
  90. 休憩 ;
  91. case '\ 0' : case '\ r' : case '\ f' : case '\ n' : break ;
  92. デフォルト :
  93. if ( rtf_isPlainText ( $ stack [ $ j ] ) )
  94. $ドキュメント = $ c ;
  95. 休憩 ;
  96. }
  97. }
  98. $ documentを 返し ます 。
  99. }
GitHubにコメントを付けてコードを取得できます 。

おわりに


最後に何がありますか? このコードはほとんどのrtfファイルを正しく処理しますが、改善する方法がいくつかあります。 まず、非テキストデータに追加のクリッピングを追加する価値があります。フォント、カラーパレット、スキン、バイナリデータ、および「できない場合は読み上げない」とマークされているすべて( \* )のみを切り取ります。 次に、 \'hh形式のキーワードをより正確に表示するために、エンコーディングとコードページを解析する価値があります。

次は? さらに、fb2、epubなどの電子書籍形式に触れたいと思います。 この点に関して、読者に助けを求めたいと思います。第一に、他のどの形式の電子書籍を見る価値があるのか​​、第二に、指定した形式のファイルをもっと見つけることができる場所です。 事前に感謝します:)

参照:

Source: https://habr.com/ru/post/J70119/


All Articles