use*_*984 5 pdf parsing postscript boost-spirit lexer
我注意到Boost精神提供了一些限制,在这里的一个问题上有一个用户请求有关提升精神的帮助,另一个用户给出了答案指定,提升精神适用于语句而不是"通用文本"(I对不起,如果我没记错的话.
现在我想用令牌来考虑Postscript和PDF,并以这种方式简化我对这种格式的处理方式,问题在于PDF是标记语言和编程语言之间的混合,其中包含跳转和表格,在考虑最流行的文件格式(如XML,C++代码和其他语言和格式)时,我无法想到类似的东西.
还有另外一个事实:我真的找不到那些在使用boost :: spirit wiriting pdf解析器或编写器方面有经验的人,所以我问,boost :: spirit它能够解析PDF文件和输出作为代币的元素?
pli*_*nth 11
虽然这与Boost无关,但请允许我向您保证,PDF(和PostScript)的解析与您想要的一样简单.假设您有一个返回一系列标记的扫描仪对象.您将从扫描仪获得的令牌类型是:
我的扫描仪是一个有限状态自动机,具有Start,Comment,String,HexArray,Token,DictEnd和Done的状态.
解析PDF的方式不是解析它,而是通过执行它.鉴于这些令牌,我的"解析器"看起来像这样(在C#中):
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我还要补充一点,如果你给每个PdfObject一个Execute()方法,使得基类实现是machine.Push(this)并且IsTerminal返回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;
}
}
Run Code Online (Sandbox Code Playgroud)
机器中有更多的支持 - 机器有一堆PdfObject和一些访问它的方法(Push,Pop,Mark,CountToMark,Index,Dup,Swap),以及ExecProcBegin和ExecProcEnd.
除此之外,它非常轻.唯一有点奇怪的是PdfObject.FromToken获取一个令牌,如果它是一个原始类型(数字,字符串,名称,十六进制,布尔)返回相应的PdfObject.否则,它接受给定的令牌并查找与PdfProcedure对象关联的过程名称的"proc set"字典.因此,当您遇到在<<proc集中查找的令牌并提出此代码时:
void DictBegin(PdfMachine machine)
{
machine.Push(new PdfMark(PdfMarkType.Dictionary));
}
Run Code Online (Sandbox Code Playgroud)
所以<<真的意味着"将堆栈标记为字典的开头.>>更有趣:
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);
}
Run Code Online (Sandbox Code Playgroud)
因此>>弹出最近的字典标记到一个数组然后将每一对放入字典.现在,我可以在不分配数组的情况下完成此操作.我可以弹出对,将它们放入字典中,直到我达到标记,无法获得名称或使堆栈下溢.
重要的一点是,PDF中没有任何语法,PostScript也没有.至少没有你注意到的那么多.唯一真正的语法(和read-eval-(push)循环显示它)是'}'.
所以当你这是一个PDF时14 0 obj << /Type /Annot /SubType /Square >> endobj,你真正看到的是一系列程序:
由于"endobj"是终端,解析结束并且堆栈的顶部是结果.
因此,当要求您在PDF中查找对象14时,交叉引用表会告诉您在哪里寻找,您在该位置创建一个带有流指针的新机器并运行它.如果堆栈的顶部是"定义"对象,那么您已经成功了.
现在你应该点头但不信任我,因为你正在考虑PDF流,如下所示:
<< [/key value]* >> stream ...raw data... endstream endobj
Run Code Online (Sandbox Code Playgroud)
同样,没有语法.proc stream查看堆栈的顶部,它应该是PdfDict.如果是,它会消耗字符直到下一个换行符(扫描仪执行此操作),将当前文件位置存储在流中作为数据开始,从dict读取流长度(这可能导致另一台机器被新建),并跳过超过流的末尾并将新流对象推送到堆栈上.endstream是一个无操作.PdfDict和PdfStream之间唯一的区别是PdfStream有一个起始位置,bool说它是一个流,否则我是对象的双用途.
PostScript几乎完全相同,只是执行环境稍微复杂一些.例如,您的机器中需要多个堆栈:参数堆栈,字典堆栈和执行堆栈.从那里,你或多或少只是将你的标记化器绑定到一组基本过程以及单词exec,然后你的大部分解释器都是用PS本身编写的.
如果你在谈论boost,那你就是在看C++,这意味着你不能像我一样快速和松散地使用内存,所以你要么想要使用智能指针,要么找出你的范围是什么并小心处理对象而不是轻率地抛弃它们,但这只是正常的C++内容.
目前,我在.NET中为我的公司制作PDF工具,但在以前的生活中我使用的是Acrobat版本1-4,而我所描述的大部分内容正是Acrobat在幕后所做的(好吧,或多或少 - 它是C,而不是C++,但它是相同的方法).
关于外部参照表(或外部参照流),您首先阅读 - 规范告诉您如果跳转到EOF并向后扫描,则会找到外部参照表的开头.你解析它(这是一个CS 101分配),解析预告片,寻找/上一个(如果有的话)并重复直到没有更多/上一个条目.这为您提供了一个用于查找对象的完整外部参照.
至于写作 - 你可以采取多种方法.最明显的一点是,当要引用一个对象时,可以通过为其指定最新的可用外部参照条目来创建新的引用对象.每当对象引用其他对象进行写入时,它们会询问是否引用了这些对象.如果是,则编写参考(即14 0 R).当编写引用对象时,您将获得当前流指针并将其存储在外部参照中,然后写入<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);
}
}
Run Code Online (Sandbox Code Playgroud)
我已经砍掉了一些谷壳,所以你可以看到下面的小麦.上下文是一个对象,它包含一个新的外部参照表以及一个用于写入流的对象,这些流可以自动处理适当的换行规则,缩进,换行等.
你应该看到的是,这里的基础知识是直截了当的,如果不是微不足道的话.而现在你应该问自己一个问题,"如果它是微不足道的,为什么市场上没有更多(严重)的Acrobat竞争?答案是,即使它是微不足道的,它仍然很容易写出不是符合规范,Acrobat处理其中的大多数.真正的挑战是能够遵守规范,并确保在字典中包含所有必需的值,并且它们在范围和语义上是正确的.地狱,甚至是日期时间格式 - 这是非常明确的 - 是我的库中的一大堆特殊情况代码,用于管理其他人在王室中搞砸了它的地方.能够生成一致正确的PDF很难并消耗PDF格式的垃圾在世界上更难.
我可以(也可能应该)写一本关于如何做到这一点的书.虽然很多边缘代码都是肮脏的,但总体结构可能非常漂亮.
tl; dr - 如果您正在考虑PDF的递归下降解析器,那么您的想法太难了.您只需要一个tokenizer和一个简单的REPL.