2013-04-08 98 views
5

Tôi nhận thấy rằng tinh thần Tăng cường cung cấp một số giới hạn, trong câu hỏi ở đây trên SO có người dùng yêu cầu trợ giúp về tinh thần tăng cường và người dùng khác đã đưa ra câu trả lời được chỉ định rằng tinh thần tăng hoạt động tốt với tuyên bố chứ không phải với "văn bản chung" (Tôi xin lỗi nếu tôi không nhớ chính xác).Tinh thần tăng cường có thể xử lý Postscript/PDF như ngôn ngữ không?

Bây giờ tôi muốn suy nghĩ về Postscript và PDF về mã thông báo và đơn giản hóa cách tiếp cận định dạng này theo cách này, vấn đề là PDF là loại kết hợp giữa ngôn ngữ đánh dấu và ngôn ngữ lập trình với các bước nhảy và các bảng trong đó, và tôi không thể nghĩ về một cái gì đó tương tự khi xem xét các định dạng tệp phổ biến nhất như XML, mã C++ và các ngôn ngữ và định dạng khác.

Ngoài ra còn có một thực tế khác: Tôi thực sự không thể tìm thấy những người đã có một số kinh nghiệm với boost :: tinh thần wiriting một phân tích cú pháp pdf hoặc nhà văn, vì vậy tôi yêu cầu, tăng :: tinh thần nó có khả năng phân tích một Tệp PDF và xuất các phần tử dưới dạng mã thông báo?

+0

Tôi đã có mã C cho máy quét cấp 1 (không có <<>>) [tại đây] (https://groups.google.com/d/msg/comp.lang.postscript/XbxHv5rcFxc/OetXbfI4PQYJ) và một phần dịch sang postscript [ở đây] (https://groups.google.com/d/msg/comp.lang.postscript/u4QmuQZhrxU/LNF_r0PWX1EJ). –

+0

Tìm một số khác (trong postscript) [tại đây] (https://groups.google.com/d/msg/comp.windows.news/g1fs5ajR1YQ/FgW3DFKx0dUJ). –

Trả lời

11

Mặc dù điều này không liên quan gì đến Boost, hãy để tôi đảm bảo với bạn rằng việc phân tích cú pháp PDF (và PostScript) không đáng kể như bạn mong muốn. Giả sử bạn có đối tượng máy quét trả về một loạt mã thông báo. Các loại thẻ bạn sẽ nhận được từ máy quét là:

  • Chuỗi
  • Dict bắt đầu (< <)
  • Dict End (>>)
  • Tên (/ bất cứ điều gì)
  • Số
  • Mảng thập phân
  • Góc trái (<)
  • Góc bên phải (>)
  • Mảng bắt đầu ([)
  • Mảng cuối (])
  • Thủ tục bắt đầu ({)
  • Thủ tục kết thúc (})
  • Comment (% foo)
  • Lời

Máy quét của tôi là một hệ thống tự động hữu hạn với các trạng thái cho Bắt đầu, Nhận xét, Chuỗi, HexArray, Mã thông báo, DictEnd và Xong.

Cách bạn phân tích cú pháp PDF không phải bằng cách phân tích cú pháp PDF, mà bằng cách thực hiện nó. Với những thẻ bài, tôi "phân tích cú pháp" trông như thế này (trong 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); 
    } 
} 

Tôi cũng sẽ thêm rằng nếu bạn cung cấp cho mỗi PdfObject một Execute() phương pháp như vậy mà thực hiện lớp cơ sở là machine.Push(this)IsTerminal rằng trả false, REPL được dễ dàng hơn:

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;     
    } 
} 

có hỗ trợ nhiều hơn trong máy - Máy có một stack của PdfObject và một vài phương pháp để truy cập vào nó (Push, Pop, Mark, CountToMark, Index, Dup, Swap), cũng như ExecProcBegin và ExecProcEnd.

Ngoài ra, nó rất nhẹ. Điều duy nhất hơi lạ là PdfObject.FromToken có một mã thông báo và nếu nó là một kiểu nguyên thủy (số, chuỗi, tên, hex, bool) trả về một PdfObject tương ứng.Nếu không, nó lấy mã thông báo đã cho và tìm trong từ điển "bộ proc" tên thủ tục được liên kết với các đối tượng PdfProcedure. Vì vậy, khi bạn gặp các dấu hiệu << đó được nhìn lên trong một tập các proc và đi lên với mã này:

