Извлечение данных из файлов XML с запутанными путями / именами узлов

Я пытаюсь извлечь значения из институциональных файлов XML с помощью R. Вот пример такого файла:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<missions xmlns="http://sensored">
    <mission missiontype="2" year="2012" platform="4135" missionnumber="1" missiontypename="Referanseflåten-Hav" callsignal="LJBD" platformname="Nybo">
        <startdate>30/01/2012</startdate>
        <stopdate>28/11/2012</stopdate>
        <purpose></purpose>
        <fishstation serialno="86001">
            <nation>58</nation>
            <platform>4135</platform>
            <station>1</station>
            <startdate>30/01/2012</startdate>
            <starttime>18:56:00</starttime>
            <stopdate>30/01/2012</stopdate>
            <stoptime>23:19:00</stoptime>
            <latitudestart>65.13833</latitudestart>
            <longitudestart>-11.04833</longitudestart>
            <system>2</system>
            <area>59</area>
            <location>07</location>
            <bottomdepthstart>653.0</bottomdepthstart>
            <bottomdepthstop>653.0</bottomdepthstop>
            <fishingdepthmax>85.0</fishingdepthmax>
            <fishingdepthmin>40.0</fishingdepthmin>
            <gear>3711</gear>
            <gearcount>1</gearcount>
            <gearcondition>4</gearcondition>
            <trawlquality>7</trawlquality>
            <dataquality>2</dataquality>
            <catchsample species="161722.G03" samplenumber="1" noname="sild'G03" aphia="126417">
                <sampletype>20</sampletype>
                <conservation>1</conservation>
                <producttype>1</producttype>
                <weight>10.0</weight>
                <count>46</count>
                <sampleproducttype>1</sampleproducttype>
                <lengthmeasurement>E</lengthmeasurement>
                <lengthsampleweight>0.863</lengthsampleweight>
                <lengthsamplecount>4</lengthsamplecount>
                <specimensamplecount>4</specimensamplecount>
                <individual specimenno="1">
                    <producttype>1</producttype>
                    <weight>0.222</weight>
                    <lengthunit>2</lengthunit>
                    <length>0.28</length>
                </individual>
                <individual specimenno="2">
                    <producttype>1</producttype>
                    <weight>0.127</weight>
                    <lengthunit>2</lengthunit>
                    <length>0.245</length>
                </individual>
                <individual specimenno="3">
                    <producttype>1</producttype>
                    <weight>0.172</weight>
                    <lengthunit>2</lengthunit>
                    <length>0.26</length>
                </individual>
                <individual specimenno="4">
                    <producttype>1</producttype>
                    <weight>0.342</weight>
                    <lengthunit>2</lengthunit>
                    <length>0.325</length>
                </individual>
            </catchsample>
        </fishstation>
    </mission>
</missions>

Мне удалось разобрать файл без проблем. Хотя имена отображаются в проанализированном списке правильно, я не могу правильно их проанализировать для использования языка XPath:

library(xml2)
library(xmltools)
doc <- xml2::read_xml("test.xml")
doc %>% xmltools::xml_view_trees()
#└── mission
#  ├── startdate
#  ├── stopdate
#  ├── purpose
#  └── fishstation
#    ├── nation
#    ├── platform
#    ├── station
#    ├── startdate
#    ├── starttime
#    ├── stopdate
#    ├── stoptime
#    ├── latitudestart
#    ├── longitudestart
#    ├── system
#    ├── area
#    ├── location
#    ├── bottomdepthstart
#    ├── bottomdepthstop
#    ├── fishingdepthmax
#    ├── fishingdepthmin
#    ├── gear
#    ├── gearcount
#    ├── gearcondition
#    ├── trawlquality
#    ├── dataquality
#    └── catchsample
#      ├── sampletype
#      ├── conservation
#      ├── producttype
#      ├── weight
#      ├── count
#      ├── sampleproducttype
#      ├── lengthmeasurement
#      ├── lengthsampleweight
#      ├── lengthsamplecount
#      ├── specimensamplecount
#      ├── individual
#        ├── producttype
#        ├── weight
#        ├── lengthunit
#        └── length
#      ├── individual
#        ├── producttype
#        ├── weight
#        ├── lengthunit
#        └── length
#      ├── individual
#        ├── producttype
#        ├── weight
#        ├── lengthunit
#        └── length
#      └── individual
#        ├── producttype
#        ├── weight
#        ├── lengthunit
#        └── length
doc %>% xmltools::xml_get_paths()
#[[1]]
# [1] "/*/*"         "/*/*/*"       "/*/*/*"       "/*/*/*"       #"/*/*/*"       "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     #"/*/*/*/*"     "/*/*/*/*"    
# [12] "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     #"/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     #"/*/*/*/*"     "/*/*/*/*"    
# [23] "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     "/*/*/*/*"     #"/*/*/*/*"     "/*/*/*/*/*"   "/*/*/*/*/*"   "/*/*/*/*/*"   "/*/*/*/*/*"   #"/*/*/*/*/*"   "/*/*/*/*/*"  
# [34] "/*/*/*/*/*"   "/*/*/*/*/*"   "/*/*/*/*/*"   "/*/*/*/*/*"   #"/*/*/*/*/*"   "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*/*" #"/*/*/*/*/*"   "/*/*/*/*/*/*"
# [45] "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*"   #"/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*/*" "/*/*/*/*/*"   #"/*/*/*/*/*/*" "/*/*/*/*/*/*"
# [56] "/*/*/*/*/*/*" "/*/*/*/*/*/*"

