PDFium Component открива таблици на PDF страница и ги връща като мрежа от клетки с обхвати на редове и колони, редове на заглавие и стойност на увереност, чрез ExtractTables за една страница и ExtractDocumentTables за цял документ. Всяка таблица се преобразува в CSV или JSON с едно извикване, а таблици, продължаващи през прекъсване на страница, могат да бъдат свързани във верига от продължения
PDF няма обект за таблица. Таблица в PDF е набор от текстови редове, позиционирани така, че човек ги чете като мрежа, понякога с начертани около тях линии, а често без. Възстановяването на мрежата означава реконструиране на намерение, което файлът никога не е записал, поради което всеки инструмент за извличане произвежда леко различни резултати и защо инструмент, който ви казва увереността си, е по-полезен от такъв, който не го прави
Два режима на откриване за два вида таблица
Линираното откриване използва начертаните линии. Всеки очертан сегмент от път се трансформира в координати на страницата чрез матрицата на обекта на страницата, хоризонталните и вертикалните линии се пресичат, а пресечните точки образуват свързани компоненти. Всеки компонент става своя собствена сортирана мрежа от X и Y позиции, което е онова, което пази две отделни таблици на една и съща страница от сливане в една безсмислена мрежа
Откриването чрез празни пространства обработва таблици, начертани с подравняване, вместо с линии. Кутиите на думите се групират във визуални редове, празнините в рамките на ред го разделят на кандидат-колони, а таблица се приема само когато поне MinRows реда повтарят поне MinColumns подравнени отляво котви в рамките на AlignmentTolerance. Факторът на празнина между редове по подразбиране е 3, което покрива типичното разстояние от около 30 пункта между базови линии за 12-пунктов текст, без да позволи единичен ред, съдържащ няколко текстови отреза, да се представя за таблица
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'annual-report.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 12; // с индекс от 1
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // по подразбиране е 0.5
Options.HeaderRowCount := 1;
Tables := Pdf.ExtractTables(Options);
for I := 0 to High(Tables) do
Writeln(Format('table %d: %dx%d cells, confidence %.2f, mode %d',
[I, Tables[I].RowCount, Tables[I].ColumnCount,
Tables[I].Confidence, Ord(Tables[I].DetectionMode)]));
if Length(Tables) > 0 then
SaveText('page12-table0.csv', Tables[0].ToCsv);
finally
Pdf.Free;
end;
end;
Как се възстановяват слети клетки?
Тук наивните извличачи грешат. Слята клетка не може да бъде идентифицирана само от глобалната мрежа, защото мрежата се извежда от всички линии на страницата, а слят регион просто липсва вътрешната линия, която би го разделила
Правилото, използвано тук, е локално: две съседни базови клетки се сливат, когато никаква гранична линия не покрива интервала между тях. Union-find ги свързва, получените правоъгълни компоненти стават стойности на RowSpan и ColumnSpan, а текстът се приписва на базова клетка по нейната централна точка и след това следва тази клетка до нейния корен на сливане. Правенето по този начин също пази цената линейна в думи плюс клетки, вместо квадратичното сканиране, което получавате от тестване на всяка дума спрямо всяка клетка
Практическият ефект е, че финансова таблица със слято заглавие „Total“, обхващащо три колони, излиза като една клетка с обхват три, вместо една запълнена клетка и две загадъчни празни
Продължение през страници
Дългите таблици се прекъсват през страници, а третирането на фрагмента на всяка страница като независима таблица принуждава извикващия да ги съшива. ExtractDocumentTables може вместо това да ги свърже, но само при строги условия: фрагментът трябва да е най-долната таблица на по-ранната страница, следващият трябва да е най-горната таблица на следващата страница, номерата на страниците трябва да са съседни, а границите на колоните трябва да съвпадат
Всичките четири условия заедно са онова, което предотвратява очевидната грешка, а именно верижно свързване на всяка четириколонна таблица в документ в една въображаема мега-таблица, защото случайно споделят брой колони. Когато условията са изпълнени, таблиците споделят идентификатор на група за продължение и носят метаданни за продължение; когато не са, получавате отделни таблици и можете сами да решите
Извличането на ниво документ споделя бюджетите MaxCells и MaxTables през страници, вместо да ги нулира на всяка страница, и възстановява активната страница в блок finally, така че извличане, изпълнено във визуализатор, оставя потребителя да гледа страницата, на която е бил
Експортиране без повреждане на данните
И двата експортера са съзнателни относно екраниране. CSV винаги поставя полетата в кавички и удвоява вътрешните кавички, което избягва класическия провал, при който клетка, съдържаща запетая, тихомълком става две колони. За слети клетки съдържанието се издава само в горно-лявата котва, така че цикъл на запис и повторно четене на CSV не дублира обхващащо заглавие през колоните, които покрива
JSON запазва Unicode, вместо да го екранира в ASCII, екранира контролни знаци и включва метаданните, от които консуматор се нуждае, за да прецени качеството: режим на откриване, увереност, граници, стойности на обхват, флагове за заглавие и информация за продължение. Ако подавате извлечени таблици на downstream система, предпочетете JSON, защото CSV ред не може да ви каже, че таблицата, от която идва, е получила оценка на увереността 0,51:
// Извличане за целия документ, запазвайки само таблиците, на които може да се вярва
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
begin
if Tables[I].Confidence < 0.75 then
begin
Log(Format('page %d table needs review (%.2f)',
[Tables[I].PageNumber, Tables[I].Confidence]));
Continue;
end;
if Tables[I].ContinuationGroup > 0 then
AppendToGroup(Tables[I].ContinuationGroup, Tables[I].ToJson)
else
EmitStandalone(Tables[I].ToJson);
end;
Настройка и знание кога да спрете
Три настройки имат по-голямо значение от останалите. MinConfidence е портата за качество, а 0,5 е съзнателно снизходителна; повишете я за автоматизирано поглъщане и я понижете за интерфейс за преглед, където човек потвърждава всеки резултат. MinColumnGap решава какво се брои за граница на колона в режим на празни пространства, а плътно зададени таблици в наситени отчети може да се нуждаят от намаляването ѝ от стойността по подразбиране от 12 пункта. MaxRowGapFactor решава кога вертикалното разстояние приключва таблица, което има значение за таблици с случайни празни редове
Бъдете честни относно границите. Линираните таблици се извличат надеждно. Чисто подравнените таблици с празни пространства се извличат добре. Таблици със завъртян текст, вложени таблици или клетки, чието съдържание пренася в нещо, изглеждащо като друг ред, ще се нуждаят от преглед, независимо как са зададени параметрите. За тях структурираният текстов модел ви дава суровия материал за изграждане на четец за конкретна област, описан в структурирани текстови блокове и ред на четене
Едно полезно съчетание: когато сканиран документ изобщо няма текст, откриването на таблици няма с какво да работи, докато не съществува текстов слой. Добавете такъв първо, както е описано в добавяне на търсим текстов слой към сканирани PDF файлове, после извличайте. Кутиите на думи, връщани от доставчик на OCR, са точно входът, от който откриването чрез празни пространства се нуждае
Извличането на таблици, структурираният текст и преформатирането четат от един и същ модел на страница в Delphi, C++Builder и Lazarus; пълното API е описано на страницата на PDFium Component за Delphi