Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyparsing/results.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

331 statements  

1# results.py 

2 

3from __future__ import annotations 

4 

5import collections 

6from collections.abc import ( 

7 MutableMapping, 

8 Mapping, 

9 MutableSequence, 

10 Iterator, 

11 Iterable, 

12) 

13import pprint 

14from typing import Any, NamedTuple 

15 

16from .util import deprecate_argument, _is_iterable, _flatten 

17 

18 

19str_type: tuple[type, ...] = (str, bytes) 

20_generator_type = type((_ for _ in ())) 

21NULL_SLICE: slice = slice(None) 

22 

23 

24class _ParseResultsWithOffset(NamedTuple): 

25 result: Any 

26 offset: int 

27 

28 

29class ParseResults: 

30 """Structured parse results, to provide multiple means of access to 

31 the parsed data: 

32 

33 - as a list (``len(results)``) 

34 - by list index (``results[0], results[1]``, etc.) 

35 - by attribute (``results.<results_name>`` - see :class:`ParserElement.set_results_name`) 

36 

37 Example: 

38 

39 .. testcode:: 

40 

41 integer = Word(nums) 

42 date_str = (integer.set_results_name("year") + '/' 

43 + integer.set_results_name("month") + '/' 

44 + integer.set_results_name("day")) 

45 # equivalent form: 

46 # date_str = (integer("year") + '/' 

47 # + integer("month") + '/' 

48 # + integer("day")) 

49 

50 # parse_string returns a ParseResults object 

51 result = date_str.parse_string("1999/12/31") 

52 

53 def test(s, fn=repr): 

54 print(f"{s} -> {fn(eval(s))}") 

55 

56 test("list(result)") 

57 test("result[0]") 

58 test("result['month']") 

59 test("result.day") 

60 test("'month' in result") 

61 test("'minutes' in result") 

62 test("result.dump()", str) 

63 

64 prints: 

65 

66 .. testoutput:: 

67 

68 list(result) -> ['1999', '/', '12', '/', '31'] 

69 result[0] -> '1999' 

70 result['month'] -> '12' 

71 result.day -> '31' 

72 'month' in result -> True 

73 'minutes' in result -> False 

74 result.dump() -> ['1999', '/', '12', '/', '31'] 

75 - day: '31' 

76 - month: '12' 

77 - year: '1999' 

78 

79 """ 

80 

81 _null_values: tuple[Any, ...] = (None, [], ()) 

82 

83 _name: str 

84 _parent: ParseResults 

85 _all_names: set[str] 

86 _toklist: list[Any] 

87 _tokdict: dict[str, list[_ParseResultsWithOffset]] 

88 _is_dict_context: bool 

89 

90 __slots__ = ( 

91 "_name", 

92 "_parent", 

93 "_all_names", 

94 "_toklist", 

95 "_tokdict", 

96 "_is_dict_context", 

97 ) 

98 

99 class List(list): 

100 """ 

101 Simple wrapper class to distinguish parsed list results that should be preserved 

102 as actual Python lists, instead of being converted to :class:`ParseResults`: 

103 

104 .. testcode:: 

105 

106 import pyparsing as pp 

107 ppc = pp.common 

108 

109 LBRACK, RBRACK, LPAR, RPAR = pp.Suppress.using_each("[]()") 

110 element = pp.Forward() 

111 item = ppc.integer 

112 item_list = pp.DelimitedList(element) 

113 element_list = LBRACK + item_list + RBRACK | LPAR + item_list + RPAR 

114 element <<= item | element_list 

115 

116 # add parse action to convert from ParseResults 

117 # to actual Python collection types 

118 @element_list.add_parse_action 

119 def as_python_list(t): 

120 return pp.ParseResults.List(t.as_list()) 

121 

122 element.run_tests(''' 

123 100 

124 [2,3,4] 

125 [[2, 1],3,4] 

126 [(2, 1),3,4] 

127 (2,3,4) 

128 ([2, 3], 4) 

129 ''', post_parse=lambda s, r: (r[0], type(r[0])) 

130 ) 

131 

132 prints: 

133 

134 .. testoutput:: 

135 :options: +NORMALIZE_WHITESPACE 

136 

137 

138 100 

139 (100, <class 'int'>) 

140 

141 [2,3,4] 

142 ([2, 3, 4], <class 'list'>) 

143 

144 [[2, 1],3,4] 

145 ([[2, 1], 3, 4], <class 'list'>) 

146 

147 [(2, 1),3,4] 

148 ([[2, 1], 3, 4], <class 'list'>) 

149 

150 (2,3,4) 

151 ([2, 3, 4], <class 'list'>) 

152 

153 ([2, 3], 4) 

154 ([[2, 3], 4], <class 'list'>) 

155 

156 (Used internally by :class:`Group` when `aslist=True`.) 

157 """ 

