Detta är den andra artikeln i en serie fristående artiklar som jag skrev i början av 2015 med anledning av en diskussion som uppstått kring Personuppgiftslagen (PUL) och vad som är en “databas” i lagens bemärkelse. Artiklarna är löst tematiskt sammanhållna och behandlar var för sig praktiska, juridiska och programmeringstekniska aspekter av hur man mycket enkelt kan använda en uppsättning Wordfiler som en fullt fungerande databas.
Läs även gärna de övriga artiklarna i serien:
- PUL och Worddatabaser (juridiska och politiska aspekter)
- Att skapa en databas från Wordfiler: Läsa in data (programmering och datainläsning)
- Att skapa en databas från Wordfiler: städa upp i databasen (databearbetning)
- Efterord: Liberal tolkning av PUL och bristande teknisk kunskap hos svenska domstolar? (kommer inom kort)
Inledning
I denna artikel visar jag hur man, med hjälp av förhållandevis lite programkod, enkelt kan bygga en sökbar databas av en samling halvstrukturerade Worddokument. Till skillnad från den föregående artikeln i denna serie kommer jag här helt att fokusera på själva datahanteringsuppgiften, med hjälp av exempelkod skriven i R. Artikeln diskuterar både anatomi och arkitektur för själva dataproblemet.
Den programkod och de Worddokument som används nedan kan laddas hem, i lätt modifierad version, från det här Github-repot. Ladda gärna ned repot, lek runt med koden och berätta hur det gick i kommentarsfältet nedan!
En disclaimer är på sin plats. Varje någorlunda begåvad databasadministratör eller programmerare kommer nog inte att ha så mycket nytta av denna text. Syftet har varit att visa på en av många metoder för att komma åt och analysera innehållet i Wordfiler. Om man skulle vilja sätta upp ett produktionsflöde för att faktiskt, under verkliga förhållanden använda Word som databasplattform, skulle man naturligtvis använda sig av ett helt annat tillvägagångssätt. Min förhoppning med denna artikel är på sin höjd att kunna ge ett trovärdigt exempel på hur någon med högst måttliga programmeringskunskaper skulle kunna gå tillväga för att göra ett snabbt grävjobb eller liknande.
Data: Några exempeldokument
Den föregående artikeln resonerade kring hur Worddokument med känslig personinformation behandlats i ett aktuellt rättsfall. Låt oss därför använda oss av några Worddokument med sådan information för att se om det snabbt går att bygga en sökbar databas med innehållet från dessa.
Som ett test valde jag att skapa ett mindre antal wordfiler (6st) med “integritetskänslig”, om än påhittad, data. Filerna innehåller uppgifter om personalia (alltså personnummer och namn), privatekonomiska förhållanden, brottshistoria och lite annat. Dessutom lade jag in lite slumpmässigt valda rubriker i de olika dokumenten för att emulera en “stökig” databas.
Här är två exempel för att illustrera detta:
Exempel 1
Exempel 2
Den som är intresserad av ytterligare detaljer kan ladda ned samtliga wordfiler från exempelrepot.
Jag skapade även en mappstruktur för att ge ännu lite mer känsla av “äkthet”:
Det bör dock nämnas att det sistnämnda enbart är kosmetika. Det är nämligen fullkomligt irrelevant i vilken mappstruktur filerna är lagrade. Det enda viktiga är filernas innehåll och interna struktur.
Att bygga en scraper
Så nu har vi vår rådata - en mappstruktur med ett antal .docx-filer.
Som jag nämnde ovan är en .docx-fil ingenting annat än en zip-komprimerad mapp med ett antal XML-filer i. Den som är intresserad av att titta närmare på en DOCX-fils innehåll kan öppna den själv i valfritt unzip-program för att få fram mappen. Vill man ha en exakt specifikation över vilka filer som ingår och hur de är strukturerade kan lätt hitta en sådan på internet, t.ex. här, men egentligen är det enda vi behöver känna till detta: allt textinnehåll (som inte ingår i en tabell eller en graf) kan hittas i filen word/content.xml.
Eftersom all data vi är intresserade av här lagras i klartext i Worddokumentet, kan hela proceduren för att bygga en databas från Wordfiler sammanfattas som följer. I denna artikel koncentrerar jag mig på steg 1-3 nedan, medan nästa artikel behandlar steg 4-6.
- Avrkomprimera varje Wordfil
- Gå igenom innehållet i varje fil och lagra resultatet i en array som innehåller data från samtliga filer
- Använd någon form av parser för att tolka XML-data till en lista/array
- Säkerställ att data ser ungefär rätt ut och lagra den som tidy data
- Välj ut de data som är intressanta och spara dem i relationstabeller
- Voila!
För att göra en sådan här process så smidig som bara går vill vi kunna automatisera så mycket som möjligt av ovanstående. Jag gillar personligen att utveckla dataflöden i R, och då särskilt med paketen dplyr (för bearbetning av tabulär data och functional piping), stringr (för text processing) och rvest (för XML- och webscraping). Vi behöver även bestämma lite körvariabler.
(OBS: Om du är slängd i programering men inte van vid just R, kanske du kommer undra över att jag ofta använder operatorn %>%
nedan. Detta är pipe
-operatorn från dplyr-paketet, som används för att skriva funktionskedjor och skriva lättläst/lättutvärderad kod. Den som är intresserad kan läsa mer om dplyr och pipe-operatorn här.)
Extrahera data från Wordfilerna
Nu när vi satt upp vår programmeringsmiljö kan vi börja läsa in data. Till att börja med ska vi lista alla Wordfiler i vår mappstruktur.
I mappstrukturen finns det alltså sex filer. Nästa steg är att unzip:a dessa:
Vi har nu fått en prydlig filstruktur med sex avkomprimerade XML-arkiv:
Efter detta är det bara att läsa in filerna. Men vilka data är det vi letar efter? Allt vi letar efter i de aktuella filerna är rubriker och brödtext lagrat som klartext (och alltså inte i t.ex. tabeller eller metadata), så i detta fall behöver vi bara ta reda på var Word lagrar ett dokuments textinnehåll. En snabb titt på XML-specifikationen för .docx-filer (t.ex. här) visar att filen vi letar efter heter word/document.xml.
När vi väl läst in XML:en behöver vi ta reda på vilken information vi vill ha var och vilken vi kan strunta i. Genom att öppna en av XML-filerna i någon textredigerare ser man ganska snabbt att all text i dokumentet finns lagrad i “<p>“-taggar, och att rubriker dessutom är uppmärkta med “<ppr>“-taggar.
Som synes ovan är data i dokumentet lagrad under prydliga rubriker, så vi kan nog förutsätta att den data som lagrats som vanlig text efter en rubrikformaterad rad också tillhör den givna rubriken.
Det finns naturligtvis många metoder för att spara denna data. Scriptet nedan använder en strategi där alla <p>-noder först extraheras till en lista och en vektor sedan skapas med information om vilka listelement som är rubriker. Sedan går scriptet igenom samtliga listelement och tar samtliga element som kommer efter en rubrik och sparar dem som underelement till den rubriken.
Det vi nu har fått är alltså en hierarkisk lista med alla dataelement ur samtliga dokument vi har gått igenom. Vi har valt att ignorera stora mängder av den information som fanns lagrad i XML-filerna, som olika sorters metadata (t.ex. textformatering), men att extrahera även denna typ av data skulle inte kräva mer än 2-3 rader ytterligare kod.
Transformera till tabellform
Listan vi skapade ovan är såklart mycket användbar, men knappast läsbar för mänskliga ögon. Data är lagrade i en djupt hierarkisk lista ganska ostrukturerade. Så hur gör vi då av vår stökiga data för att kunna använda den till något vettigt?
Även om våra data ser röriga ut så har vi ju faktiskt själva definierat strukturen på dem i koden ovan. Strukturen på datalistan ser ut som följer:
Vår uppgift är alltså att gå igenom denna lista, element för element, och skapa enkla rader av data som vi kan lägga till en eller flera tabeller. Tabeller är generellt mycket lättare att förstå än hierarkiska listor, och liknar dessutom en traditionell relationsdatabas mycket mer.
Så let’s do it! Vi börjar med att skapa en tom data.frame
:
Sedan går vi igenom hela listan och binder datat till vår nyskapade tabell:
Vi har nu lagrat allt vårt data i tabellform. Det borde betyda att vi äntligen kan ta en titt på hur det ser ut:
Nu börjar det likna något! Det här ser ju nästan ut som en riktig databas. Och allt vi hittills gjort är egentligen bara att unzip:a alla Wordfiler och bygga ett script som parsar lite XML-data och stuvar om den till en tabell. Vem sade att det inte var enkelt att bygga en databas från Worddokument?!
Det är nog värt att understryka att vi redan nu är färdiga med att bygga en sökbar databas från våra Wordfiler. Databasen har till och med en nyckel, docnum
, vilket ju i detta fall indirekt även blir en personnyckel eftersom lagringsprincipen i detta fall är “en person - ett dokument”.
Men det betyder självklart inte att det är lätt att söka i databasen. All relevant data finns ju lagrad i text
-kolumnen, och den är ganska stökig. I nästa artikel ska vi därför titta på hur vi kan göra för att städa upp i stökig textdata och skapa en riktig relationsdatabas för personuppgifter.