Скажем, я хочу извлечь долготу и широту для каждого fishstation вместе с кодом вида и индивидуальным весом для каждой выбранной рыбы. Я могу сделать это, используя не очень эффективный способ памяти, преобразовав XML-документ в список и используя стандартный код R:

library(XML)

doc <- xmlInternalTreeParse("test.xml")

getNodeSet(doc, "//fishstation") # Does not work
getNodeSet(doc, "/*/*/*/*") # Works

tmp <- xmlToList(doc)[[1]]
tmp <- tmp[names(tmp) == "fishstation"]

lapply(tmp, function(k) {

  sp <- k$catchsample$.attrs
  inds <- k$catchsample
  inds <- inds[names(inds) == "individual"]

  data.frame(lon = k$longitudestart, lat = k$latitudestart, 
sp = unname(sp[names(sp) == "species"]), 
weight = unname(sapply(inds, function(j) j$weight)))
})

# $fishstation
#        lon             lat         sp   weight
# 1 -11.04833        65.13833 161722.G03  0.222
# 2 -11.04833        65.13833 161722.G03  0.127
# 3 -11.04833        65.13833 161722.G03  0.172
# 4 -11.04833        65.13833 161722.G03  0.342

Однако было бы чрезвычайно удобно использовать для этого язык XPath, поскольку такой подход значительно ускорит процесс (мои файлы могут иметь размер несколько гигабайт). Пока мне не удалось настроить процедуру извлечения таким образом, чтобы использование языка XPath работало для этих файлов. Я прекрасно понимаю, что это могло произойти из-за фундаментальной ошибки пользователя. Раньше я не работал с XML-файлами.

1) Как мне прочитать файл примера в R, чтобы имена узлов сохранились?

2) Как мне извлечь значения из приведенного выше примера более эффективным с точки зрения памяти способом?


person Mikko    schedule 14.02.2019    source источник


Ответы (1)


Что делает этот файл сложным, так это пространство имен в тексте xml. В пакете xml2 есть функция для вырезания пространства имен, что упрощает процесс.
Как только это будет сделано, нужно найти людей, а затем продвинуться вверх по дереву, чтобы найти родительские узлы, а затем извлечь запрошенную информацию.

library(xml2)
#read the file in as xml
page<-read_xml("fish.xml")
#strip the name space out
page<-xml_ns_strip(page)

#find all of the individuals
individuals<-xml_find_all(page, ".//individual")
#find the parent catchsample nodes for the individuals
catchnodes<-xml_find_first( individuals, ".//parent::catchsample")
#find the parents for the catchsample node the fishstation
fishstations<-xml_find_first( catchnodes, ".//parent::fishstation ")

#individuals, catchnodes and fishstations should all have the same length

#extract the desired information from each set of nodes
weights<-xml_text(xml_find_first(individuals, ".//weight"))
lat<-xml_text(xml_find_first(fishstations, ".//latitudestart"))
long<-xml_text(xml_find_first(fishstations, ".//longitudestart"))
species<-xml_attr(catchnodes, "species")  #information is an attribute

#package together
data.frame(lat, long, species, weights)
#        lat      long    species weights
# 1 65.13833 -11.04833 161722.G03   0.222
# 2 65.13833 -11.04833 161722.G03   0.127
# 3 65.13833 -11.04833 161722.G03   0.172
# 4 65.13833 -11.04833 161722.G03   0.342

Вот тот же код, но без удаления пространства имен:

page<-read_xml("fish.xml")
ns <- xml_ns(page)

#find all of the individuals
individuals<-xml_find_all(page, ".//d1:individual", ns)

catchnodes<-xml_find_first(individuals, ".//parent::d1:catchsample", ns)
fishstations<-xml_find_first(catchnodes, ".//parent::d1:fishstation", ns)

weights<-xml_text(xml_find_first(individuals, ".//d1:weight", ns))
lat<-xml_text(xml_find_first(fishstations, ".//d1:latitudestart", ns))
long<-xml_text(xml_find_first(fishstations, ".//d1:longitudestart", ns))
species<-xml_attr(catchnodes, "species") 
person Dave2e    schedule 14.02.2019
comment
Это решение имеет потенциал, но функция xml_ns_strip примерно в 4 раза медленнее, чем xmlToList (синхронизация системы с использованием фактических данных). Есть идеи, как обойти это предостережение? - person Mikko; 16.02.2019
comment
@Mikko, я использую xml_ns_strip для упрощения работы с документом. Вы можете избежать использования этой функции и добавить пространство имен к вызовам функции xml_find_ * с параметром ns. Смотрите редактирование выше, как это выглядит с оставленным пространством имен. - person Dave2e; 16.02.2019