Распространенной и сложной проблемой получения данных является извлечение таблиц из PDF. Ранее я описывал, как извлечь текст из PDF с помощью PDF.js , библиотеки рендеринга PDF, созданной Mozilla Labs.
Процесс рендеринга требует HTML-объекта canvas, а затем рисует на нем каждый объект (символ, линию, прямоугольник и т. Д.). Самый простой способ получить их список — перехватить все вызовы PDF.js, выполняемые функциями рисования на объекте canvas. (см. « Самомодифицирующиеся JavaScript » для аналогичной техники). Метод «set» ниже добавляет закрытие оболочки к каждой функции, которая регистрирует вызов.
function replace(ctx, key) {
var val = ctx[key];
if (typeof(val) == "function") {
ctx[key] = function() {
var args = Array.prototype.slice.call(arguments);
console.log("Called " + key + "(" + args.join(",") + ")");
return val.apply(ctx, args);
}
}
}
for (var k in context) {
replace(context, k);
}
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
Это позволяет нам увидеть серию звонков:
Called transform(1,0,0,1,150.42,539.67) Called translate(0,0) Called scale(1,-1) Called scale(0.752625,0.752625) Called measureText(C) Called save() Called scale(0.9701818181818181,1) Called fillText(C,0,0) Called restore() Called restore() Called save() Called transform(1,0,0,1,150.42,539.6
Мы можем легко получить текст, отметив первый аргумент для каждого вызова «fillText»:
"Congregations Ranked by Growth and Decline in Membership and Worship Attendance, 2006 to 2011Philadelphia Presbytery - Table 16Net Membership ChangeNet Worship ChangePercent ChangePercent ChangeWorship 2006Worship 2011Membership 2006Membership 2011Abington, Abington- 143(74)-13.18%(57)0(15)0.00%(22)NumberRank3003001,085942Anchor, Wrightstown0(23)0.00%(27)-12(25)-21.43%(52)NumberRank56449797Arch Street, Philadelphia-117(71)-68.42%(117)27(5)90.00% (2)NumberRank305717154Aston, Aston3(21)3.53%(22)-5(19)-9.43% (31)NumberRank53488588BeaconNo reportboth yearsNo reportboth yearsNumberRankBensalem, Bensalem-23(39)-13.94%(62)-28(36)-28.57% (64)NumberRank9870165142Berean, Philadelphia106(4)44.92%(4)No reportboth yearsNumberRank00236342Bethany Collegiate, Havertown- 188(76)-42.44%(110)43(3)21.29%(7)NumberRank202245443255Bethel, Philadelphia-13(33)-13.68%(60)-27(35)-35.06% (71)NumberRank77509582Bethesda, Philadelphia9(18)5.56%(18)No reportboth yearsNumberRank1150162171Beverly Hills, Upper Darby-3(26)-3.03% (32)-11(24)-20.00%(48)NumberRank55449996Bridesburg, Philadelphia0(23)0.00%(27)No reportboth yearsNumberRank004444Bristol, BristolNo reportboth yearsNo reportboth yearsNumberRankPage 1 of 10Report prepared by Research Services, Presbyterian Church (U.S.A.)1- 800-728-7228, ext #204006-Oct-12"
Примечательно, что это не отслеживает окончания строк, и не все символы записываются в ожидаемом порядке (первая строка отображается после второй).
Призывы к преобразованию, переводу и масштабированию контролируют место размещения текста. Метод fillText также принимает набор параметров (x, y), который перемещает отдельные буквы между словами. Точная позиция — это комбинация последовательных операций, которые моделируются как стек матричных операций.
К счастью, PDF.js отслеживает вывод этих операций по мере их рендеринга, поэтому нам не нужно пересчитывать его.
Таким образом, мы можем сделать метод, который записывает буквы и их реальные позиции. Этот метод принимает объект внутреннего контекста, тип перехода состояния и аргументы перехода. Этот метод затем вызывается из функции ‘record’, указанной выше.
var chars = [];
var cur = {};
function record(ctx, state, args) {
if (state === 'fillText') {
var c = args[0];
cur.c = c;
cur.x = ctx._transformMatrix[4] + args[1];
cur.y = ctx._transformMatrix[5] + args[2];
chars[chars.length] = cur;
cur = {};
}
}
Эти результаты могут быть отсортированы по позиции (х и у). Метод sort упорядочивает буквы по позициям — если они сдвинуты на небольшую величину вверх или вниз, они считаются одной строкой.
chars.sort(
function(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
if (Math.abs(dy) < 0.5) {
return dx * -1;
} else {
return dy * -1;
}
}
);
Это создает несколько трудностей: это не обнаруживает текст справа налево, и становится ясно, что нам будет трудно узнать, когда вы находитесь за столом, а когда нет.
Для этого мы определяем функцию, которая может преобразовывать массив букв и позиций в вывод в стиле CSV. Это отслеживает от буквы к букве — если он видит «большое» изменение у, он создает новую строку. Если он видит «большое» изменение в x, он рассматривает его как новый столбец.
Реальная проблема — определить «большой», который для моего тестового PDF был около 15 и 20, для dx и dy.
function getText(marks, ex, ey, v) {
var x = marks[0].x;
var y = marks[0].y;
var txt = '';
for (var i = 0; i < marks.length; i++) {
var c = marks[i];
var dx = c.x - x;
var dy = c.y - y;
if (Math.abs(dy) > ey) {
txt += "\"\n\"";
if (marks[i+1]) {
// line feed - start from position of next line
x = marks[i+1].x;
}
}
if (Math.abs(dx) > ex) {
txt += "\",\"";
}
if (v) {
console.log(dx + ", " + dy);
}
txt += c.c;
x = c.x;
y = c.y;
}
return txt;
}
Этот алгоритм не обрабатывает новые строки в строках, и, как ни странно, столбцы выходят не в правильном порядке, но они выглядят непоследовательно. Строки с большими пробелами (например, em-dash) обнаруживаются как имеющие несколько столбцов, но это может быть очищено позже — вот пример выходных данных.
Вы можете увидеть пример ниже, и окончательный источник доступен на github .
Congregations Ranked by Growth and Decline in M","embership and W","orship Attendance, 2006 to 2011" "","Philadelphia Presbytery"," - Table 16" "","Net ","Membership ","Change" "","Net Worship ","Change","Percent ","Change","Percent ","Change","Worship"," 2006","Worship"," 2011","Membership"," 2006","Membership"," 2011" "","Abington, Abington","-143","(74)","-13.18%(57)","0","(15)","0.00%(22)","Number","Rank","300","300","1,085","942" "","Anchor, Wrightstown","0","(23)","0.00%(27)","-12","(25)","-21.43%(52)","Number","Rank","56","44","97","97" "","Arch Street, Philadelphia","-117","(71)","-68.42%","(117)","27(5)","90.00%(2)","Number","Rank","30","57","171","54" "","Aston, Aston","3","(21)","3.53%(22)","-5","(19)","-9.43%(31)","Number","Rank","53","48","85","88" "","Beacon","No report","both years","No report","both years","Number","Rank" "","Bensalem, Bensalem","-23","(39)","-13.94%(62)","-28","(36)","-28.57%(64)","Number","Rank","98","70","165","142" "","Berean, Philadelphia","106(4)","44.92%(4)","No report","both years","Number","Rank","0","0","236","342" "","Bethany Collegiate, Havertown","-188","(76)","-42.44%","(110)","43(3)","21.29%(7)","Number","Rank","202","245","443","255" "","Bethel, Philadelphia","-13","(33)","-13.68%(60)","-27","(35)","-35.06%(71)","Number","Rank","77","50","95","82" "","Bethesda, Philadelphia","9","(18)","5.56%(18)","No report","both years","Number","Rank","115","0","162","171" "","Beverly Hills, Upper Darby","-3","(26)","-3.03%(32)","-11","(24)","-20.00%(48)","Number","Rank","55","44","99","96" "","Bridesburg, Philadelphia","0","(23)","0.00%(27)","No report","both years","Number","Rank","0","0","44","44" "","Bristol, Bristol","No report","both years","No report","both years","Number","Rank" "","Page 1 of 10","Report prepared by Research Services, Presbyterian Church (U.S.A.)","1-800-728-7228, ext #2040","06-Oct-12"