158 

159 def __new__(cls, contained=None): 

160 if contained is None: 

161 contained = [] 

162 

163 if not isinstance(contained, list): 

164 raise TypeError( 

165 f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}" 

166 ) 

167 

168 return list.__new__(cls) 

169 

170 def __new__(cls, toklist=None, name=None, **kwargs): 

171 if isinstance(toklist, ParseResults): 

172 return toklist 

173 self = object.__new__(cls) 

174 self._name = None 

175 self._parent = None 

176 self._all_names = set() 

177 self._is_dict_context = False 

178 

179 if toklist is None: 

180 self._toklist = [] 

181 elif isinstance(toklist, (list, _generator_type)): 

182 self._toklist = ( 

183 [toklist[:]] 

184 if isinstance(toklist, ParseResults.List) 

185 else list(toklist) 

186 ) 

187 else: 

188 self._toklist = [toklist] 

189 self._tokdict = {} 

190 return self 

191 

192 # Performance tuning: we construct a *lot* of these, so keep this 

193 # constructor as small and fast as possible 

194 def __init__( 

195 self, 

196 toklist=None, 

197 name=None, 

198 aslist=True, 

199 modal=True, 

200 isinstance=isinstance, 

201 **kwargs, 

202 ) -> None: 

203 asList = deprecate_argument(kwargs, "asList", True, new_name="aslist") 

204 

205 asList = asList and aslist 

206 self._tokdict: dict[str, list[_ParseResultsWithOffset]] 

207 

208 if name is None or name == "": 

209 return 

210 

211 if isinstance(name, int): 

212 name = str(name) 

213 

214 if not modal: 

215 self._all_names = {name} 

216 

217 self._name = name 

218 

219 if toklist in self._null_values: 

220 return 

221 

222 if isinstance(toklist, (str_type, type)): 

223 toklist = [toklist] 

224 

225 if asList: 

226 if isinstance(toklist, ParseResults): 

227 self[name] = _ParseResultsWithOffset(ParseResults(toklist._toklist), 0) 

228 else: 

229 self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0) 

230 self[name]._name = name 

231 return 

232 

233 try: 

234 self[name] = toklist[0] 

235 except (KeyError, TypeError, IndexError): 

236 if toklist is not self: 

237 self[name] = toklist 

238 else: 

239 self._name = name 

240 

241 def __eq__(self, other): 

242 return (self is other) or ( 

243 isinstance(other, type(self)) 

244 and self._name == other._name 

245 and self.as_list() == other.as_list() 

246 and self.as_dict() == other.as_dict() 

247 ) 

248 

249 def __getitem__(self, i): 

250 if isinstance(i, (int, slice)): 

251 return self._toklist[i] 

252 

253 if i not in self._all_names: 

254 return self._tokdict[i][-1].result 

255 

256 return ParseResults([v.result for v in self._tokdict[i]]) 

257 

258 def __setitem__(self, k, v, isinstance=isinstance): 

259 no_value = object() 

260 if isinstance(v, _ParseResultsWithOffset): 