void DictBegin(PdfMachine machine) 
{ 
    machine.Push(new PdfMark(PdfMarkType.Dictionary)); 
} 

Vì vậy << thực sự có nghĩa là "đánh dấu sự chồng như sự khởi đầu của một cuốn từ điển >> được thú vị hơn.:

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); 
} 

Vì vậy >> Pops đến vạch từ điển gần thành một mảng sau đó đưa từng cặp vào từ điển. Bây giờ, tôi có thể làm điều này mà không phân bổ mảng. tôi có thể cặp chỉ pop, đưa chúng vào từ điển cho đến khi tôi đánh dấu, không nhận được tên hoặc tràn ngăn xếp.

Điều quan trọng là thực sự không có bất kỳ cú pháp nào trong PDF, cũng như không có bất kỳ cú pháp nào trong PostScript. Ít nhất là không nhiều như bạn muốn thông báo. Cú pháp thực sự duy nhất (và vòng lặp đọc-eval- (push) hiển thị nó) là '}'.

Vì vậy, khi bạn này là một PDF 14 0 obj << /Type /Annot /SubType /Square >> endobj những gì bạn thực sự nhìn thấy là một loạt các thủ tục:

  1. Đẩy 14
  2. Đẩy 0
  3. Execute obj (Pop hai con số và đẩy một "định nghĩa" vật).
  4. Execute điển bắt đầu
  5. Push/Loại
  6. Push/Annot
  7. Push/Loại con:
  8. Push/Quảng trường
  9. Execute cuối từ điển
  10. Execute endobj (bật các đối tượng trên và sau đó nhận được (không phải pop) tiếp theo, nếu thứ hai là một định nghĩa, thiết lập "giá trị" của nó cho đối tượng đầu tiên, khác ném).

Vì "endobj" là đầu cuối, đầu phân tích cú pháp và đầu ngăn xếp là kết quả.

Vì vậy, khi bạn được yêu cầu tra cứu đối tượng 14 trong tệp PDF, bảng tham chiếu chéo cho bạn biết nơi cần tìm, bạn tạo một Máy mới có con trỏ luồng tại vị trí đó và chạy nó. Nếu phần trên cùng của ngăn xếp là một đối tượng "định nghĩa", bạn đã thành công.

Về bây giờ bạn nên gật đầu nhưng không tin tưởng tôi, kể từ khi bạn đang suy nghĩ về suối PDF, mà trông như thế này:

<< [/key value]* >> stream ...raw data... endstream endobj 

Một lần nữa, không có cú pháp. Các proc stream nhìn ở phía trên cùng của ngăn xếp, mà nên là một PdfDict. Nếu có, nó tiêu thụ ký tự cho đến dòng mới tiếp theo (máy quét thực hiện điều này), lưu trữ vị trí tệp hiện tại trong luồng khi dữ liệu bắt đầu, đọc độ dài luồng từ dict (có thể khiến Máy khác được tạo mới) và bỏ qua qua cuối luồng và đẩy đối tượng luồng mới trên ngăn xếp. endstream là một no-op. Sự khác biệt duy nhất giữa PdfDict và PdfStream là PdfStream có vị trí bắt đầu và một bool nói rằng đó là luồng, nếu không tôi sẽ nhắm mục tiêu kép đối tượng.

PostScript gần giống hệt ngoại trừ môi trường thực thi phức tạp hơn một chút.Ví dụ, bạn cần một số ngăn xếp trong máy của bạn: một ngăn xếp tham số, một ngăn xếp từ điển và một ngăn xếp thực hiện. Từ đó, bạn ít nhiều chỉ cần ràng buộc tokenizer của bạn vào tập các thủ tục nguyên thủy cũng như từ exec, và sau đó hầu hết các thông dịch viên của bạn được viết bằng PS chính nó.

Nếu bạn đang nói về việc tăng cường, bạn đang xem C++, điều đó có nghĩa là bạn không thể nhanh chóng và mất trí nhớ như tôi, vì vậy bạn sẽ muốn sử dụng con trỏ thông minh hoặc tìm ra nơi bạn phạm vi và cẩn thận để vứt bỏ đồ vật thay vì ném chúng đi một cách vô cùng, nhưng đó chỉ là công cụ C++ bình thường.

