mirror of
https://github.com/jsvine/pdfplumber.git
synced 2026-08-29 08:34:23 +08:00
feat: add --structure-text, like pdfinfo -struct-text (but better)
This commit is contained in:
+38
-4
@@ -2,8 +2,9 @@
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict, deque
|
||||
from itertools import chain
|
||||
from typing import List
|
||||
from typing import Any, DefaultDict, Dict, List
|
||||
|
||||
from .pdf import PDF
|
||||
|
||||
@@ -22,13 +23,20 @@ def parse_args(args_raw: List[str]) -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"infile", nargs="?", type=argparse.FileType("rb"), default=sys.stdin.buffer
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"--structure",
|
||||
help="Write the structure tree as JSON. "
|
||||
"All arguments except --pages, --laparams, and --indent will be ignored",
|
||||
"All other arguments except --pages, --laparams, and --indent will be ignored",
|
||||
action="store_true",
|
||||
)
|
||||
group.add_argument(
|
||||
"--structure-text",
|
||||
help="Write the structure tree as JSON including text contents. "
|
||||
"All other arguments except --pages, --laparams, and --indent will be ignored",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument("--format", choices=["csv", "json"], default="csv")
|
||||
|
||||
parser.add_argument("--types", nargs="+")
|
||||
@@ -61,12 +69,38 @@ def parse_args(args_raw: List[str]) -> argparse.Namespace:
|
||||
return args
|
||||
|
||||
|
||||
def add_text_to_mcids(pdf: PDF, data: List[Dict[str, Any]]) -> None:
|
||||
page_contents: DefaultDict[int, Any] = defaultdict(lambda: defaultdict(str))
|
||||
for page in pdf.pages:
|
||||
text_contents = page_contents[page.page_number]
|
||||
for c in page.chars:
|
||||
mcid = c.get("mcid")
|
||||
if mcid is None:
|
||||
continue
|
||||
text_contents[mcid] += c["text"]
|
||||
d = deque(data)
|
||||
while d:
|
||||
el = d.popleft()
|
||||
if "children" in el:
|
||||
d.extend(el["children"])
|
||||
pageno = el.get("page_number")
|
||||
if pageno is None:
|
||||
continue
|
||||
text_contents = page_contents[pageno]
|
||||
if "mcids" in el:
|
||||
el["text"] = [text_contents[mcid] for mcid in el["mcids"]]
|
||||
|
||||
|
||||
def main(args_raw: List[str] = sys.argv[1:]) -> None:
|
||||
args = parse_args(args_raw)
|
||||
|
||||
with PDF.open(args.infile, pages=args.pages, laparams=args.laparams) as pdf:
|
||||
if args.structure:
|
||||
json.dump(pdf.structure_tree, sys.stdout, indent=args.indent)
|
||||
elif args.structure_text:
|
||||
tree = pdf.structure_tree
|
||||
add_text_to_mcids(pdf, tree)
|
||||
json.dump(tree, sys.stdout, indent=args.indent, ensure_ascii=False)
|
||||
elif args.format == "csv":
|
||||
pdf.to_csv(
|
||||
sys.stdout,
|
||||
|
||||
@@ -16,6 +16,114 @@ logging.disable(logging.ERROR)
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
SCOTUS_TEXT = [
|
||||
{
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"LineHeight": 25.75,
|
||||
"TextIndent": 21.625,
|
||||
"O": "Layout",
|
||||
},
|
||||
"mcids": [1],
|
||||
"text": [
|
||||
"IN THE SUPREME COURT OF THE UNITED STATES - - - - - - - - - - - - "
|
||||
"- - - - - x MICHAEL A. KNOWLES, : WARDEN, :"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"LineHeight": 25.75,
|
||||
"StartIndent": 86.375,
|
||||
"O": "Layout",
|
||||
},
|
||||
"mcids": [2],
|
||||
"text": [" Petitioner :"],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"LineHeight": 25.75,
|
||||
"TextIndent": 50.375,
|
||||
"O": "Layout",
|
||||
},
|
||||
"mcids": [3, 4],
|
||||
"text": [
|
||||
" v. ",
|
||||
": No. 07-1315 ALEXANDRE MIRZAYANCE. : - - - - - - - - - - - - - -"
|
||||
" - - - x",
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"O": "Layout",
|
||||
"SpaceAfter": 24.5,
|
||||
"LineHeight": 25.75,
|
||||
"StartIndent": 165.625,
|
||||
"EndIndent": 57.625,
|
||||
},
|
||||
"mcids": [5],
|
||||
"text": [" Washington, D.C. Tuesday, January 13, 2009"],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"LineHeight": 25.75,
|
||||
"TextIndent": 100.75,
|
||||
"O": "Layout",
|
||||
},
|
||||
"mcids": [6],
|
||||
"text": [
|
||||
" The above-entitled matter came on for oral argument before the "
|
||||
"Supreme Court of the United States at 1:01 p.m. APPEARANCES: "
|
||||
"STEVEN E. MERCER, ESQ., Deputy Attorney General, Los"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {
|
||||
"O": "Layout",
|
||||
"SpaceAfter": 179.125,
|
||||
"LineHeight": 25.75,
|
||||
"TextIndent": 21.625,
|
||||
"EndIndent": 50.375,
|
||||
"TextAlign": "None",
|
||||
},
|
||||
"mcids": [7],
|
||||
"text": [
|
||||
" Angeles, Cal.; on behalf of the Petitioner. CHARLES M. SEVILLA, "
|
||||
"ESQ., San Diego, Cal.; on behalf of the Respondent. "
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {"O": "Layout", "TextAlign": "Center", "SpaceAfter": 8.5},
|
||||
"mcids": [8],
|
||||
"text": ["1\n"],
|
||||
},
|
||||
{
|
||||
"type": "P",
|
||||
"page_number": 1,
|
||||
"attributes": {"O": "Layout", "TextAlign": "Center"},
|
||||
"mcids": [9],
|
||||
"text": ["Alderson Reporting Company "],
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def run(cmd):
|
||||
return Popen(cmd, stdout=PIPE).communicate()[0]
|
||||
|
||||
@@ -89,6 +197,12 @@ class Test(unittest.TestCase):
|
||||
# lol no structure
|
||||
assert c == []
|
||||
|
||||
def test_cli_structure_text(self):
|
||||
path = os.path.join(HERE, "pdfs/scotus-transcript-p1.pdf")
|
||||
res = run([sys.executable, "-m", "pdfplumber.cli", path, "--structure-text"])
|
||||
c = json.loads(res)
|
||||
assert c == SCOTUS_TEXT
|
||||
|
||||
def test_cli_json(self):
|
||||
res = run(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user