261 cur_tokdict_value = self._tokdict.get(k, no_value) 

262 if cur_tokdict_value is no_value: 

263 self._tokdict[k] = [v] 

264 else: 

265 cur_tokdict_value.append(v) 

266 sub = v.result 

267 elif isinstance(k, (int, slice)): 

268 self._toklist[k] = v 

269 sub = v 

270 else: 

271 cur_tokdict_value = self._tokdict.get(k, no_value) 

272 if cur_tokdict_value is no_value: 

273 self._tokdict[k] = [_ParseResultsWithOffset(v, 0)] 

274 else: 

275 cur_tokdict_value.append(_ParseResultsWithOffset(v, 0)) 

276 sub = v 

277 if isinstance(sub, ParseResults): 

278 sub._parent = self 

279 

280 def __delitem__(self, i): 

281 if not isinstance(i, (int, slice)): 

282 del self._tokdict[i] 

283 return 

284 

285 # slight optimization if del results[:] 

286 if i == NULL_SLICE: 

287 self._toklist.clear() 

288 return 

289 

290 mylen = len(self._toklist) 

291 del self._toklist[i] 

292 

293 # convert int to slice 

294 if isinstance(i, int): 

295 if i < 0: 

296 i += mylen 

297 i = slice(i, i + 1) 

298 # get removed indices 

299 removed = list(range(*i.indices(mylen))) 

300 removed.reverse() 

301 # fixup indices in token dictionary; copy() shares these lists, so 

302 # renumber a private copy rather than the list itself 

303 for name, occurrences in self._tokdict.items(): 

304 occurrences = occurrences[:] 

305 for j in removed: 

306 for k, (value, position) in enumerate(reversed(occurrences)): 

307 if position > j: 

308 occurrences[len(occurrences) - 1 - k] = _ParseResultsWithOffset( 

309 value, position - 1 

310 ) 

311 else: 

312 break 

313 self._tokdict[name] = occurrences 

314 

315 def __contains__(self, k) -> bool: 

316 return k in self._tokdict 

317 

318 def __len__(self) -> int: 

319 return len(self._toklist) 

320 

321 def __bool__(self) -> bool: 

322 return not not (self._toklist or self._tokdict) 

323 

324 def __iter__(self) -> Iterator: 

325 return iter(self._toklist) 

326 

327 def __reversed__(self) -> Iterator: 

328 return iter(self._toklist[::-1]) 

329 

330 def keys(self): 

331 return iter(self._tokdict) 

332 

333 def values(self): 

334 return (self[k] for k in self.keys()) 

335 

336 def items(self): 

337 return ((k, self[k]) for k in self.keys()) 

338 

339 def haskeys(self) -> bool: 

340 """ 

341 Since ``keys()`` returns an iterator, this method is helpful in bypassing 

342 code that looks for the existence of any defined results names.""" 

343 return not not self._tokdict 

344 

345 def pop(self, *args, **kwargs): 

346 """ 

347 Removes and returns item at specified index (default= ``last``). 

348 Supports both ``list`` and ``dict`` semantics for ``pop()``. If 

349 passed no argument or an integer argument, it will use ``list`` 

350 semantics and pop tokens from the list of parsed tokens. If passed 

351 a non-integer argument (most likely a string), it will use ``dict`` 

352 semantics and pop the corresponding value from any defined results 

353 names. A second default return value argument is supported, just as in 

354 ``dict.pop()``. 

355 

356 Example: 

357 

358 .. doctest:: 

359 

360 >>> numlist = Word(nums)[...] 

361 >>> print(numlist.parse_string("0 123 321")) 

362 ['0', '123', '321'] 

363 

364 >>> def remove_first(tokens): 

365 ... tokens.pop(0) 

366 ... 

367 >>> numlist.add_parse_action(remove_first) 

368 [W:(0-9)]... 

369 >>> print(numlist.parse_string("0 123 321")) 

370 ['123', '321'] 

371 

372 >>> label = Word(alphas) 

373 >>> patt = label("LABEL") + Word(nums)[1, ...] 

374 >>> print(patt.parse_string("AAB 123 321").dump()) 

375 ['AAB', '123', '321'] 

376 - LABEL: 'AAB' 

377 

378 >>> # Use pop() in a parse action to remove named result 

379 >>> # (note that corresponding value is not 

380 >>> # removed from list form of results) 

381 >>> def remove_LABEL(tokens): 

382 ... tokens.pop("LABEL") 

383 ... return tokens 

384 ... 

385 >>> patt.add_parse_action(remove_LABEL) 

386 {W:(A-Za-z) {W:(0-9)}...} 

387 >>> print(patt.parse_string("AAB 123 321").dump()) 

388 ['AAB', '123', '321'] 

389 

390 """ 

