library(tidyverse)
## ── Attaching packages ────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.0     ✔ purrr   0.2.5
## ✔ tibble  1.4.2     ✔ dplyr   0.7.8
## ✔ tidyr   0.8.2     ✔ stringr 1.3.1
## ✔ readr   1.1.1     ✔ forcats 0.3.0
## ── Conflicts ───────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(glue)
## 
## Attaching package: 'glue'
## The following object is masked from 'package:dplyr':
## 
##     collapse
households <- tibble(
  household_id = 1:2,
  x = c(3, 5),
  y = c(4, 4)
)

persons <- tibble(
  household_id = c(1, 1, 2, 2, 2),
  person_id = 1:5,
  sex = c("m", "f", "m", "f", "f"),
  age = c(43, 39, 55, 54, 17)
)

persons_xml <-
  persons %>%
  mutate(xml = glue('<person id="{person_id}" sex="{sex}" age="{age}"/>')) %>%
  group_by(household_id) %>%
  summarize(person_xml = glue_collapse(glue("  {xml}"), sep = "\n")) %>%
  ungroup()

persons_xml
## # A tibble: 2 x 2
##   household_id person_xml                                                 
##          <dbl> <S3: glue>                                                 
## 1            1 "  <person id=\"1\" sex=\"m\" age=\"43\"/>\n  <person id=\…
## 2            2 "  <person id=\"3\" sex=\"m\" age=\"55\"/>\n  <person id=\…
households_xml <-
  households %>%
  left_join(persons_xml, by = "household_id") %>%
  mutate(xml = glue('<household id="{household_id}" x="{x}" y="{y}">\n{person_xml}\n</household>')) %>%
  summarize(household_xml = glue_collapse(xml, sep = "\n"))

households_xml %>%
  pull()
## <household id="1" x="3" y="4">
##   <person id="1" sex="m" age="43"/>
##   <person id="2" sex="f" age="39"/>
## </household>
## <household id="2" x="5" y="4">
##   <person id="3" sex="m" age="55"/>
##   <person id="4" sex="f" age="54"/>
##   <person id="5" sex="f" age="17"/>
## </household>