Hiện tại, tôi tạo công cụ PDF cho công ty của mình. Tuy nhiên, trước đây tôi đã làm việc trên Acrobat phiên bản 1-4 và hầu hết những gì tôi mô tả chính xác là những gì Acrobat đã làm dưới mui xe (tốt, nhiều hơn hoặc ít hơn - đó là C, không phải C++, nhưng đó là cách tiếp cận tương tự).

Đối với bảng xref (hoặc luồng xref), bạn đọc đầu tiên - thông số cho bạn biết rằng nếu bạn chuyển sang EOF và quét lại, bạn sẽ thấy bảng bắt đầu của xref. Bạn phân tích cú pháp đó (đó là bài tập CS 101), phân tích cú pháp đoạn giới thiệu, tìm đến/Prev nếu có và lặp lại cho đến khi không có mục nhập nào khác/Trước đó. Điều đó cung cấp cho bạn một xref hoàn chỉnh để tìm kiếm các đối tượng.

Đối với văn bản - có một số cách tiếp cận mà bạn có thể thực hiện. Rõ ràng nhất là khi một đối tượng được tham chiếu, bạn tạo một đối tượng tham chiếu mới bằng cách gán mục nhập xref mới nhất có sẵn cho nó. Bất cứ khi nào các đối tượng tham chiếu đến các đối tượng khác để viết, chúng sẽ hỏi xem các đối tượng này có được tham chiếu hay không. Nếu có, họ viết tham chiếu (ví dụ: 14 0 R). Khi nói đến thời gian để viết một đối tượng tham chiếu, bạn sẽ có được con trỏ dòng hiện tại và lưu nó vào xref, sau đó viết <objnum> <generation> obj <object contents> endobj. Ví dụ: mã của tôi để viết một từ điển trông giống như sau:

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); 
    } 
} 

Tôi đã cắt nhỏ một chút bột để bạn có thể thấy lúa mì bên dưới. Ngữ cảnh là một đối tượng chứa một bảng xref mới cũng như một đối tượng để ghi vào các luồng tự động xử lý các quy tắc đường thẳng thích hợp mới, thụt đầu dòng, gói dòng, v.v.

Những gì bạn sẽ thấy là những điều cơ bản ở đây là thẳng về phía trước, nếu không nhỏ. Và bây giờ là khi bạn nên tự hỏi mình câu hỏi, "nếu nó tầm thường, làm thế nào đến đó không phải là (nghiêm trọng) cạnh tranh cho Acrobat trên thị trường? Câu trả lời là mặc dù nó tầm thường, nó vẫn còn dễ dàng để viết PDF mà aren Thách thức thực sự là để có thể tôn vinh các spec và chắc chắn rằng bạn bao gồm tất cả các giá trị cần thiết trong một từ điển và rằng họ đang ở trong phạm vi và ngữ nghĩa chính xác. định dạng - được xác định khá rõ ràng - là một đống mã trường hợp đặc biệt trong thư viện của tôi để quản lý nơi người khác đã sửa đổi nó một cách royally.

Tôi có thể (và có lẽ nên) viết một cuốn sách về cách thực hiện điều này.Trong khi rất nhiều mã rìa là grubby, cấu trúc tổng thể ure có thể rất đẹp.

tl; dr - Nếu bạn đang nghĩ đến trình phân tích cú pháp gốc đệ quy cho PDF, bạn đang nghĩ quá khó. Tất cả bạn cần là một tokenizer và REPL đơn giản.

+0

điều này trông thực sự thú vị, 2 điều không rõ ràng đối với tôi: với "dòng đọc của bạn theo dòng + nhảy khi bạn cần" tiếp cận cách bạn quản lý phần 'xref'? Làm thế nào về việc viết một pdf? Làm thế nào bạn quản lý viết một khi bạn cần phải viết nó từng dòng mà không cần nhảy? – user2244984

+0

+1 Câu trả lời xuất sắc, đồng ý đầy đủ. Tôi thích tinh thần, nhưng sẽ không sử dụng nó ở đây (tốt, có thể cho lexing với Spirit Lex). @ user2244984 Đối với văn bản, bạn sẽ luôn luôn có (và cần) một đại diện inmemory để đi qua trong thứ tự dòng đầu ra. – sehe