391 if not args: 

392 args = [-1] 

393 for k, v in kwargs.items(): 

394 if k == "default": 

395 args = (args[0], v) 

396 else: 

397 raise TypeError(f"pop() got an unexpected keyword argument {k!r}") 

398 if isinstance(args[0], int) or len(args) == 1 or args[0] in self: 

399 index = args[0] 

400 ret = self[index] 

401 del self[index] 

402 return ret 

403 else: 

404 defaultvalue = args[1] 

405 return defaultvalue 

406 

407 def get(self, key, default_value=None): 

408 """ 

409 Returns named result matching the given key, or if there is no 

410 such name, then returns the given ``default_value`` or ``None`` if no 

411 ``default_value`` is specified. 

412 

413 Similar to ``dict.get()``. 

414 

415 Example: 

416 

417 .. doctest:: 

418 

419 >>> integer = Word(nums) 

420 >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day") 

421 

422 >>> result = date_str.parse_string("1999/12/31") 

423 >>> result.get("year") 

424 '1999' 

425 >>> result.get("hour", "not specified") 

426 'not specified' 

427 >>> result.get("hour") 

428 

429 """ 

430 if key in self: 

431 return self[key] 

432 else: 

433 return default_value 

434 

435 def insert(self, index, ins_string): 

436 """ 

437 Inserts new element at location index in the list of parsed tokens. 

438 

439 Similar to ``list.insert()``. 

440 

441 Example: 

442 

443 .. doctest:: 

444 

445 >>> numlist = Word(nums)[...] 

446 >>> print(numlist.parse_string("0 123 321")) 

447 ['0', '123', '321'] 

448 

449 >>> # use a parse action to insert the parse location 

450 >>> # in the front of the parsed results 

451 >>> def insert_locn(locn, tokens): 

452 ... tokens.insert(0, locn) 

453 ... 

454 >>> numlist.add_parse_action(insert_locn) 

455 [W:(0-9)]... 

456 >>> print(numlist.parse_string("0 123 321")) 

457 [0, '0', '123', '321'] 

458 

459 """ 

460 self._toklist.insert(index, ins_string) 

461 # fixup indices in token dictionary; copy() shares these lists, so 

462 # renumber a private copy rather than the list itself 

463 for name, occurrences in self._tokdict.items(): 

464 occurrences = occurrences[:] 

465 for k, (value, position) in enumerate(occurrences): 

466 if position > index: 

467 occurrences[k] = _ParseResultsWithOffset(value, position + 1) 

468 self._tokdict[name] = occurrences 

469 

470 def append(self, item): 

471 """ 

472 Add single element to end of ``ParseResults`` list of elements. 

473 

474 Example: 

475 

476 .. doctest:: 

477 

478 >>> numlist = Word(nums)[...] 

479 >>> print(numlist.parse_string("0 123 321")) 

480 ['0', '123', '321'] 

481 

482 >>> # use a parse action to compute the sum of the parsed integers, 

483 >>> # and add it to the end 

484 >>> def append_sum(tokens): 

485 ... tokens.append(sum(map(int, tokens))) 

486 ... 

487 >>> numlist.add_parse_action(append_sum) 

488 [W:(0-9)]... 

489 >>> print(numlist.parse_string("0 123 321")) 

490 ['0', '123', '321', 444] 

491 """ 

