From cbc91c67d0dfc6deeb128cc58278ae4e07c2819a Mon Sep 17 00:00:00 2001 From: Jeremy Singer-Vine Date: Thu, 13 Aug 2020 08:24:41 -0400 Subject: [PATCH] Add convert.py/.to_json/.to_csv & improve testcov Moves most of the logic previously in cli.py to convert.py, for usage by other submodules. Adds Container.to_json and Container.to_csv. Makes adjustments/fixes to other parts of the library, based on edge-cases encountered (such as infinite recursion in anntations). --- README.md | 4 +- pdfplumber/cli.py | 104 ++++++---------------------- pdfplumber/container.py | 6 +- pdfplumber/convert.py | 132 ++++++++++++++++++++++++++++++++++++ pdfplumber/page.py | 39 ++++++++--- pdfplumber/utils.py | 33 +++++++-- tests/pdfs/annotations.pdf | Bin 0 -> 132177 bytes tests/pdfs/pdffill-demo.pdf | Bin 108017 -> 120145 bytes tests/test_basics.py | 9 ++- tests/test_convert.py | 76 +++++++++++++++++++++ tests/test_utils.py | 1 + 11 files changed, 296 insertions(+), 108 deletions(-) mode change 100755 => 100644 pdfplumber/cli.py create mode 100644 pdfplumber/convert.py create mode 100644 tests/pdfs/annotations.pdf create mode 100644 tests/test_convert.py diff --git a/README.md b/README.md index 474eda2..be004c7 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ The output will be a CSV containing info about every character, line, and rectan | Argument | Description | |----------|-------------| -|`--format [format]`| `csv` or `json`. The `json` format returns slightly more information; it includes PDF-level metadata and height/width information about each page.| +|`--format [format]`| `csv` or `json`. The `json` format returns more information; it includes PDF-level and page-level metadata, plus dictionary-nested attributes.| |`--pages [list of pages]`| A space-delimited, `1`-indexed list of pages or hyphenated page ranges. E.g., `1, 11-15`, which would return data for pages 1, 11, 12, 13, 14, and 15.| -|`--types [list of object types to extract]`| Choices are `char`, `line`, `curve`, `rect`, `rect_edge`. Defaults to `char`, `line`, `curve`, `rect`.| +|`--types [list of object types to extract]`| Choices are `char`, `rect`, `line`, `curve`, `image`, `annot`. Defaults to all.| ## Python library diff --git a/pdfplumber/cli.py b/pdfplumber/cli.py old mode 100755 new mode 100644 index a2d1c5e..3b72193 --- a/pdfplumber/cli.py +++ b/pdfplumber/cli.py @@ -1,116 +1,50 @@ #!/usr/bin/env python -import pdfplumber +from . import convert +from .pdf import PDF import argparse from itertools import chain - -try: - from cdecimal import Decimal, ROUND_HALF_UP -except ImportError: - from decimal import Decimal, ROUND_HALF_UP -import unicodecsv -import codecs -import json import sys -class DecimalEncoder(json.JSONEncoder): - def default(self, o): - if isinstance(o, Decimal): - return float(o.quantize(Decimal(".0001"), rounding=ROUND_HALF_UP)) - return super(DecimalEncoder, self).default(o) - - def parse_page_spec(p_str): if "-" in p_str: - return list(range(*map(int, p_str.split("-")))) + start, end = map(int, p_str.split("-")) + return range(start, end + 1) else: return [int(p_str)] -def parse_args(): +def parse_args(args_raw): parser = argparse.ArgumentParser("pdfplumber") - stdin = sys.stdin.buffer if sys.version_info[0] >= 3 else sys.stdin parser.add_argument( - "infile", nargs="?", type=argparse.FileType("rb"), default=stdin + "infile", nargs="?", type=argparse.FileType("rb"), default=sys.stdin.buffer ) parser.add_argument("--format", choices=["csv", "json"], default="csv") - parser.add_argument("--encoding", default="utf-8") - - TYPE_DEFAULTS = ["char", "anno", "line", "curve", "rect"] parser.add_argument( "--types", nargs="+", - choices=TYPE_DEFAULTS + ["rect_edge"], - default=TYPE_DEFAULTS, + default=convert.DEFAULT_TYPES, + choices=convert.DEFAULT_TYPES, ) parser.add_argument("--pages", nargs="+", type=parse_page_spec) - args = parser.parse_args() + parser.add_argument( + "--indent", type=int, help="Indent level for JSON pretty-printing." + ) + + args = parser.parse_args(args_raw) if args.pages is not None: args.pages = list(chain(*args.pages)) return args -def to_csv(pdf, types, encoding): - objs = [] - fields = set() - for t in types: - new_objs = getattr(pdf, t + "s") - if len(new_objs): - objs += new_objs - fields = fields.union(set(new_objs[0].keys())) - - first_columns = [ - "object_type", - "page_number", - "x0", - "x1", - "y0", - "y1", - "doctop", - "top", - "bottom", - "width", - "height", - ] - - cols = first_columns + list(sorted(set(fields) - set(first_columns))) - stdout = sys.stdout.buffer if sys.version_info[0] >= 3 else sys.stdout - w = unicodecsv.DictWriter(stdout, fieldnames=cols, encoding=encoding) - w.writeheader() - w.writerows(objs) - - -def to_json(pdf, types, encoding): - data = {"metadata": pdf.metadata} - - def get_page_data(page): - d = dict((t + "s", getattr(page, t + "s")) for t in types) - d["width"] = page.width - d["height"] = page.height - return d - - data["pages"] = list(map(get_page_data, pdf.pages)) - - if hasattr(sys.stdout, "buffer"): - sys.stdout = codecs.getwriter("utf-8")(sys.stdout.buffer, "strict") - json.dump(data, sys.stdout, cls=DecimalEncoder) - else: - json.dump(data, sys.stdout, cls=DecimalEncoder, encoding=encoding) - - -def main(): - args = parse_args() - pdf = pdfplumber.open(args.infile, pages=args.pages) - if args.format == "csv": - to_csv(pdf, args.types, args.encoding) - else: - to_json(pdf, args.types, args.encoding) - - -if __name__ == "__main__": - main() +def main(args_raw=sys.argv[1:]): + args = parse_args(args_raw) + converter = {"csv": convert.to_csv, "json": convert.to_json}[args.format] + kwargs = {"csv": {}, "json": {"indent": args.indent}}[args.format] + with PDF.open(args.infile, pages=args.pages) as pdf: + converter(pdf, sys.stdout, args.types, **kwargs) diff --git a/pdfplumber/container.py b/pdfplumber/container.py index e483674..2a142c6 100644 --- a/pdfplumber/container.py +++ b/pdfplumber/container.py @@ -1,5 +1,5 @@ from itertools import chain -from . import utils +from . import utils, convert class Container(object): @@ -64,3 +64,7 @@ def test(x): return x["orientation"] == "v" return list(filter(test, self.edges)) + + +Container.to_json = convert.to_json +Container.to_csv = convert.to_csv diff --git a/pdfplumber/convert.py b/pdfplumber/convert.py new file mode 100644 index 0000000..fb34003 --- /dev/null +++ b/pdfplumber/convert.py @@ -0,0 +1,132 @@ +from .utils import decode_text +from decimal import Decimal, ROUND_HALF_UP +from pdfminer.pdftypes import PDFStream, PDFObjRef +from pdfminer.psparser import PSLiteral +import json +import csv +import base64 +from io import StringIO + +DEFAULT_TYPES = [ + "char", + "rect", + "line", + "curve", + "image", + "annot", +] + +COLS_TO_PREPEND = [ + "object_type", + "page_number", + "x0", + "x1", + "y0", + "y1", + "doctop", + "top", + "bottom", + "width", + "height", +] + +ENCODINGS_TO_TRY = [ + "utf-8", + "latin-1", + "utf-16", + "utf-16le", +] + + +def to_b64(data_bytes): + return base64.b64encode(data_bytes).decode("ascii") + + +def serialize(obj): + # Convert int-like + t = type(obj) + if t is Decimal: + return float(obj.quantize(Decimal(".0001"), rounding=ROUND_HALF_UP)) + # If tuple/list passed, bulk-convert + elif t in (list, tuple): + return t(serialize(x) for x in obj) + elif t is dict: + return {k: serialize(v) for k, v in obj.items()} + elif t is PDFStream: + return {"rawdata": to_b64(obj.rawdata)} + elif t is PSLiteral: + return decode_text(obj.name) + elif t is bytes: + try: + for e in ENCODINGS_TO_TRY: + return obj.decode(e) + # If none of the decodings work, raise whatever error + # decoding with utf-8 causes + except: # pragma: no cover + obj.decode(ENCODINGS_TO_TRY[0]) + elif obj is None: + return None + elif t in (int, float, str, bool): + return obj + else: + return str(obj) + + +def to_json(container, stream=None, types=DEFAULT_TYPES, indent=None): + def page_to_dict(page): + d = { + "page_number": page.page_number, + "initial_doctop": page.initial_doctop, + "rotation": page.rotation, + "cropbox": page.cropbox, + "mediabox": page.mediabox, + "bbox": page.bbox, + "width": page.width, + "height": page.height, + } + for t in types: + d[t + "s"] = getattr(page, t + "s") + return d + + if hasattr(container, "pages"): + data = { + "metadata": container.metadata, + "pages": list(map(page_to_dict, container.pages)), + } + else: + data = page_to_dict(container) + + serialized = serialize(data) + + if stream is None: + return json.dumps(serialized, indent=indent) + else: + return json.dump(serialized, stream, indent=indent) + + +def to_csv(container, stream=None, types=DEFAULT_TYPES): + if stream is None: + stream = StringIO() + to_string = True + else: + to_string = False + + objs = [] + + # Determine set of fields for all objects + fields = set() + for t in types: + new_objs = getattr(container, t + "s") + if len(new_objs): + objs += new_objs + new_keys = [k for k, v in new_objs[0].items() if type(v) is not dict] + fields = fields.union(set(new_keys)) + + cols = COLS_TO_PREPEND + list(sorted(set(fields) - set(COLS_TO_PREPEND))) + + w = csv.DictWriter(stream, fieldnames=cols, extrasaction="ignore") + w.writeheader() + w.writerows(serialize(objs)) + if to_string: + stream.seek(0) + return stream.read() diff --git a/pdfplumber/page.py b/pdfplumber/page.py index 38c9667..1a4f232 100644 --- a/pdfplumber/page.py +++ b/pdfplumber/page.py @@ -2,7 +2,6 @@ from .utils import resolve, resolve_all from .table import TableFinder from .container import Container - import re lt_pat = re.compile(r"^LT") @@ -60,30 +59,45 @@ def layout(self): @property def annots(self): def parse(annot): - data = resolve(annot.resolve()) - rect = self.decimalize(resolve_all(data["Rect"])) + rect = self.decimalize(annot["Rect"]) + + a = annot.get("A", {}) + extras = { + "uri": a.get("URI"), + "title": annot.get("T"), + "contents": annot.get("Contents"), + } + for k, v in extras.items(): + if v is not None: + extras[k] = v.decode("utf-8") + parsed = { "page_number": self.page_number, + "object_type": "annot", + "x0": rect[0], + "y0": rect[1], + "x1": rect[2], + "y1": rect[3], "doctop": self.initial_doctop + self.height - rect[3], "top": self.height - rect[3], - "x0": rect[0], "bottom": self.height - rect[1], - "x1": rect[2], "width": rect[2] - rect[0], "height": rect[3] - rect[1], - "data": data, } - uri = data.get("A", {}).get("URI") - if uri is not None: - parsed["URI"] = uri.decode("utf-8") + parsed.update(extras) + # Replace the indirect reference to the page dictionary + # with a pointer to our actual page + if "P" in annot: + annot["P"] = self + parsed["data"] = annot return parsed - raw = resolve(self.page_obj.annots) or [] + raw = resolve_all(self.page_obj.annots) or [] return list(map(parse, raw)) @property def hyperlinks(self): - return [a for a in self.annots if "URI" in a] + return [a for a in self.annots if a["uri"] is not None] @property def objects(self): @@ -246,6 +260,9 @@ def to_image(self, **conversion_kwargs): kwargs["resolution"] = DEFAULT_RESOLUTION return PageImage(self, **kwargs) + def __repr__(self): + return f"" + class DerivedPage(Page): is_original = False diff --git a/pdfplumber/utils.py b/pdfplumber/utils.py index ea8791a..4d1edda 100644 --- a/pdfplumber/utils.py +++ b/pdfplumber/utils.py @@ -1,6 +1,8 @@ from pdfminer.utils import PDFDocEncoding from pdfminer.psparser import PSLiteral from pdfminer.pdftypes import PDFObjRef +from pdfminer.pdfdocument import PDFDocument +from pdfminer.pdfpage import PDFPage from decimal import Decimal, ROUND_HALF_UP import numbers from operator import itemgetter, gt, lt, add, sub @@ -92,20 +94,37 @@ def resolve(x): return x -# via pdfminer.pdftypes, altered slightly +def get_dict_type(d): + if type(d) is not dict: + return None + t = d.get("Type") + if type(t) is PSLiteral: + return decode_text(t.name) + else: + return t + + def resolve_all(x): """ Recursively resolves the given object and all the internals. """ t = type(x) if t == PDFObjRef: - return resolve_all(x.resolve()) - elif t == list: - return [resolve_all(v) for v in x] - elif t == tuple: - return tuple(resolve_all(v) for v in x) + resolved = x.resolve() + + # Avoid infinite recursion + if get_dict_type(resolved) == "Page": + return x + + return resolve_all(resolved) + elif t in (list, tuple): + return t(resolve_all(v) for v in x) elif t == dict: - return dict((k, resolve_all(v)) for k, v in x.items()) + if get_dict_type(x) == "Annot": + exceptions = ["Parent"] + else: + exceptions = [] + return dict((k, v if k in exceptions else resolve_all(v)) for k, v in x.items()) else: return x diff --git a/tests/pdfs/annotations.pdf b/tests/pdfs/annotations.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e8f76d91203d02bdce4fa1d9a345a05494ba85d9 GIT binary patch literal 132177 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 z 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 literal 0 HcmV?d00001 diff --git a/tests/pdfs/pdffill-demo.pdf b/tests/pdfs/pdffill-demo.pdf index dcc7eb3227c06c849da100606ca146eaaa00280c..a63d021dd2d9ec548ffa63563555871152cd8fde 100644 GIT binary patch delta 12178 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 diff --git a/tests/test_basics.py b/tests/test_basics.py index 414dd98..37f55f9 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -28,7 +28,8 @@ def test_pagecount(self): assert(len(self.pdf.pages) == 1) def test_page_number(self): - assert(self.pdf.pages[0].page_number == 1) + assert self.pdf.pages[0].page_number == 1 + assert str(self.pdf.pages[0]) == "" def test_objects(self): assert len(self.pdf.chars) @@ -46,7 +47,11 @@ def test_annots(self): assert len(pdf.annots) assert len(pdf.hyperlinks) == 17 uri = "http://www.pdfill.com/pdf_drawing.html" - assert pdf.hyperlinks[0]["URI"] == uri + assert pdf.hyperlinks[0]["uri"] == uri + + path = os.path.join(HERE, "pdfs/annotations.pdf") + with pdfplumber.open(path) as pdf: + assert len(pdf.annots) def test_crop_and_filter(self): def test(obj): diff --git a/tests/test_convert.py b/tests/test_convert.py new file mode 100644 index 0000000..0ebc8d8 --- /dev/null +++ b/tests/test_convert.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +import unittest +import pytest +import pandas as pd +import pdfplumber +from subprocess import Popen, PIPE +from io import StringIO +import json +import sys +import os + +import logging +logging.disable(logging.ERROR) + +HERE = os.path.abspath(os.path.dirname(__file__)) + +def run(cmd): + return Popen(cmd, stdout = PIPE).communicate()[0] + +class Test(unittest.TestCase): + + @classmethod + def setup_class(self): + self.path = os.path.join(HERE, "pdfs/pdffill-demo.pdf") + self.pdf = pdfplumber.open(self.path, pages = [ 1, 2, 5 ]) + + @classmethod + def teardown_class(self): + self.pdf.close() + + def test_json(self): + c = json.loads(self.pdf.to_json()) + assert c["pages"][0]["rects"][0]["bottom"] == float(self.pdf.pages[0].rects[0]["bottom"]) + + def test_single_pages(self): + c = json.loads(self.pdf.pages[0].to_json()) + assert c["rects"][0]["bottom"] == float(self.pdf.pages[0].rects[0]["bottom"]) + + def test_additional_attr_types(self): + path = os.path.join(HERE, "pdfs/issue-67-example.pdf") + with pdfplumber.open(path, pages = [ 1 ]) as pdf: + c = json.loads(pdf.to_json()) + assert len(c["pages"][0]["images"]) + + def test_csv(self): + c = self.pdf.to_csv() + assert c.split("\r\n")[1] == ( + 'char,1,45.83,58.826,656.82,674.82,117.18,117.18,135.18,12.996,' + '18.0,12.996,,,,,,TimesNewRomanPSMT,,,,"(0, 0, 0)",,,18.0,,,,,Y,,1,' + ) + + io = StringIO() + self.pdf.to_csv(io) + io.seek(0) + c_from_io = io.read() + assert c == c_from_io + + + def test_cli(self): + res = run([ + "pdfplumber", + self.path, + "--format", + "json", + "--pages", + "1-2", + "5", + "--indent", + "2", + ]) + + c = json.loads(res) + assert c["pages"][0]["page_number"] == 1 + assert c["pages"][1]["page_number"] == 2 + assert c["pages"][2]["page_number"] == 5 + assert c["pages"][0]["rects"][0]["bottom"] == float(self.pdf.pages[0].rects[0]["bottom"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index fb06d26..67cc718 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -41,6 +41,7 @@ def test_resolve(self): annot = self.pdf.annots[0] annot_ad0 = utils.resolve(annot["data"]["A"]["D"][0]) assert annot_ad0["MediaBox"] == [0, 0, 612, 792] + assert utils.resolve(1) == 1 def test_resolve_all(self): info = self.pdf.doc.xrefs[0].trailer["Info"]