psparser.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. #!/usr/bin/env python
  2. import sys
  3. import re
  4. from utils import choplist
  5. STRICT = 0
  6. ## PS Exceptions
  7. ##
  8. class PSException(Exception):
  9. pass
  10. class PSEOF(PSException):
  11. pass
  12. class PSSyntaxError(PSException):
  13. pass
  14. class PSTypeError(PSException):
  15. pass
  16. class PSValueError(PSException):
  17. pass
  18. ## Basic PostScript Types
  19. ##
  20. ## PSObject
  21. ##
  22. class PSObject(object):
  23. """Base class for all PS or PDF-related data types."""
  24. pass
  25. ## PSLiteral
  26. ##
  27. class PSLiteral(PSObject):
  28. """A class that represents a PostScript literal.
  29. Postscript literals are used as identifiers, such as
  30. variable names, property names and dictionary keys.
  31. Literals are case sensitive and denoted by a preceding
  32. slash sign (e.g. "/Name")
  33. Note: Do not create an instance of PSLiteral directly.
  34. Always use PSLiteralTable.intern().
  35. """
  36. def __init__(self, name):
  37. self.name = name
  38. return
  39. def __repr__(self):
  40. return '/%s' % self.name
  41. ## PSKeyword
  42. ##
  43. class PSKeyword(PSObject):
  44. """A class that represents a PostScript keyword.
  45. PostScript keywords are a dozen of predefined words.
  46. Commands and directives in PostScript are expressed by keywords.
  47. They are also used to denote the content boundaries.
  48. Note: Do not create an instance of PSKeyword directly.
  49. Always use PSKeywordTable.intern().
  50. """
  51. def __init__(self, name):
  52. self.name = name
  53. return
  54. def __repr__(self):
  55. return self.name
  56. ## PSSymbolTable
  57. ##
  58. class PSSymbolTable(object):
  59. """A utility class for storing PSLiteral/PSKeyword objects.
  60. Interned objects can be checked its identity with "is" operator.
  61. """
  62. def __init__(self, klass):
  63. self.dict = {}
  64. self.klass = klass
  65. return
  66. def intern(self, name):
  67. if name in self.dict:
  68. lit = self.dict[name]
  69. else:
  70. lit = self.klass(name)
  71. self.dict[name] = lit
  72. return lit
  73. PSLiteralTable = PSSymbolTable(PSLiteral)
  74. PSKeywordTable = PSSymbolTable(PSKeyword)
  75. LIT = PSLiteralTable.intern
  76. KWD = PSKeywordTable.intern
  77. KEYWORD_PROC_BEGIN = KWD('{')
  78. KEYWORD_PROC_END = KWD('}')
  79. KEYWORD_ARRAY_BEGIN = KWD('[')
  80. KEYWORD_ARRAY_END = KWD(']')
  81. KEYWORD_DICT_BEGIN = KWD('<<')
  82. KEYWORD_DICT_END = KWD('>>')
  83. def literal_name(x):
  84. if not isinstance(x, PSLiteral):
  85. if STRICT:
  86. raise PSTypeError('Literal required: %r' % x)
  87. else:
  88. return str(x)
  89. return x.name
  90. def keyword_name(x):
  91. if not isinstance(x, PSKeyword):
  92. if STRICT:
  93. raise PSTypeError('Keyword required: %r' % x)
  94. else:
  95. return str(x)
  96. return x.name
  97. ## PSBaseParser
  98. ##
  99. EOL = re.compile(r'[\r\n]')
  100. SPC = re.compile(r'\s')
  101. NONSPC = re.compile(r'\S')
  102. HEX = re.compile(r'[0-9a-fA-F]')
  103. END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
  104. END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
  105. HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
  106. END_NUMBER = re.compile(r'[^0-9]')
  107. END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
  108. END_STRING = re.compile(r'[()\134]')
  109. OCT_STRING = re.compile(r'[0-7]')
  110. ESC_STRING = {'b': 8, 't': 9, 'n': 10, 'f': 12, 'r': 13, '(': 40, ')': 41, '\\': 92}
  111. class PSBaseParser(object):
  112. """Most basic PostScript parser that performs only tokenization.
  113. """
  114. BUFSIZ = 4096
  115. debug = 0
  116. def __init__(self, fp):
  117. self.fp = fp
  118. self.seek(0)
  119. return
  120. def __repr__(self):
  121. return '<%s: %r, bufpos=%d>' % (self.__class__.__name__, self.fp, self.bufpos)
  122. def flush(self):
  123. return
  124. def close(self):
  125. self.flush()
  126. return
  127. def tell(self):
  128. return self.bufpos+self.charpos
  129. def poll(self, pos=None, n=80):
  130. pos0 = self.fp.tell()
  131. if not pos:
  132. pos = self.bufpos+self.charpos
  133. self.fp.seek(pos)
  134. print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
  135. self.fp.seek(pos0)
  136. return
  137. def seek(self, pos):
  138. """Seeks the parser to the given position.
  139. """
  140. if 2 <= self.debug:
  141. print >>sys.stderr, 'seek: %r' % pos
  142. self.fp.seek(pos)
  143. # reset the status for nextline()
  144. self.bufpos = pos
  145. self.buf = ''
  146. self.charpos = 0
  147. # reset the status for nexttoken()
  148. self._parse1 = self._parse_main
  149. self._curtoken = ''
  150. self._curtokenpos = 0
  151. self._tokens = []
  152. return
  153. def fillbuf(self):
  154. if self.charpos < len(self.buf):
  155. return
  156. # fetch next chunk.
  157. self.bufpos = self.fp.tell()
  158. self.buf = self.fp.read(self.BUFSIZ)
  159. if not self.buf:
  160. raise PSEOF('Unexpected EOF')
  161. self.charpos = 0
  162. return
  163. def nextline(self):
  164. """Fetches a next line that ends either with \\r or \\n.
  165. """
  166. linebuf = ''
  167. linepos = self.bufpos + self.charpos
  168. eol = False
  169. while 1:
  170. self.fillbuf()
  171. if eol:
  172. c = self.buf[self.charpos]
  173. # handle '\r\n'
  174. if c == '\n':
  175. linebuf += c
  176. self.charpos += 1
  177. break
  178. m = EOL.search(self.buf, self.charpos)
  179. if m:
  180. linebuf += self.buf[self.charpos:m.end(0)]
  181. self.charpos = m.end(0)
  182. if linebuf[-1] == '\r':
  183. eol = True
  184. else:
  185. break
  186. else:
  187. linebuf += self.buf[self.charpos:]
  188. self.charpos = len(self.buf)
  189. if 2 <= self.debug:
  190. print >>sys.stderr, 'nextline: %r' % ((linepos, linebuf),)
  191. return (linepos, linebuf)
  192. def revreadlines(self):
  193. """Fetches a next line backword.
  194. This is used to locate the trailers at the end of a file.
  195. """
  196. self.fp.seek(0, 2)
  197. pos = self.fp.tell()
  198. buf = ''
  199. while 0 < pos:
  200. prevpos = pos
  201. pos = max(0, pos-self.BUFSIZ)
  202. self.fp.seek(pos)
  203. s = self.fp.read(prevpos-pos)
  204. if not s:
  205. break
  206. while 1:
  207. n = max(s.rfind('\r'), s.rfind('\n'))
  208. if n == -1:
  209. buf = s + buf
  210. break
  211. yield s[n:]+buf
  212. s = s[:n]
  213. buf = ''
  214. return
  215. def _parse_main(self, s, i):
  216. m = NONSPC.search(s, i)
  217. if not m:
  218. return len(s)
  219. j = m.start(0)
  220. c = s[j]
  221. self._curtokenpos = self.bufpos+j
  222. if c == '%':
  223. self._curtoken = '%'
  224. self._parse1 = self._parse_comment
  225. return j+1
  226. elif c == '/':
  227. self._curtoken = ''
  228. self._parse1 = self._parse_literal
  229. return j+1
  230. elif c in '-+' or c.isdigit():
  231. self._curtoken = c
  232. self._parse1 = self._parse_number
  233. return j+1
  234. elif c == '.':
  235. self._curtoken = c
  236. self._parse1 = self._parse_float
  237. return j+1
  238. elif c.isalpha():
  239. self._curtoken = c
  240. self._parse1 = self._parse_keyword
  241. return j+1
  242. elif c == '(':
  243. self._curtoken = ''
  244. self.paren = 1
  245. self._parse1 = self._parse_string
  246. return j+1
  247. elif c == '<':
  248. self._curtoken = ''
  249. self._parse1 = self._parse_wopen
  250. return j+1
  251. elif c == '>':
  252. self._curtoken = ''
  253. self._parse1 = self._parse_wclose
  254. return j+1
  255. else:
  256. self._add_token(KWD(c))
  257. return j+1
  258. def _add_token(self, obj):
  259. self._tokens.append((self._curtokenpos, obj))
  260. return
  261. def _parse_comment(self, s, i):
  262. m = EOL.search(s, i)
  263. if not m:
  264. self._curtoken += s[i:]
  265. return (self._parse_comment, len(s))
  266. j = m.start(0)
  267. self._curtoken += s[i:j]
  268. self._parse1 = self._parse_main
  269. # We ignore comments.
  270. #self._tokens.append(self._curtoken)
  271. return j
  272. def _parse_literal(self, s, i):
  273. m = END_LITERAL.search(s, i)
  274. if not m:
  275. self._curtoken += s[i:]
  276. return len(s)
  277. j = m.start(0)
  278. self._curtoken += s[i:j]
  279. c = s[j]
  280. if c == '#':
  281. self.hex = ''
  282. self._parse1 = self._parse_literal_hex
  283. return j+1
  284. self._add_token(LIT(self._curtoken))
  285. self._parse1 = self._parse_main
  286. return j
  287. def _parse_literal_hex(self, s, i):
  288. c = s[i]
  289. if HEX.match(c) and len(self.hex) < 2:
  290. self.hex += c
  291. return i+1
  292. if self.hex:
  293. self._curtoken += chr(int(self.hex, 16))
  294. self._parse1 = self._parse_literal
  295. return i
  296. def _parse_number(self, s, i):
  297. m = END_NUMBER.search(s, i)
  298. if not m:
  299. self._curtoken += s[i:]
  300. return len(s)
  301. j = m.start(0)
  302. self._curtoken += s[i:j]
  303. c = s[j]
  304. if c == '.':
  305. self._curtoken += c
  306. self._parse1 = self._parse_float
  307. return j+1
  308. try:
  309. self._add_token(int(self._curtoken))
  310. except ValueError:
  311. pass
  312. self._parse1 = self._parse_main
  313. return j
  314. def _parse_float(self, s, i):
  315. m = END_NUMBER.search(s, i)
  316. if not m:
  317. self._curtoken += s[i:]
  318. return len(s)
  319. j = m.start(0)
  320. self._curtoken += s[i:j]
  321. try:
  322. self._add_token(float(self._curtoken))
  323. except ValueError:
  324. pass
  325. self._parse1 = self._parse_main
  326. return j
  327. def _parse_keyword(self, s, i):
  328. m = END_KEYWORD.search(s, i)
  329. if not m:
  330. self._curtoken += s[i:]
  331. return len(s)
  332. j = m.start(0)
  333. self._curtoken += s[i:j]
  334. if self._curtoken == 'true':
  335. token = True
  336. elif self._curtoken == 'false':
  337. token = False
  338. else:
  339. token = KWD(self._curtoken)
  340. self._add_token(token)
  341. self._parse1 = self._parse_main
  342. return j
  343. def _parse_string(self, s, i):
  344. m = END_STRING.search(s, i)
  345. if not m:
  346. self._curtoken += s[i:]
  347. return len(s)
  348. j = m.start(0)
  349. self._curtoken += s[i:j]
  350. c = s[j]
  351. if c == '\\':
  352. self.oct = ''
  353. self._parse1 = self._parse_string_1
  354. return j+1
  355. if c == '(':
  356. self.paren += 1
  357. self._curtoken += c
  358. return j+1
  359. if c == ')':
  360. self.paren -= 1
  361. if self.paren: # WTF, they said balanced parens need no special treatment.
  362. self._curtoken += c
  363. return j+1
  364. self._add_token(self._curtoken)
  365. self._parse1 = self._parse_main
  366. return j+1
  367. def _parse_string_1(self, s, i):
  368. c = s[i]
  369. if OCT_STRING.match(c) and len(self.oct) < 3:
  370. self.oct += c
  371. return i+1
  372. if self.oct:
  373. self._curtoken += chr(int(self.oct, 8))
  374. self._parse1 = self._parse_string
  375. return i
  376. if c in ESC_STRING:
  377. self._curtoken += chr(ESC_STRING[c])
  378. self._parse1 = self._parse_string
  379. return i+1
  380. def _parse_wopen(self, s, i):
  381. c = s[i]
  382. if c == '<':
  383. self._add_token(KEYWORD_DICT_BEGIN)
  384. self._parse1 = self._parse_main
  385. i += 1
  386. else:
  387. self._parse1 = self._parse_hexstring
  388. return i
  389. def _parse_wclose(self, s, i):
  390. c = s[i]
  391. if c == '>':
  392. self._add_token(KEYWORD_DICT_END)
  393. i += 1
  394. self._parse1 = self._parse_main
  395. return i
  396. def _parse_hexstring(self, s, i):
  397. m = END_HEX_STRING.search(s, i)
  398. if not m:
  399. self._curtoken += s[i:]
  400. return len(s)
  401. j = m.start(0)
  402. self._curtoken += s[i:j]
  403. token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
  404. SPC.sub('', self._curtoken))
  405. self._add_token(token)
  406. self._parse1 = self._parse_main
  407. return j
  408. def nexttoken(self):
  409. while not self._tokens:
  410. self.fillbuf()
  411. self.charpos = self._parse1(self.buf, self.charpos)
  412. token = self._tokens.pop(0)
  413. if 2 <= self.debug:
  414. print >>sys.stderr, 'nexttoken: %r' % (token,)
  415. return token
  416. ## PSStackParser
  417. ##
  418. class PSStackParser(PSBaseParser):
  419. def __init__(self, fp):
  420. PSBaseParser.__init__(self, fp)
  421. self.reset()
  422. return
  423. def reset(self):
  424. self.context = []
  425. self.curtype = None
  426. self.curstack = []
  427. self.results = []
  428. return
  429. def seek(self, pos):
  430. PSBaseParser.seek(self, pos)
  431. self.reset()
  432. return
  433. def push(self, *objs):
  434. self.curstack.extend(objs)
  435. return
  436. def pop(self, n):
  437. objs = self.curstack[-n:]
  438. self.curstack[-n:] = []
  439. return objs
  440. def popall(self):
  441. objs = self.curstack
  442. self.curstack = []
  443. return objs
  444. def add_results(self, *objs):
  445. if 2 <= self.debug:
  446. print >>sys.stderr, 'add_results: %r' % (objs,)
  447. self.results.extend(objs)
  448. return
  449. def start_type(self, pos, type):
  450. self.context.append((pos, self.curtype, self.curstack))
  451. (self.curtype, self.curstack) = (type, [])
  452. if 2 <= self.debug:
  453. print >>sys.stderr, 'start_type: pos=%r, type=%r' % (pos, type)
  454. return
  455. def end_type(self, type):
  456. if self.curtype != type:
  457. raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
  458. objs = [obj for (_, obj) in self.curstack]
  459. (pos, self.curtype, self.curstack) = self.context.pop()
  460. if 2 <= self.debug:
  461. print >>sys.stderr, 'end_type: pos=%r, type=%r, objs=%r' % (pos, type, objs)
  462. return (pos, objs)
  463. def do_keyword(self, pos, token):
  464. return
  465. def nextobject(self):
  466. """Yields a list of objects.
  467. Returns keywords, literals, strings, numbers, arrays and dictionaries.
  468. Arrays and dictionaries are represented as Python lists and dictionaries.
  469. """
  470. while not self.results:
  471. (pos, token) = self.nexttoken()
  472. #print (pos,token), (self.curtype, self.curstack)
  473. if isinstance(token, (int, long, float, bool, str, PSLiteral)):
  474. # normal token
  475. self.push((pos, token))
  476. elif token == KEYWORD_ARRAY_BEGIN:
  477. # begin array
  478. self.start_type(pos, 'a')
  479. elif token == KEYWORD_ARRAY_END:
  480. # end array
  481. try:
  482. self.push(self.end_type('a'))
  483. except PSTypeError:
  484. if STRICT:
  485. raise
  486. elif token == KEYWORD_DICT_BEGIN:
  487. # begin dictionary
  488. self.start_type(pos, 'd')
  489. elif token == KEYWORD_DICT_END:
  490. # end dictionary
  491. try:
  492. (pos, objs) = self.end_type('d')
  493. if len(objs) % 2 != 0:
  494. raise PSSyntaxError('Invalid dictionary construct: %r' % objs)
  495. # construct a Python dictionary.
  496. d = dict((literal_name(k), v) for (k, v) in choplist(2, objs) if v is not None)
  497. self.push((pos, d))
  498. except PSTypeError:
  499. if STRICT:
  500. raise
  501. elif token == KEYWORD_PROC_BEGIN:
  502. # begin proc
  503. self.start_type(pos, 'p')
  504. elif token == KEYWORD_PROC_END:
  505. # end proc
  506. try:
  507. self.push(self.end_type('p'))
  508. except PSTypeError:
  509. if STRICT:
  510. raise
  511. else:
  512. if 2 <= self.debug:
  513. print >>sys.stderr, 'do_keyword: pos=%r, token=%r, stack=%r' % \
  514. (pos, token, self.curstack)
  515. self.do_keyword(pos, token)
  516. if self.context:
  517. continue
  518. else:
  519. self.flush()
  520. obj = self.results.pop(0)
  521. if 2 <= self.debug:
  522. print >>sys.stderr, 'nextobject: %r' % (obj,)
  523. return obj
  524. import unittest
  525. ## Simplistic Test cases
  526. ##
  527. class TestPSBaseParser(unittest.TestCase):
  528. TESTDATA = r'''%!PS
  529. begin end
  530. " @ #
  531. /a/BCD /Some_Name /foo#5f#xbaa
  532. 0 +1 -2 .5 1.234
  533. (abc) () (abc ( def ) ghi)
  534. (def\040\0\0404ghi) (bach\\slask) (foo\nbaa)
  535. (this % is not a comment.)
  536. (foo
  537. baa)
  538. (foo\
  539. baa)
  540. <> <20> < 40 4020 >
  541. <abcd00
  542. 12345>
  543. func/a/b{(c)do*}def
  544. [ 1 (z) ! ]
  545. << /foo (bar) >>
  546. '''
  547. TOKENS = [
  548. (5, KWD('begin')), (11, KWD('end')), (16, KWD('"')), (19, KWD('@')),
  549. (21, KWD('#')), (23, LIT('a')), (25, LIT('BCD')), (30, LIT('Some_Name')),
  550. (41, LIT('foo_xbaa')), (54, 0), (56, 1), (59, -2), (62, 0.5),
  551. (65, 1.234), (71, 'abc'), (77, ''), (80, 'abc ( def ) ghi'),
  552. (98, 'def \x00 4ghi'), (118, 'bach\\slask'), (132, 'foo\nbaa'),
  553. (143, 'this % is not a comment.'), (170, 'foo\nbaa'), (180, 'foobaa'),
  554. (191, ''), (194, ' '), (199, '@@ '), (211, '\xab\xcd\x00\x124\x05'),
  555. (226, KWD('func')), (230, LIT('a')), (232, LIT('b')),
  556. (234, KWD('{')), (235, 'c'), (238, KWD('do*')), (241, KWD('}')),
  557. (242, KWD('def')), (246, KWD('[')), (248, 1), (250, 'z'), (254, KWD('!')),
  558. (256, KWD(']')), (258, KWD('<<')), (261, LIT('foo')), (266, 'bar'),
  559. (272, KWD('>>'))
  560. ]
  561. OBJS = [
  562. (23, LIT('a')), (25, LIT('BCD')), (30, LIT('Some_Name')),
  563. (41, LIT('foo_xbaa')), (54, 0), (56, 1), (59, -2), (62, 0.5),
  564. (65, 1.234), (71, 'abc'), (77, ''), (80, 'abc ( def ) ghi'),
  565. (98, 'def \x00 4ghi'), (118, 'bach\\slask'), (132, 'foo\nbaa'),
  566. (143, 'this % is not a comment.'), (170, 'foo\nbaa'), (180, 'foobaa'),
  567. (191, ''), (194, ' '), (199, '@@ '), (211, '\xab\xcd\x00\x124\x05'),
  568. (230, LIT('a')), (232, LIT('b')), (234, ['c']), (246, [1, 'z']),
  569. (258, {'foo': 'bar'}),
  570. ]
  571. def get_tokens(self, s):
  572. import StringIO
  573. class MyParser(PSBaseParser):
  574. def flush(self):
  575. self.add_results(*self.popall())
  576. parser = MyParser(StringIO.StringIO(s))
  577. r = []
  578. try:
  579. while 1:
  580. r.append(parser.nexttoken())
  581. except PSEOF:
  582. pass
  583. return r
  584. def get_objects(self, s):
  585. import StringIO
  586. class MyParser(PSStackParser):
  587. def flush(self):
  588. self.add_results(*self.popall())
  589. parser = MyParser(StringIO.StringIO(s))
  590. r = []
  591. try:
  592. while 1:
  593. r.append(parser.nextobject())
  594. except PSEOF:
  595. pass
  596. return r
  597. def test_1(self):
  598. tokens = self.get_tokens(self.TESTDATA)
  599. print tokens
  600. self.assertEqual(tokens, self.TOKENS)
  601. return
  602. def test_2(self):
  603. objs = self.get_objects(self.TESTDATA)
  604. print objs
  605. self.assertEqual(objs, self.OBJS)
  606. return
  607. if __name__ == '__main__':
  608. unittest.main()