492 self._toklist.append(item) 

493 

494 def extend(self, itemseq): 

495 """ 

496 Add sequence of elements to end of :class:`ParseResults` list of elements. 

497 

498 Example: 

499 

500 .. testcode:: 

501 

502 patt = Word(alphas)[1, ...] 

503 

504 # use a parse action to append the reverse of the matched strings, 

505 # to make a palindrome 

506 def make_palindrome(tokens): 

507 tokens.extend(reversed([t[::-1] for t in tokens])) 

508 return ''.join(tokens) 

509 

510 patt.add_parse_action(make_palindrome) 

511 print(patt.parse_string("lskdj sdlkjf lksd")) 

512 

513 prints: 

514 

515 .. testoutput:: 

516 

517 ['lskdjsdlkjflksddsklfjkldsjdksl'] 

518 """ 

519 if isinstance(itemseq, ParseResults): 

520 self.__iadd__(itemseq) 

521 else: 

522 self._toklist.extend(itemseq) 

523 

524 def clear(self): 

525 """ 

526 Clear all elements and results names. 

527 """ 

528 del self._toklist[:] 

529 self._tokdict.clear() 

530 

531 def __getattr__(self, name): 

532 try: 

533 return self[name] 

534 except KeyError: 

535 if name.startswith("__"): 

536 raise AttributeError(name) 

537 return "" 

538 

539 def __add__(self, other: ParseResults) -> ParseResults: 

540 if not isinstance(other, ParseResults): 

541 return NotImplemented 

542 if not other: 

543 return self 

544 

545 ret = self.copy() 

546 ret += other 

547 return ret 

548 

549 def __iadd__(self, other: ParseResults) -> ParseResults: 

550 if not isinstance(other, ParseResults): 

551 return NotImplemented 

552 if not other: 

553 return self 

554 

555 if other._tokdict: 

556 offset = len(self._toklist) 

557 # addoffset = lambda a: offset if a < 0 else a + offset 

558 otheritems = other._tokdict.items() 

559 otherdictitems = [ 

560 # (k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) 

561 ( 

562 k, 

563 _ParseResultsWithOffset( 

564 v.result, 

565 # addoffset(v[1]) 

566 (offset if v.offset < 0 else v.offset + offset), 

567 ), 

568 ) 

569 for k, vlist in otheritems 

570 for v in vlist 

571 ] 

572 for k, v in otherdictitems: 

573 self[k] = v 

574 if isinstance(v.result, ParseResults): 

575 v.result._parent = self 

576 

577 self._toklist += other._toklist 

578 self._all_names |= other._all_names 

579 return self 

580 

581 def __radd__(self, other) -> ParseResults: 

582 if isinstance(other, int) and other == 0: 

583 # useful for merging many ParseResults using sum() builtin 

584 return self.copy() 

585 return NotImplemented 

586 

587 def __repr__(self) -> str: 

588 return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})" 

589 

590 def __str__(self) -> str: 

591 return ( 

592 "[" 

593 + ", ".join( 

594 [ 

595 str(i) if isinstance(i, ParseResults) else repr(i) 

596 for i in self._toklist 

597 ] 

598 ) 

599 + "]" 

600 ) 

601 

602 def _asStringList(self, sep=""): 

603 out = [] 

604 for item in self._toklist: 

605 if out and sep: 

606 out.append(sep) 

607 if isinstance(item, ParseResults): 

608 out += item._asStringList() 

609 else: 

610 out.append(str(item)) 

611 return out 

612 

613 def as_list(self, *, flatten: bool = False) -> list: 

