Generate a data frame from many xml files

I came up with one way to use tidyverse. I changed different things. If you just want to replace lapply, use purrr::map. map_dfr does the bind_rows inside. Take care: Base R is more permissive and tidyverse less. For example, it seems you have some identical columns names in your parent and kids table. not good!

library(xml2)
library(tidyverse)

# Make a temporary file (tf) and a temporary folder (tdir)
tf <- tempfile(tmpdir = tdir <- tempdir())

## Download the zip file 
download.file("https://data.val.se/val/val2014/valnatt/valnatt.zip", tf)

## Unzip it in the temp folder
xml_files <- unzip(tf, exdir = tdir)

## Parse the 4th file in the folder (first file with municipal data reg. municipal election)
t <- read_xml(xml_files[4])

df <- xml_find_all(t, "//VALDISTRIKT") %>% 
  map_dfr(~ {
    # extract the attributes from the parent tag as a data.frame
    parent <- xml_attrs(.x) %>% enframe() %>% spread(name, value)
    # make a data.frame out of the attributes of the kids
    kids <- xml_children(.x) %>% map_dfr(~ as.list(xml_attrs(.x)))
    # combine them (bind_cols does not repeat parent rows)
    cbind.data.frame(parent, kids) %>% set_tidy_names() %>% as_tibble() 
  })
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
#> New names:
#> RÖSTER -> RÖSTER..4
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..5
#> RÖSTER -> RÖSTER..8
#> RÖSTER_FGVAL -> RÖSTER_FGVAL..9
df
#> # A tibble: 330 x 17
#>    KOD    MODNR   NAMN     `RÖSTER..4` `RÖSTER_FGVAL.~ TID_RAPPORT PARTI
#>    <chr>  <chr>   <chr>    <chr>        <chr>            <chr>       <chr>
#>  1 01140~ 117118~ Apoteks~ 926          845              2014091422~ M    
#>  2 01140~ 117118~ Apoteks~ 926          845              2014091422~ C    
#>  3 01140~ 117118~ Apoteks~ 926          845              2014091422~ FP   
#>  4 01140~ 117118~ Apoteks~ 926          845              2014091422~ KD   
#>  5 01140~ 117118~ Apoteks~ 926          845              2014091422~ S    
#>  6 01140~ 117118~ Apoteks~ 926          845              2014091422~ V    
#>  7 01140~ 117118~ Apoteks~ 926          845              2014091422~ MP   
#>  8 01140~ 117118~ Apoteks~ 926          845              2014091422~ SD   
#>  9 01140~ 117118~ Apoteks~ 926          845              2014091422~ FI   
#> 10 01140~ 117118~ Apoteks~ 926          845              2014091422~ PP   
#> # ... with 320 more rows, and 10 more variables: `RÖSTER..8` <chr>,
#> #   `RÖSTER_FGVAL..9` <chr>, PROCENT <chr>, PROCENT_FGVAL <chr>,
#> #   `PROCENT_ÄNDRING` <chr>, TEXT <chr>, `RÖSTBERÄTTIGADE` <chr>,
#> #   `RÖSTBERÄTTIGADE_KLARA_VALDISTRIKT_FGVAL` <chr>,
#> #   `SUMMA_RÖSTER` <chr>, `SUMMA_RÖSTER_FGVAL` <chr>

Created on 2018-06-28 by the reprex package (v0.2.0).

About step 2

You can use fs :package: to manipulate files and folder and stringr to detect your string pattern. Get the list of the files in your directory and keep only those name with valnatt_ then 4 digits then the letter K, using a regex:"valnatt_\d{4}K.xml$`

library(xml2)
library(tidyverse)
#> Warning: le package 'tibble' a été compilé avec la version R 3.4.4
#> Warning: le package 'tidyr' a été compilé avec la version R 3.4.4
#> Warning: le package 'purrr' a été compilé avec la version R 3.4.4
#> Warning: le package 'dplyr' a été compilé avec la version R 3.4.4
#> Warning: le package 'stringr' a été compilé avec la version R 3.4.4

# Make a temporary file (tf) and a temporary folder (tdir)
tf <- tempfile(tmpdir = tdir <- tempdir())

## Download the zip file 
download.file("https://data.val.se/val/val2014/valnatt/valnatt.zip", tf)

## Unzip it in the temp folder
xml_files <- unzip(tf, exdir = tdir)

files_to_import <- fs::dir_ls(tdir) %>%
  str_subset(pattern = "valnatt_\\d{4}K.xml$")
head(files_to_import)
#> [1] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0114K.xml"
#> [2] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0115K.xml"
#> [3] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0117K.xml"
#> [4] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0120K.xml"
#> [5] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0123K.xml"
#> [6] "C:/Users/chris/AppData/Local/Temp/Rtmpo5NbU2/valnatt_0125K.xml"
length(files_to_import)
#> [1] 290

Created on 2018-06-28 by the reprex package (v0.2.0).

You can then use purrr::map and friends on this vector of use fs::dir_map() but you'll less control on the result.

Hopes it helps.

3 Likes