これはBoostとは関係ありませんが、PDF(とPostScript)の解析は、あなたが望むほど簡単ではないことを保証します。一連のトークンを返すスキャナオブジェクトがあるとします。あなたがスキャナから取得するトークンの種類は次のとおりです。
- 文字列
- 辞書です(< <)開始
- Dictのエンド(>>)
- 名(/何でも)
- 数
- 16進数配列
- 左角(<)
- ライトアングル(>)
- アレイ開始([)
- アレイ端(])
- 手順始める({)
- 手順端(})
- コメント(%のFOO)
- ワード
マイスキャナーは、開始、コメント、文字列、HexArray、トークン、DictEnd、およびDoneの状態を持つ有限状態オートマトンです。
あなたがPDFを解析する方法は、それを解析するのではなく、実行することです。これらのトークンを考えると、私の「パーサは」(C#で)次のようになります。あなたはすべてのPdfObjectに基本クラスの実装はmachine.Push(this)
とIsTerminal
であるように、execute()メソッドを与える場合
while (true) {
MLPdfToken = scanner.GetToken();
if (token == null)
return MachineExit.EndOfFile;
PdfObject obj = PdfObject.FromToken(token);
PdfProcedure proc = obj as PdfProcedure;
if (proc != null)
{
if (IsExecuting())
{
if (token.Type == PdfTokenType.RBrace)
proc.Execute(this);
else
Push(obj);
}
else {
proc.Execute(this);
}
if (proc.IsTerminal)
return Machine.ParseComplete;
}
else {
Push(obj);
}
}
また、私はあることを追加します戻りfalse
、REPLを簡単に取得します。
while (true) {
MLPdfToken = scanner.GetToken();
if (token == null)
return MachineExit.EndOfFile;
PdfObject obj = PdfObject.FromToken(token);
if (IsExecuting())
{
if (token.Type == PdfTokenType.RBrace)
obj.Execute(this);
else
Push(obj);
}
else {
obj.Execute(this);
if (obj.IsTerminal)
return Machine.ParseComplete;
}
}
より多くのサポートがマシンにあります - マシンはPdfObjectのスタックとそれにアクセスするためのいくつかの方法がある(プッシュ、ポップを、マーク、CountToMark、インデックス、DUP、スワップ)は、 ExecProcBeginとExecProcEndがあります。
それを超えると、非常に軽いです。わずかに奇妙なのは、PdfObject.FromToken
がトークンを取り、それがプリミティブ型(数値、文字列、名前、16進数、ブール)ならば、対応するPdfObjectを返します。それ以外の場合は、指定されたトークンをとり、PdfProcedure
オブジェクトに関連付けられたプロシージャ名の「proc set」ディクショナリを検索します。あなたはprocのセットの中で検索し、このコードを思い付くますトークン<<
遭遇したときに:
void DictBegin(PdfMachine machine)
{
machine.Push(new PdfMark(PdfMarkType.Dictionary));
}
ので<<
が本当に意味の「辞書のスタートとしてスタックをマーク>>
をより面白くなります。:
void DictEnd(PdfMachine machine)
{
PdfDict dict = new PdfDict();
// PopThroughMark pops the entire stack up to the first matching mark,
// throws an exception if it fails.
PdfObject[] arr = machine.PopThroughMark(PdfMarkType.Dictionary);
if ((arr.Length & 1) != 0)
throw new PdfException("dictionaries need an even number of objects.");
for (int i=0; i < arr.Length; i += 2)
{
PdfObject key = arr[i], val = arr[i + 1];
if (key.Type != PdfObjectType.Name)
throw new PdfException("dictionaries need a /name for the key.");
dict.put((PdfName)key, val);
}
machine.Push(dict);
}
は、それでは辞書に各ペアを置く配列への最寄りの辞書マークまで>>
ポップが。今、私は配列を割り当てることなく、これを行っている可能性があります。私はただのポップペアをできた、辞書にそれらを置きます私がマークを打つか、名前を得ることができないか、スタックのアンダーフロー。
重要なテイクアウェイは、実際にPDFに構文がなく、PostScriptには何もありません。少なくともあなたが気づくほどではありません。唯一の実際のSyntax(そしてread-eval-(push)ループはそれを示しています)は '}'です。 14
プッシュが0
objが(2つの数値をポップし、「定義」を押して実行し
- プッシュ:
は、だから、これはどのようなあなたが本当に見する一連の手順でPDF 14 0 obj << /Type /Annot /SubType /Square >> endobj
ときオブジェクト)。
- 辞書は
- プッシュ/タイプ
- プッシュ/ Annotの
- プッシュ/サブ
- プッシュ/スクエア
- は((トップオブジェクトをポップして、取得endobjを実行し
- 辞書エンドを実行し始める実行ポップしない)、2番目が定義の場合、最初のオブジェクトに "value"を設定し、それ以外の場合はスローします。
"endobj"はターミナルなので、解析が終了し、スタックの先頭が結果になります。
したがって、PDF内のオブジェクト14を検索するように求められたら、相互参照テーブルはどこにシークするかを指示し、その場所にストリームポインタを持つ新しいマシンを作成して実行します。スタックの先頭が「定義」オブジェクトであれば、成功しました。
<< [/key value]* >> stream ...raw data... endstream endobj
は再び、構文はありません。今、あなたはうなずいていますが、このように見えるPDFストリーム、考えていることから、私を信頼していないする必要がありますについて
。 proc stream
は、スタックの先頭を調べます。これはPdfDictでなければなりません。そうであれば、次の改行(スキャナがこれを行う)まで文字を消費し、データの開始としてストリームに現在のファイル位置を格納し、dictからストリーム長を読み込み(別のMachineを新しくする可能性があります)、スキップしますストリームの終わりを過ぎてスタックに新しいストリームオブジェクトをプッシュします。エンドストリームはノーオペレーションです。 PdfDictとPdfStreamの唯一の違いは、PdfStreamには開始位置があり、それがストリームであることを示すboolがあります。そうでなければ、私はオブジェクトを二重目的とします。
PostScriptは、実行環境がもう少し複雑であることを除いてほとんど同じです。たとえば、マシンには、パラメータスタック、ディクショナリスタック、および実行スタックという複数のスタックが必要です。そこから、多かれ少なかれあなたのトークナイザを基本的なプロシージャのセットとexecという単語にバインドするだけで、ほとんどのインタープリタはPS自身で書かれます。
あなたがブーストについて話しているなら、あなたはC++を見ています。つまり、あなたは私と同じくらい速くてゆるいメモリであることができないので、スマートポインタを使うか、スコープはどこにあるのか、オブジェクトを捨てるのではなく、オブジェクトを処分することに注意してください。しかし、これは普通のC++のものです。
現在のところ私は自分の会社のためのPDFツールを.NETで作っていますが、以前の人生ではAcrobatのバージョン1-4で作業しましたが、私が書いたことの大部分は、 C++ではなくCでしたが、同じアプローチです)。
xrefテーブル(または外部参照ストリーム)に関しては、最初にそれを読んでいます - 仕様では、EOFにジャンプしてスキャンバックすると、xrefテーブルの開始点がわかります。あなたはそれを解析し(CS 101の割り当てです)、予告編を解析し、もしあれば/ Prevを探して、それ以上/ Prevの項目がなくなるまで繰り返す。これは、オブジェクトを検索するための完全な外部参照を提供します。
執筆に関しては、あなたが取ることができるアプローチがいくつかあります。最も明白なのは、オブジェクトが参照されることになっているときに、利用可能な最新のxrefエントリを割り当てることによって、新しい参照オブジェクトを作成するということです。オブジェクトが書き込みのために他のオブジェクトを参照するときはいつでも、これらのオブジェクトが参照されているかどうかを尋ねます。存在する場合は、参照番号(14 0 R
)を記述します。参照されるオブジェクトを書き込む時間が来たら、現在のストリームポインタを取得してxrefに格納し、次に<objnum> <generation> obj <object contents> endobj
と書きます。たとえば、辞書を書くために私のコードは次のようになります。あなたが下に小麦を見ることができるので、私はいくつかのもみ殻をみじん切りしまし
public override ToStream(PdfStreamingContext context)
{
if (context.HasReference(this)) // is object referenced in xref
{
PdfUtils.WriteObjectDefinitionBegin(this, context);
}
context.Writer.Indent();
context.Writer.WriteLine("<<");
WriteContents(context);
context.Writer.Exdent();
context.Writer.Writeline(">>");
if (context.HasReference(this))
{
PdfUtils.WriteObjectDefinitionEnd(this, context);
}
}
。コンテキストは、新しいxrefテーブルと、適切な改行、インデント、行折り返しなどを自動的に処理するストリームへの書き込みオブジェクトを保持するオブジェクトです。
ここでの基本事項は簡単ではないにしても、まっすぐであることです。 「今のところ、Acrobatの市場での競争がそれほどではないのはどうですか?答えは簡単ですが、まだ簡単なことではないPDFを書くことができます。実際の課題は、スペックを尊重し、必要な値をすべて辞書に入れて、それらが範囲内で意味的に正しいことを確認することです。書式は非常によく指定されていますが、私の図書館では、他の人がそれを嫌うところを管理するための特別なケースコードの盛り上がりです。PDFを一貫して正確に生成できることは難しく、PDFの海でゴミを消費します
これを行う方法についての本を書くことはできましたが(おそらくそうすべきです)、フリンジコードの多くは厄介ですが、構造全体ureは非常にきれいです。
tl; dr - PDFの再帰的降下パーサーを考えているなら、あまりにも難しいと思っています。必要なのはトークナイザと単純なREPLだけです。
私はレベル1のスキャナ(<<>>はありません)[こちら](https://groups.google.com/d/msg/comp.lang.postscript/XbxHv5rcFxc/OetXbfI4PQYJ)のCコードを取得しています。ポストスクリプトへの部分的な翻訳[こちら](https://groups.google.com/d/msg/comp.lang.postscript/u4QmuQZhrxU/LNF_r0PWX1EJ)。 –
別のもの(追記)[こちら](https://groups.google.com/d/msg/comp.windows.news/g1fs5ajR1YQ/FgW3DFKx0dUJ)が見つかりました。 –