614 """ 

615 Returns the parse results as a nested list of matching tokens, all converted to strings. 

616 If ``flatten`` is True, all the nesting levels in the returned list are collapsed. 

617 

618 Example: 

619 

620 .. doctest:: 

621 

622 >>> patt = Word(alphas)[1, ...] 

623 >>> result = patt.parse_string("sldkj lsdkj sldkj") 

624 >>> # even though the result prints in string-like form, 

625 >>> # it is actually a pyparsing ParseResults 

626 >>> type(result) 

627 <class 'pyparsing.results.ParseResults'> 

628 >>> print(result) 

629 ['sldkj', 'lsdkj', 'sldkj'] 

630 

631 .. doctest:: 

632 

633 >>> # Use as_list() to create an actual list 

634 >>> result_list = result.as_list() 

635 >>> type(result_list) 

636 <class 'list'> 

637 >>> print(result_list) 

638 ['sldkj', 'lsdkj', 'sldkj'] 

639 

640 .. versionchanged:: 3.2.0 

641 New ``flatten`` argument. 

642 """ 

643 

644 if flatten: 

645 return [*_flatten(self)] 

646 else: 

647 return [ 

648 res.as_list() if isinstance(res, ParseResults) else res 

649 for res in self._toklist 

650 ] 

651 

652 def as_dict(self) -> dict: 

653 """ 

654 Returns the named parse results as a nested dictionary. 

655 

656 Example: 

657 

658 .. doctest:: 

659 

660 >>> integer = pp.Word(pp.nums) 

661 >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day") 

662 

663 >>> result = date_str.parse_string('1999/12/31') 

664 >>> type(result) 

665 <class 'pyparsing.results.ParseResults'> 

666 >>> result 

667 ParseResults(['1999', '/', '12', '/', '31'], {'year': '1999', 'month': '12', 'day': '31'}) 

668 

669 >>> result_dict = result.as_dict() 

670 >>> type(result_dict) 

671 <class 'dict'> 

672 >>> result_dict 

673 {'year': '1999', 'month': '12', 'day': '31'} 

674 

675 >>> # even though a ParseResults supports dict-like access, 

676 >>> # sometime you just need to have a dict 

677 >>> import json 

678 >>> print(json.dumps(result)) 

679 Traceback (most recent call last): 

680 TypeError: Object of type ParseResults is not JSON serializable 

681 >>> print(json.dumps(result.as_dict())) 

682 {"year": "1999", "month": "12", "day": "31"} 

683 """ 

684 

685 def to_item(obj): 

686 if isinstance(obj, ParseResults): 

687 if obj.haskeys() or obj._is_dict_context: 

688 return obj.as_dict() 

689 return [to_item(v) for v in obj] 

690 else: 

691 return obj 

692 

693 return dict((k, to_item(v)) for k, v in self.items()) 

694 

695 def copy(self) -> ParseResults: 

696 """ 

697 Returns a new shallow copy of a :class:`ParseResults` object. 

698 :class:`ParseResults` items contained within the source are 

699 shared with the copy. Use :meth:`ParseResults.deepcopy` to 

700 create a copy with its own separate content values. 

701 """ 

702 ret: ParseResults = object.__new__(ParseResults) 

703 ret._toklist = self._toklist[:] 

704 # the occurrence lists are shared with the original; the only methods 

705 # that renumber them (__delitem__, insert) copy before writing, so a 

706 # copy can never renumber the original's offsets 

707 ret._tokdict = {**self._tokdict} 

708 ret._parent = self._parent 

709 ret._all_names = {*self._all_names} 

710 ret._name = self._name 

711 return ret 

712 

713 def deepcopy(self) -> ParseResults: 

714 """ 

715 Returns a new deep copy of a :class:`ParseResults` object. 

716 

717 .. versionadded:: 3.1.0 

718 """ 

719 ret = self.copy() 

720 # map id() of each copied token to its copy, so that results names 

721 # referencing items in the token list stay linked to the copies (and 

722 # decoupled from the original) 

723 memo: dict[int, Any] = {} 

