A PDFium Component 3.117.0 verziója akkor kapcsol össze egy oldalhatáron átnyúló táblát, ha vagy mindkét töredék hozzáér az oldal széléhez, vagy nincs törzsszöveg az első töredék alatt és a második felett, az élőfejeket és élőlábakat figyelmen kívül hagyva. Az ExtractDocumentTables ezt a tartalomtudatos vizsgálatot a régebbi oldalmargó-vizsgálat alternatívájaként alkalmazza, visszautasítja azt a következő oldali töredéket, amelynek az első sora egyetlen teljes szélességű feliratozócella, és a következő oldalra átcsorduló egyetlen sort is a folytatási lánc részeként tartja meg
A táblázatdetektálásról és -kinyerésről szóló cikk a folytatást négy szigorú kapuként mutatta be, és ezek egyikeként kezelte az „oldal széléhez ér" feltételt. Az a leírás pontos volt ahhoz a kiadáshoz, amelyet lefedett, ugyanakkor téves volt azoknak a tábláknak a többségénél, amelyeket az emberek ténylegesen a komponens elé tesznek. Ez a cikk a helyesbítés: mely dokumentumokat nem tud kezelni a margóvizsgálat, mi lépett a helyébe, és a két mellékeset, amelyet a javítás magával rántott
Miért vall kudarcot az oldalmargó-vizsgálat a Word-exportoknál?
Az oldalmargó-vizsgálat azért vall kudarcot, mert a szövegszerkesztő az alsó margónál hagyja abba a sorok elrendezését, nem a papír szélénél. A ContinuationMargin 36 pontos alapértékével az eredeti szabály azt írta elő, hogy a korábbi töredék alsó éle az oldal aljától számított 36 ponton belül legyen, a későbbi töredék felső éle pedig az oldal tetejétől számított 36 ponton belül. Egy Wordből az alapértelmezett, egy hüvelykes margókkal exportált dokumentum az utolsó sort legalább 72 ponttal az oldal alja fölé teszi, élőláb esetén még feljebb, így a feltétel soha nem teljesült. Egy ilyen dokumentum minden hosszú táblája független töredékként jött vissza, nullán álló ContinuationGroup értékkel, a hívó pedig visszakerült a kézi összeférceléshez. A vizsgálat továbbra is értelmes ahhoz, amire tervezték: olyan elrendezésmotorok jelentéseihez, amelyek egy oldalt egy rögzített tartalmi dobozig töltenek ki, és a következő oldalt a tetejénél kezdik. Ez nem rossz szabály, hanem hiányos, ezért tartotta meg a 3.117.0, és tett mellé egy második utat a lecserélése helyett
Mit vizsgál helyette a tartalomtudatos teszt?
A tartalomtudatos vizsgálat azt ellenőrzi, hogy a két töredék közötti helyet a táblán kívül más is elfoglalja-e, mégpedig az egyes oldalak szódobozait használva az oldalgeometria helyett. Amíg az ExtractDocumentTables bejárja a dokumentumot, oldalanként feljegyzi az élőlábsáv fölött kezdődő szavak közül a legalacsonyabb alsó élt, valamint az élőfejsáv alatt végződő szavak közül a legmagasabb felső élt. Mindkét sáv ContinuationMargin pont mély, így ugyanaz az opció mostantól kettős szolgálatot teljesít: az oldalszél tartaléka és az élőfej-, illetve élőlábsávok magassága egyszerre. Egy töredékpár akkor megy át, ha a korábbi alsó éle az oldalán lévő legalsó törzsszövegnél nem feljebb van, a későbbi felső éle pedig a következő oldal legmagasabb törzsszövegénél nem lejjebb, mindkettő AlignmentTolerance értéken belül. Közönséges szavakkal: a tábla volt az utolsó dolog az N. oldalon és az első az N+1. oldalon, az oldalszám vagy a dokumentum címe pedig a margósávban nem számít. Ez a kizárás nem önkényes. Az ISO 32000-1 §14.8.2.2 az élőfejeket és élőlábakat tördelési artefaktumként osztályozza: olyan tartalomként, amely az oldaltörés miatt létezik, nem annak ellenére, és ugyanaz az elképzelés, amely miatt egy címkézett olvasó átugorhatja őket, az teszi lehetővé, hogy egy tábla folytatódjon rajtuk túl. A marked contentről szóló cikk azt tárgyalja, hogyan deklarálják a címkézett fájlok ezeket az artefaktumokat explicit módon; itt az osztályozás a pozícióból következik, mert a legtöbb exportált tábla egyáltalán nem hordoz címkéket
A két vizsgálat VAGY kapcsolatban áll össze. Egy elrendezésmotor-jelentés, amelynek a táblái a papír széléig futnak, átmegy az elsőn; egy Word-export, amelynek a táblái a margónál megállnak, átmegy a másodikon; egy dokumentum, amely mindkettőt tudja, kétszer is átmegy. Csak miután valamelyik sikerrel jár, futnak a többi kapuk, méghozzá rögzített sorrendben: az oldalszámoknak szomszédosaknak kell lenniük, a későbbi töredék nem kezdődhet feliratsorral, az oszlophatároknak pedig a kétszeres AlignmentTolerance értéken belül kell egyezniük, ami az alapértékekkel 6 pont. Az enumeráció a TPdfTableContinuation a ptcNone, ptcStart, ptcMiddle és ptcEnd értékekkel. Az a töredék, amelyet ptcEnd-ként jelöltünk meg, majd tovább kapcsolódik egy újabb oldalhoz, ptcMiddle szintre kerül elő, így egy háromoldalas tábla oldalsorrendben start, middle, end formában olvasható. A csoportszámok 1-től indulnak, a 0 pedig a nem kapcsoltat jelenti, a ToJson pedig ugyanezt az információt adja ki a continuation és continuationGroup tagokként, és ez a forma az előnyben részesítendő, ha a láncolást egy későbbi szolgáltatás végzi
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'itinerary-from-word.pdf';
Pdf.LoadDocument;
Options := TPdfTableExtractionOptions.Default;
Options.DetectContinuations := True; // alapérték; csak a szemléletesség kedvéért
Options.ContinuationMargin := 54; // kétsoros élőláb, kb. 50 pont mély
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
case Tables[I].Continuation of
ptcStart:
Writeln(Format('group %d starts on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
ptcMiddle, ptcEnd:
Writeln(Format('group %d continues on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
else
Writeln(Format('standalone table on page %d (%d rows)',
[Tables[I].PageNumber, Tables[I].RowCount]));
end;
finally
Pdf.Free;
end;
end;
Hogyan akadályozza meg a feliratsor két tábla összeolvadását?
Az a következő oldali töredék, amelynek az első sora egyetlen, minden oszlopra kiterjedő cella, új táblaként kezelődik, soha nem az előző folytatásaként. Ez a szabály azért létezik, mert a tartalomtudatos vizsgálat önmagában túl mohón kapcsol össze. Az eset, amely felszínre hozta, egy átiratjellegű űrlap volt: egy tábla véget ér az 1. oldal alja közelében, egy azonos oszlopszélességű második tábla kezdődik a 2. oldal teteje közelében, közöttük csak az élőláb ül, az oszlopok pedig pontosan egyeznek. A margóvizsgálat alatt a kettő soha nem találkozott, mert egyik sem ért széléhez; a tartalomvizsgálat alatt azonnal összekapcsolódtak, és a szakaszokra tagolt űrlapból egy összefüggéstelen rács lett. Ami elválasztja őket, az a cellaszerkezetben látszik. A második tábla egy szakaszfelirattal nyílik, például „RECIPIENT INFORMATION", amely egyetlen, teljes szélességben összevont cellaként van elrendezve, egy valódi folytatás pedig soha nem tesz ilyet, mert a felirat ahhoz a táblához tartozik, amely már az előző oldalon elkezdődött. A TableStartsWithCaptionRow pontosan ezt kódolja: a töredéknek legalább két oszlopa van, és tartalmaz olyan cellát, amelyre RowIndex = 0, ColumnIndex = 0 és ColumnSpan = ColumnCount. Az ellenőrzés csak a későbbi töredéken fut, így az a tábla, amelynek a saját feliratsora az első oldalán ül, érintetlen marad; a felirat az N. oldalon van, és csak az N+1. oldali töredéket vizsgáljuk
A következő oszlop-összehasonlítás, a TablesHaveMatchingColumns, szigorúbb annál, mint hogy „ugyanannyi oszlop". Újraépíti az egyes töredékek határpozícióit a cellák téglalapjaiból, interpolálja azokat a határokat, amelyeket az összevont cellák elrejtenek, és visszautasítja a párt, ha bármelyik határ a toleranciánál nagyobbat csúszik. Két négyoszlopos tábla, amelynek mások az arányai, így külön marad akkor is, ha minden más egyezik
Mi történik azzal az egyetlen sorral, amely átcsordul a következő oldalra?
Az a vonalazott rács, amely egyetlen sort visz át a következő oldalra, mostantól felismerődik és összekapcsolódik, feltéve hogy bekerül egy folytatási láncba; önmagában eldobódik. A 2-es alapértelmezett MinRows azért létezik, hogy egy kóbor vonalpár ne jelentődjön táblaként, a törésen átlökött utolsó sor viszont valódi sor, amelyet a 2-es kemény alsó korlát csendben eldobott, a tábla többi része pedig kereknek látszott, holott nem volt az. A dokumentumszintű bejárás három lépésben kezeli. Ha a DetectContinuations és a DetectRuledTables is be van állítva, az oldalankénti menet átmenetileg 1-re csökkentett sorkorláttal futtatja a vonalazott detektort, ezért fogad el az ExtractTables mostantól 1-es MinRows értéket a vonalazott rácsoknál, míg a whitespace-detektálás belső korlátja 2 marad. A folytatások a teljes eredmény felett jelölődnek meg. Ezután minden olyan tábla eltávolítódik, amely rövidebb a hívó MinRows értékénél, és nem része egyetlen láncnak sem. Az egysoros töredék csak azért éli túl, mert összekapcsolódott, egy egysoros rács pedig egyébként hétköznapi oldal közepén pontosan úgy szűrődik ki, mint korábban
// Minden lánc újraépítése egyetlen CSV-vé, a megismételt fejlécsorok eldobásával
// a folytatástöredékeken
procedure ExportChains(const Tables: TPdfTables; const Folder: string);
var
I, R: Integer;
Lines: TStringList;
Csv: TStringList;
begin
Csv := TStringList.Create;
Lines := TStringList.Create;
try
for I := 0 to High(Tables) do
begin
if Tables[I].Continuation in [ptcNone, ptcStart] then
Csv.Clear;
Lines.Text := string(Tables[I].ToCsv);
if (Tables[I].Continuation in [ptcMiddle, ptcEnd]) and
(Lines.Count > 1) and (Tables[I].RowCount > 1) then
Lines.Delete(0); // a szövegszerkesztő megismételte a fejlécet
for R := 0 to Lines.Count - 1 do
Csv.Add(Lines[R]);
if Tables[I].Continuation in [ptcNone, ptcEnd] then
Csv.SaveToFile(Format('%s\page%d-group%d.csv',
[Folder, Tables[I].PageNumber, Tables[I].ContinuationGroup]));
end;
finally
Lines.Free;
Csv.Free;
end;
end;
Két részlet szándékos ebben a rutinban. Az egysoros átcsordulás soha nem tűnik el, mert a RowCount-ra vonatkozó őr megtartja, az a szövegszerkesztő pedig, amely minden oldalon megismétli a fejlécsort, olyan töredéket állít elő, amelynek az első sora megint a fejléc, így a nulladik sor eldobása a middle és end töredékeken helyes abban az esetben, és helytelen egy olyan generátornál, amely nem ismétli a fejléceket. Egy dokumentumon ellenőrizze, mielőtt egy egész mappára ráereszti a rutint
Hol állnak meg a szabályok
A tartalomtudatos vizsgálat csak annyit ér, amennyire jó az a szövegréteg, amelyet olvas. Egy teljesen szöveg nélküli beolvasott oldalon a feljegyzett törzsszöveg-szélsőértékek az oldalhatárokra esnek vissza, a „nincs semmi közötte" feltétel üresen teljesül, és csak a feliratsor- és oszlopkapuk maradnak; egy ilyen oldalon lévő vonalazott rácsot így is megtalál az elemző, üres csontvázként, tehát a lánc helyesen kapcsolódhat össze, a környező szövegről viszont valójában semmi nem igazolódott. Ha ez számít, előbb adjon hozzá szövegréteget. A képként, nem szövegként renderelt élőlábak láthatatlanok a sávlogika számára, és ugyanezért ártalmatlanok is
A sávok egyetlen számot jelentenek. A ContinuationMargin-nél mélyebb élőláb az alsó sorait a törzsszöveg-zónában hagyja, amitől a korábbi töredék úgy néz ki, mintha szöveg követné, és ez blokkolja a kapcsolást; emelje az opciót a valódi sávmélységre, ahogy az első példa teszi. Ha viszont túl messzire emeli, egy rövid záróbekezdés az oldal alja közelében bekerül a sávba, és figyelmen kívül marad, ami a táblát ahhoz kapcsolja, ami utána következik. A feliratszabálynak megvan a tükörkép-hibája is: az a generátor, amely egy összevont „continued" sávot ír minden folytatástöredék első soraként, azt fogja kapni, hogy ezek a töredékek új táblaként visszautasítódnak, és ma az egyetlen orvosság az, hogy a ContinuationGroup alapján saját maga varrja össze őket, mivel a szabálynak nincs kapcsolója
A whitespace-szel felismert táblák semmit nem kapnak az egysoros könnyítésből. A whitespace-stratégiának két egymáshoz igazodó sor kell ahhoz, hogy egyáltalán táblát lásson, így egy vonalazatlan tábla, amely egy sort átcsordít, továbbra is azzal a sorral rövidebben jelentődik. Ha ebbe belefut, a strukturált szövegblokkok és olvasási sorrend mögötti szódobozok adják a nyers pozíciókat a helyreállításához. Azon a mintahalmazon, amely ezt a munkát vezérelte — tizenhárom szövegszerkesztő- és böngészőexport —, az öt valódi többoldalas táblát tartalmazó dokumentum mind egyetlen lánccá kapcsolódott, a korábban összeolvadó átirat-űrlap pedig külön maradt, és ez az a mérce, amelyhez a kiadást mértük, nem ígéret minden lehetséges elrendezésre
A folytatásjelölés, a feliratszabály és az egysoros menet mind abban a dokumentumszintű útvonalban él, amelyet a Delphi-, C++Builder- és Lazarus-build közösen használ; a teljes táblakinyerési API a PDFium Component for Delphi oldalán van leírva