724 # replace values with copies if they are of known mutable types 

725 for i, obj in enumerate(self._toklist): 

726 if isinstance(obj, ParseResults): 

727 ret._toklist[i] = obj.deepcopy() 

728 elif isinstance(obj, (str, bytes)): 

729 continue 

730 elif isinstance(obj, MutableMapping): 

731 ret._toklist[i] = dest = type(obj)() 

732 for k, v in obj.items(): 

733 dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v 

734 elif isinstance(obj, Iterable): 

735 ret._toklist[i] = type(obj)( 

736 v.deepcopy() if isinstance(v, ParseResults) else v for v in obj # type: ignore[call-arg] 

737 ) 

738 else: 

739 continue 

740 memo[id(obj)] = ret._toklist[i] 

741 

742 # rebuild the results-name dict so that named results point at the 

743 # deep-copied tokens, instead of remaining linked to the original 

744 ret._tokdict = { 

745 name: [ 

746 _ParseResultsWithOffset(memo.get(id(value), value), offset) 

747 for value, offset in occurrences 

748 ] 

749 for name, occurrences in self._tokdict.items() 

750 } 

751 

752 return ret 

753 

754 def get_name(self) -> str | None: 

755 r""" 

756 Returns the results name for this token expression. 

757 

758 Useful when several different expressions might match 

759 at a particular location. 

760 

761 Example: 

762 

763 .. testcode:: 

764 

765 integer = Word(nums) 

766 ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") 

767 house_number_expr = Suppress('#') + Word(nums, alphanums) 

768 user_data = (Group(house_number_expr)("house_number") 

769 | Group(ssn_expr)("ssn") 

770 | Group(integer)("age")) 

771 user_info = user_data[1, ...] 

772 

773 result = user_info.parse_string("22 111-22-3333 #221B") 

774 for item in result: 

775 print(item.get_name(), ':', item[0]) 

776 

777 prints: 

778 

779 .. testoutput:: 

780 

781 age : 22 

782 ssn : 111-22-3333 

783 house_number : 221B 

784 

785 """ 

786 if self._name: 

787 return self._name 

788 elif self._parent: 

789 par: ParseResults = self._parent 

790 parent_tokdict_items = par._tokdict.items() 

791 return next( 

792 ( 

793 k 

794 for k, vlist in parent_tokdict_items 

795 for v, loc in vlist 

796 if v is self 

797 ), 

798 None, 

799 ) 

800 elif len(self) == 1 and len(self._tokdict) == 1: 

801 first_name, first_pr_offset = next(iter(self._tokdict.items())) 

802 if first_pr_offset[0].offset <= 0: 

803 return first_name 

804 

805 return None 

806 

807 def dump(self, indent="", full=True, include_list=True, _depth=0) -> str: 

808 """ 

809 Diagnostic method for listing out the contents of 

810 a :class:`ParseResults`. Accepts an optional ``indent`` argument so 

811 that this string can be embedded in a nested display of other data. 

812 

813 Example: 

814 

815 .. testcode:: 

816 

817 integer = Word(nums) 

818 date_str = integer("year") + '/' + integer("month") + '/' + integer("day") 

819 

820 result = date_str.parse_string('1999/12/31') 

821 print(result.dump()) 

822 

823 prints: 

824 

825 .. testoutput:: 

826 

827 ['1999', '/', '12', '/', '31'] 

828 - day: '31' 

829 - month: '12' 

830 - year: '1999' 

831 """ 

832 out = [] 

833 NL = "\n" 

834 out.append(indent + str(self.as_list()) if include_list else "") 

835 

836 if not full: 

837 return "".join(out) 

838 

839 if self.haskeys(): 

840 items = sorted((str(k), v) for k, v in self.items()) 

841 for k, v in items: 

842 if out: 

843 out.append(NL) 

844 out.append(f"{indent}{(' ' * _depth)}- {k}: ") 

845 if not isinstance(v, ParseResults): 

846 out.append(repr(v)) 

847 continue 

848 

849 if not v: 

850 out.append(str(v)) 

851 continue 

852 

853 out.append( 

854 v.dump( 

855 indent=indent, 

856 full=full, 

857 include_list=include_list, 

858 _depth=_depth + 1, 

859 ) 

860 ) 

861 if not any(isinstance(vv, ParseResults) for vv in self): 

862 return "".join(out) 

863 

864 v = self 

865 incr = " " 

866 nl = "\n" 

867 for i, vv in enumerate(v): 

868 if isinstance(vv, ParseResults): 

869 vv_dump = vv.dump( 

870 indent=indent, 

871 full=full, 

872 include_list=include_list, 

873 _depth=_depth + 1, 

874 ) 

875 out.append( 

876 f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv_dump}" 

877 ) 

878 else: 

879 out.append( 

880 f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv}" 

881 ) 

882 

883 return "".join(out) 

884 

885 def pprint(self, *args, **kwargs): 

886 """ 

887 Pretty-printer for parsed results as a list, using the 

888 `pprint <https://docs.python.org/3/library/pprint.html>`_ module. 

889 Accepts additional positional or keyword args as defined for 

890 `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ . 

891 

892 Example: 

893 

894 .. testcode:: 

895 

896 ident = Word(alphas, alphanums) 

897 num = Word(nums) 

898 func = Forward() 

899 term = ident | num | Group('(' + func + ')') 

900 func <<= ident + Group(Optional(DelimitedList(term))) 

901 result = func.parse_string("fna a,b,(fnb c,d,200),100") 

902 result.pprint(width=40) 

903 

904 prints: 

905 

906 .. testoutput:: 

907 

908 ['fna', 

909 ['a', 

910 'b', 

911 ['(', 'fnb', ['c', 'd', '200'], ')'], 

912 '100']] 

913 """ 

914 pprint.pprint(self.as_list(), *args, **kwargs) 

915 

916 # add support for pickle protocol 

917 def __getstate__(self): 

918 return ( 

919 self._toklist, 

920 ( 

921 self._tokdict.copy(), 

922 None, 

923 list(self._all_names), 

924 self._name, 

925 self._is_dict_context, 

926 ), 

927 ) 

928 

929 def __setstate__(self, state): 

930 self._toklist, ( 

931 self._tokdict, 

932 _, 

933 inAccumNames, 

934 self._name, 

935 self._is_dict_context, 

936 ) = state 

937 self._all_names = set(inAccumNames) 

938 self._parent = None 

939 

940 def __getnewargs__(self): 

941 return self._toklist, self._name 

942 

943 def __dir__(self): 

944 return dir(type(self)) + list(self.keys()) 

945 

946 @classmethod 

947 def from_dict(cls, other, name=None) -> ParseResults: 

948 """ 

949 Helper classmethod to construct a :class:`ParseResults` from a ``dict``, preserving the 

950 name-value relations as results names. If an optional ``name`` argument is 

951 given, a nested :class:`ParseResults` will be returned. 

952 """ 

953 ret = cls([]) 

954 for k, v in other.items(): 

955 if isinstance(v, Mapping): 

956 ret += cls.from_dict(v, name=k) 

957 else: 

958 ret += cls([v], name=k, aslist=_is_iterable(v)) 

959 if name is not None: 

960 ret = cls([ret], name=name) 

961 return ret 

962 

963 asList = as_list 

964 """ 

965 .. deprecated:: 3.0.0 

966 use :meth:`as_list` 

967 """ 

968 asDict = as_dict 

969 """ 

970 .. deprecated:: 3.0.0 

971 use :meth:`as_dict` 

972 """ 

973 getName = get_name 

974 """ 

975 .. deprecated:: 3.0.0 

976 use :meth:`get_name` 

977 """ 

978 

979 

980MutableMapping.register(ParseResults) 

981MutableSequence.register(ParseResults)