Znovu budeme vytvářet frekvenční slovník pro Mobyho Dicka po jednotlivých kapitolách, tentokrát ale náš text bude v XML. Budeme nuceni “parsovat XML”, tj. extrahovat vybranou informaci z vybraných hierarchicky uspořádaných elementů XML. Naštěstí potřebujeme extrahovat jen holý text a nadpisy kapitol.
Náš dokument Moby Dick sbalený na úrovni kořenového tagu. Na začátku je ještě nějaké technické záhlaví, to se nepočítá.
Toto je pohled na soubor v textovém editoru. Všechny textové editory, které poznají XML, vám dovolí rozbalovat a sbalovat jednotlivé úrovně textu. Dokument je uspořádaný podle normy TEI (Text Encoding Initiative). Kdybychom se v ní vyznali, hned bychom věděli, že jednotlivé kapitoly jsou vnořeny do elementu <div1> v elementech head a <p>. Ale i když tu normu neznáme, asi na to snadno přijdeme, když si dokument prohlédneme v textovém editoru.
V záhlaví na obrázku najdeme v elementu xmlns tzv. namespace. To je odkaz na lidsky psaný dokument s definicemi jednotlivých tagů, tj. na co se má používat který. Na rozdíl od DTD nebo XML schématu nebo jakékoli jiné šablony, na kterou XML dokument může odkazovat, ten odkaz na namespaces neslouží k automatické validaci dokumentu. Projeví se jenom tak, že ke každému názvu elementu přibude při prohledávání pomyslná předpona, která bude obsahovat ten odkaz. Pokud budeme uzel například prohledávat jazykem XPATH, budeme tu předponu muset pokaždé použít před vlastním názvem uzlu a oddělit ji dvojtečkou. Věnujeme tomu dál malou odbočku.
Informace, kterou chceme extrahovat, vypadá v textu takto:
<p rend="fiction">And the women of New Bedford, they bloom like their own red roses. But roses only bloom in summer; whereas the fine carnation of their cheeks is perennial as sunlight in the seventh heavens. Elsewhere match that bloom of theirs, ye cannot, save in Salem, where they tell me the young girls breathe such musk, their sailor sweethearts smell them miles off shore, as though they were drawing nigh the odorous Moluccas instead of the Puritanic sands.</p>
</div1>
<div1 type="chapter" n="7" id="_76091">
<head>The Chapel</head>
<p rend="fiction">In the same New Bedford there stands a Whaleman's Chapel, and few are the moody fishermen, shortly bound for the Indian Ocean or Pacific, who fail to make a Sunday visit to the spot. I am sure that I did not.</p>
Tedy ještě jednou: uvnitř tagů <p> s atributem a hodnotou rend = "fiction" je text kapitoly a uvnitř tagu <head> je název kapitoly. Oba tagy jsou zanořené do tagu <div1> s atributem type = "chapter".
Budeme postupovat tak, že R necháme převést XML dokument na tzv. Document Object Model (DOM). Ta není specifická pro R, je to obecná reprezentace XML, ale v R jsou pro ni implementované funkce. Pozor, tento postup nebude fungovat na opravdu velká data, tam se musí použít tzv. Event-Driven parsing (více informací např.na http://www.inside-r.org/packages/cran/xml/docs/xmlEventParse). Rozdíl je, že ten DOM se musí celý načíst do R, zatímco při Event-Driven XML parsing se do něj sahá nějak přímo. Programování bez DOM by ovšem pro nás bylo mnohem složitější.
DOM vypadá jako stromový graf, v němž každý element je uzlem a jehož kořenem je tag, jenž obklopuje celý XML dokument. V našem případě je kořenem tag s názvem TEI.
Použijeme balíček XML.
if (require(XML) == FALSE) {
print("Installing the XML package...")
install.packages("XML", verbose = TRUE)
}
## Loading required package: XML
library(XML)
Načteme si soubor s Moby Dickem v XML. R ho teď bude vnímat jako ten DOM, který si interně uloží jako XMLInternalDocument nebo XMLAbstractDocument. Když si ho člověk prohlédne, vypadá to jako vektor s jedním obrovským znakovým elementem.
doc <- xmlTreeParse("data/XML1/melville1.xml", useInternalNodes = TRUE)
str(doc)
## Classes 'XMLInternalDocument', 'XMLAbstractDocument' <externalptr>
Parametr useInternalNodes nastavíme na TRUE proto, abychom mohli XML dokument načtený v proměnné doc prohledávat pomocí dotazového jazyka XPATH. Jazyk XPATH je zcela nezávislý na R a je třeba se ho naučit zvlášť. Pomocí XPATH vyextrahujeme zase objekt XML, který ale bude obsahovat jen uzel/uzly, které jsme zaměřili tím dotazem (a jejich vnořené uzly se všemi atributy). Bude to seznam, jehož každý element bude vypadat jako text s tagy.
Napřed si takhle vytáhneme názvy kapitol (bez čísel, protože ta jsou v dokumentu uložena jinde). Použijeme funkci getNodeSet(). Ta chce:
xmlTreeParse() s nastaveným parametrem useInternalNodes = TRUE.path)chapters.ns.l <- getNodeSet(doc, path = "/tei:TEI//tei:div1[@type='chapter']", namespaces = c(tei = "http://www.tei-c.org/ns/1.0"))
Dotaz se dá přeložit takto:
TEI s předponou tei. (Kořen se značí jedním lomítkem před. Předpona je od názvu uzlu oddělená dvojtečkou.)div1. Potomek je od předka oddělen dvěma lomítky. Jedním lomítkem se vždy odděluje jenom dítě, tj. přímý potomek.Pro další informace o hodnotě našeho parametru path je třeba konzultovat přímo nějaký zdroj o XPATH.
namespacesKde se vzala předpona tei? To je to namespace, které jsme zmínili na začátku. Museli jsme si ho nějak zkráceně pojmenovat v parametru namespaces funkce getNodeSet(). Tam patří pojmenovaný vektor. Je tam schválně nastaveno, že to může být celý vektor s namespaces, protože se často stává, že se parsuje dokument, který je slepený z dokumentů s různými namespaces. Použití namespaces nám umožňuje přesnější extrakci. Například když se v různých namespaces používá stejný tag, ale v každém má jiný význam, můžeme dotaz omezit jenom na jeden z významů. Jak si pojmenujeme namespaces v tom vektoru, je samozřejmě úplně jedno:
length(xchapters.ns.l <- getNodeSet(doc, "/pepa:TEI//pepa:div1[@type='chapter']", namespaces = c(pepa = "http://www.tei-c.org/ns/1.0")) )
## [1] 134
Když už se dokument jednou odkazuje na nějaké namespace, tak se nedá ignorovat. Nic bychom nenašli:
length(chapters.ns.l <- getNodeSet(doc, "/TEI//div1[@type='chapter']") )
## [1] 0
To bychom to namespace museli vypárat přímo z dokumentu.
To by šlo takhle:
Původní dokument:
<!ATTLIST figure url CDATA #IMPLIED >
]>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
Po úpravě (bude se jmenovat melville1 - Copy.xml)
<!ATTLIST figure url CDATA #IMPLIED >
]>
<TEI >
A uložíme si ho jako objekt bez_namespace.
bez_namespace <- xmlTreeParse("data/XML1/melville1 - Copy.xml", useInternalNodes = TRUE)
str(doc)
## Classes 'XMLInternalDocument', 'XMLAbstractDocument' <externalptr>
length(chapters.ns.l <- getNodeSet(bez_namespace, "/TEI//div1[@type='chapter']") )
## [1] 134
Vrátíme se zpátky k našemu správnému DOM s názvem doc. Vyextrahovali jsme z něj všechny elementy div1, které měly hodnotu atributu type = 'chapter'. Máme je v proměnné chapters.ns.l (ns jako nodeset), která obsahuje objekt třídy XMLNodeSet se strukturou seznamu.
class(chapters.ns.l)
## [1] "XMLNodeSet"
length(chapters.ns.l)
## [1] 134
Podíváme se, jak vypadá první kapitola v NodeSetu chapters.ns.l.
length(chapters.ns.l[[1]])
## [1] 1
str(chapters.ns.l[[1]])
## Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
Je tam jeden dlouhý řetězec, který začíná tagem <div1> a končí tagem <div1/>. Uvnitř jsou další tagy. Z každého elementu “div1”, který obsahuje odstavce kapitoly a název kapitoly, musíme extrahovat text odstavců a text názvu kapitoly, každé do své vlastní proměnné.
Naším cílem je dostat znakový vektor s názvy kapitol. Nejdřív si ale ukážeme, jak dostat text názvu kapitoly z jednoho uzlu XML v objektu chapters.ns.l. Pak si napíšeme funkci, kterou uplatníme na celý objekt chapters.ns.l.
V každém elementu objektu chapters.ns.l
chap.title.ns.l <- xmlElementsByTagName(chapters.ns.l[[1]], "head")
str(chap.title.ns.l)
## List of 1
## $ head:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
Dostali jsme seznam o délce 1. To znamená, že v první kapitole se našel jeden element s názvem head, tj. jeden nadpis. Jenže tento seznam ještě neobsahuje přímo ten text, ale celý element:
chap.title.ns.l
## $head
## <head>Loomings</head>
Text z XML elementu pohodlně vyloví funkce xmlValue(). Jen pozor na malý zádrhel: nemůžeme ji pustit přímo na chap.title.ns.l, protože to je seznam, v němž teprve jsou uzly. My jsme si právě ukázali, že je tam jen jeden.
class(chap.title.ns.l[[1]])
## [1] "XMLInternalElementNode" "XMLInternalNode"
## [3] "XMLAbstractNode"
A to už je zase XML uzel, a na ten nám už bude fungovat funkce na zpracování XML. Pustíme ji tedy dovnitř:
xmlValue(chap.title.ns.l[[1]])
## [1] "Loomings"
str(xmlValue(chap.title.ns.l[[1]]))
## chr "Loomings"
Tak. To je celá extrakce pro první kapitolu. Mohli bychom ji teď zabudovat do for-cyklu, který by ji pustil na jednu kapitolu po druhé. Místo toho si ji ale schováme do funkce a tu funkci aplikujeme na objekt chapters.ns.l. Kód bude kratší a provedení rychlejší. Funkci si pojmenujeme get_subnode_value, abychom s její pomocí mohli případně extrahovat i element s jiným názvem než “head”, ale když ho neuvedeme, bude se defaultně hledat “head”. Funkce bude chtít jeden nodeset a jeden název tagu (což defaultně je právě “head”).
get_subnode_value <- function(chapnode, tagnode = "head") {
#chci jeden nodeset a jeden název tagu.
# Když ho nedostanu, budu hledat "head".
chaptitle_node <- xmlElementsByTagName(chapnode, tagnode)
# Z uzlu nodesetu vytahám všechny uzly s tagem, který
# je hodnotou atributu "tagnode", defaultně tedy "head".
# Vyrobila jsem seznam.
chaptitle_text <- xmlValue(chaptitle_node[[1]])
# vytáhne z uzlu text
return(chaptitle_text)
}
Funkci otestujeme na první kapitole:
chapnode <- chapters.ns.l[[1]]
str(get_subnode_value(chapters.ns.l[[1]]))
## chr "Loomings"
Tak to je žádaný výsledek pro první kapitolu. Pro provedení této operace na všech kapitolách naráz musíme povolat nějakou funkci z rodiny apply. Náš objekt je seznam, takže připadá v úvahu lapply() nebo sapply(). Liší se tím, co vrátí. Ta první vrátí seznam, ta druhá se pokusí výslednou strukturu co nejvíc zjednodušit bez ztráty informace a tady může zjednodušit až na vektor. Vezmeme tedy tu druhou a tím si ušetříme krok unlist().
chaptitles.v <- sapply(chapters.ns.l, get_subnode_value)
head(chaptitles.v)
## [1] "Loomings" "The Carpet-Bag" "The Spouter-Inn" "The Counterpane"
## [5] "Breakfast" "The Street"
Tak. Přesně tohle jsme chtěli. Teď ještě extrahovat text z odstavců textu v každé kapitole.
Napřed si to zase vyzkoušíme na první kapitole a pak teprve přidáme nějaké apply na sériovou výrobu z celé knihy.
V chapters.ns.l[[1]] tedy máme uzel s tagem div1, který obsahuje uzly s tagem p. Budeme věřit, že všechny obsahují pouze text knihy, a nebudeme dotaz specifikovat dál na atributy toho uzlu, kde byl atribut a hodnota rend = 'fiction', což dává tušit, že p je značka pro odstavec jakéhokoli textu, třeba i metadat.
paras.ns.l <- xmlElementsByTagName(chapters.ns.l[[1]], "p")
length(paras.ns.l)
## [1] 14
#paras.ns.l
str(paras.ns.l)
## List of 14
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
## $ p:Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr>
Aha, odstavců je v každé kapitole víc než nadpisů a z každého odstavce musíme vypárat text zvlášť. Takže už pro jednu kapitolu to znamená, že funkci xmlValue() budeme muset strčit do nějakého apply()!
Nejvýhodnějším výstupem prvního kroku by byl vektor, v jehož každém elementu by byl text jednoho odstavce. 1. kapitola, s kterou pracujeme, má 14 odstavců.
chaptext.v <- sapply(paras.ns.l, xmlValue)
str(chaptext.v)
## Named chr [1:14] "Call me Ishmael. Some years ago- never mind how long precisely- having little or no money in my purse, and nothing particular t"| __truncated__ ...
## - attr(*, "names")= chr [1:14] "p" "p" "p" "p" ...
Tento vektor potřebujeme přetvořit ve vektor, v němž každým elementem bude jeden token. A taky všechen text převedeme na malá písmena.
chapunite.v <- paste(chaptext.v, collapse = " ")
chapwords.v <- unlist(strsplit(chapunite.v, split = "\\W"))
clean_chapwords.v <- chapwords.v[chapwords.v != ""]
words.lower.v <- tolower(clean_chapwords.v)
head(words.lower.v)
## [1] "call" "me" "ishmael" "some" "years" "ago"
Takový výsledek by se nám mohl někdy hodit sám o sobě, když si budeme chtít vypárat holý text z nějakého XML dokumentu podle TEI, tak si napíšeme funkci, která nám ho obstará. Jednotlivé kroky už jsme si prošli. Funkci nazveme get_plaintext() a bude chtít seznam s uzly, který se dostane například z funkce xmlElementsByTagName().
Pozor, tahle funkce bude fungovat jen na jedné kapitole! Zatím ji můžeme krmit pouze chapters.ns.l[[1]]!
get_plaintext <- function(nodelist, tagname){
paras.ns.l <- xmlElementsByTagName(nodelist, tagname)
print(paras.ns.l)
}
#get_plaintext(chapters.ns.l[[1]], "p")
Zatím naše funkce vytahá všechny uzly s tagem <p> a vytiskne je. To je dobrá kontrola. Pokračujeme.
get_plaintext <- function(nodelist, tagname){
paras.ns.l <- xmlElementsByTagName(nodelist, tagname)
# teď dostaneme vektor s textem odstavce pro každý odstavec kapitoly
chaptext.v <- sapply(paras.ns.l, xmlValue)
print(str(chaptext.v))
}
get_plaintext(chapters.ns.l[[1]], "p")
## Named chr [1:14] "Call me Ishmael. Some years ago- never mind how long precisely- having little or no money in my purse, and nothing particular t"| __truncated__ ...
## - attr(*, "names")= chr [1:14] "p" "p" "p" "p" ...
## NULL
Taky to vyšlo podle plánu.
get_plaintext <- function(nodelist, tagname) {
paras.ns.l <- xmlElementsByTagName(nodelist, tagname)
# teď dostaneme vektor s textem odstavce pro každý odstavec kapitoly
chaptext.v <- sapply(paras.ns.l, xmlValue)
# teď si odstavce pospojujeme do jednoho vektoru s jedním elementem
chapunite.v <- paste(chaptext.v, collapse = " ")
# teď ho roztrháme na tokeny a uděláme z něj vektor
# funkce strsplit nám jak známo vyrobila seznam
# a toho jsme se chtěli hned zbavit
chapwords.v <- unlist(strsplit(chapunite.v, split = "\\W"))
# taky nám tam zbyly prázdné znaky po interpunkci a mezerách
# zbavíme se jich
clean_chapwords.v <- chapwords.v[chapwords.v != ""]
# všechny tokeny převedeme na malá písmena
words.lower.v <- tolower(clean_chapwords.v)
}
head(get_plaintext(chapters.ns.l[[1]], "p"), 20)
## [1] "call" "me" "ishmael" "some" "years"
## [6] "ago" "never" "mind" "how" "long"
## [11] "precisely" "having" "little" "or" "no"
## [16] "money" "in" "my" "purse" "and"
Tak. Funkce get_plaintext() teď vezme kapitolu TEI dokumentu a vrátí z ní vektor tokenů převedených na malá písmena. Pokud ji budeme chtít použít na víc kapitol, musíme ji zavolat z lapply(). S ohledem na to, že chceme počítat frekvenční tabulky pro každou kapitolu, bude to výhodnější než sapply(). Ještě si tam dodáme názvy kapitol.
book_chap_plaintext.l <- lapply(chapters.ns.l, get_plaintext, "p")
book_chap_plaintext.l[[1]][1:20]
## [1] "call" "me" "ishmael" "some" "years"
## [6] "ago" "never" "mind" "how" "long"
## [11] "precisely" "having" "little" "or" "no"
## [16] "money" "in" "my" "purse" "and"
names(book_chap_plaintext.l) <- chaptitles.v
book_chap_plaintext.l[[1]][1:10]
## [1] "call" "me" "ishmael" "some" "years" "ago" "never"
## [8] "mind" "how" "long"
Teď máme seznam pojmenovaných elementů - názvy jsou názvy kapitol, obsahem jsou jednotlivé tokeny v každé kapitole zvlášť. Zbývá vyrobit ty frekvenční tabulky.
chapter.raws.l <- lapply(book_chap_plaintext.l, table)
chapter.raws.l[[1]][1:25]
##
## a abandon abominate about absent account act
## 69 1 1 7 1 2 1
## activity after afternoon again against ago ah
## 1 1 1 1 1 2 1
## ain air all alleys almost aloft always
## 1 2 23 1 3 2 2
## am american among amount
## 4 1 2 1
chapter.freqs.l <- lapply(book_chap_plaintext.l, function(x) {100 * table(x)/sum(table(x))})
chapter.freqs.l[[1]][1:25]
## x
## a abandon abominate about absent account
## 3.10391363 0.04498426 0.04498426 0.31488979 0.04498426 0.08996851
## act activity after afternoon again against
## 0.04498426 0.04498426 0.04498426 0.04498426 0.04498426 0.04498426
## ago ah ain air all alleys
## 0.08996851 0.04498426 0.04498426 0.08996851 1.03463788 0.04498426
## almost aloft always am american among
## 0.13495277 0.08996851 0.08996851 0.17993702 0.04498426 0.08996851
## amount
## 0.04498426
Celý kód by byl bez všech našich vysvětlivek a odboček mnohem elegantnější a kratší. Můžete si vyzkoušet, jak dlouho provedení trvá:
(start.time <- Sys.time())
## [1] "2016-05-11 16:25:05 CEST"
doc <- xmlTreeParse("data/XML1/melville1.xml", useInternalNodes = TRUE)
chapters.ns.l <- getNodeSet(doc, path = "/tei:TEI//tei:div1[@type='chapter']", namespaces = c(tei = "http://www.tei-c.org/ns/1.0"))
get_subnode_value <- function(chapnode, tagnode = "head") {
#chci jeden nodeset a jeden název tagu.
# Když ho nedostanu, budu hledat "head".
chaptitle_node <- xmlElementsByTagName(chapnode, tagnode)
# Z uzlu nodesetu vytahám všechny uzly s tagem, který
# je hodnotou atributu "tagnode", defaultně tedy "head".
# Vyrobila jsem seznam.
chaptitle_text <- xmlValue(chaptitle_node[[1]])
# vytáhne z uzlu text
return(chaptitle_text)
}
get_plaintext <- function(nodelist, tagname) {
paras.ns.l <- xmlElementsByTagName(nodelist, tagname)
# teď dostaneme vektor s textem odstavce pro každý odstavec kapitoly
chaptext.v <- sapply(paras.ns.l, xmlValue)
# teď si odstavce pospojujeme do jednoho vektoru s jedním elementem
chapunite.v <- paste(chaptext.v, collapse = " ")
# teď ho roztrháme na tokeny a uděláme z něj vektor
# funkce strsplit nám jak známo vyrobila seznam
# a toho jsme se chtěli hned zbavit
chapwords.v <- unlist(strsplit(chapunite.v, split = "\\W"))
# taky nám tam zbyly prázdné znaky po interpunkci a mezerách
# zbavíme se jich
clean_chapwords.v <- chapwords.v[chapwords.v != ""]
# všechny tokeny převedeme na malá písmena
words.lower.v <- tolower(clean_chapwords.v)
}
chaptitles.v <- sapply(chapters.ns.l, get_subnode_value)
book_chap_plaintext.l <- lapply(chapters.ns.l, get_plaintext, "p")
book_chap_plaintext.l[[1]][1:20]
## [1] "call" "me" "ishmael" "some" "years"
## [6] "ago" "never" "mind" "how" "long"
## [11] "precisely" "having" "little" "or" "no"
## [16] "money" "in" "my" "purse" "and"
names(book_chap_plaintext.l) <- chaptitles.v
chapter.raws.l <- lapply(book_chap_plaintext.l, table)
chapter.raws.l[[1]][1:25]
##
## a abandon abominate about absent account act
## 69 1 1 7 1 2 1
## activity after afternoon again against ago ah
## 1 1 1 1 1 2 1
## ain air all alleys almost aloft always
## 1 2 23 1 3 2 2
## am american among amount
## 4 1 2 1
chapter.freqs.l <- lapply(book_chap_plaintext.l, function(x) {100 * table(x)/sum(table(x))})
chapter.freqs.l[[1]][1:25]
## x
## a abandon abominate about absent account
## 3.10391363 0.04498426 0.04498426 0.31488979 0.04498426 0.08996851
## act activity after afternoon again against
## 0.04498426 0.04498426 0.04498426 0.04498426 0.04498426 0.04498426
## ago ah ain air all alleys
## 0.08996851 0.04498426 0.04498426 0.08996851 1.03463788 0.04498426
## almost aloft always am american among
## 0.13495277 0.08996851 0.08996851 0.17993702 0.04498426 0.08996851
## amount
## 0.04498426
(end.time <- Sys.time())
## [1] "2016-05-11 16:25:07 CEST"
duration <- end.time - start.time
cat("The script was executed in ", duration, "seconds.")
## The script was executed in 2.27713 seconds.
xpathSApply()První krok: uložíme si elementy div1 do proměnné jako XML, použijeme funkci saveXML(). Kdybychom ji nepoužili, dostali bychom znakový vektor, který už by nešel dál prohledávat XML funkcemi.
kapitoly <- xpathSApply(doc, path = "//tei:div1", namespaces = c(tei = "http://www.tei-c.org/ns/1.0"), saveXML )
Druhý krok: kuchařka ze StackOverflow
vnitrky_kapitol <- lapply(kapitoly, function(book) unlist(xpathSApply(xmlInternalTreeParse(book), "//p/text()", saveXML)))
print(vnitrky_kapitol[[137]])
## [1] "\"And i only am escaped alone to tell thee\""
## [2] "Job."
## [3] "The drama's done. Why then here does any one step forth?- Because one did survive the wreck."
## [4] "It so chanced, that after the Parsee's disappearance, I was he whom the Fates ordained to take the place of Ahab's bowsman, when that bowsman assumed the vacant post; the same, who, when on the last day the three men were tossed from out of the rocking boat, was dropped astern. So, floating on the margin of the ensuing scene, and in full sight of it, when the halfspent suction of the sunk ship reached me, I was then, but slowly, drawn towards the closing vortex. When I reached it, it had subsided to a creamy pool. Round and round, then, and ever contracting towards the button-like black bubble at the axis of that slowly wheeling circle, like another Ixion I did revolve. Till, gaining that vital centre, the black bubble upward burst; and now, liberated by reason of its cunning spring, and, owing to its great buoyancy, rising with great force, the coffin life-buoy shot lengthwise from the sea, fell over, and floated by my side. Buoyed up by that coffin, for almost one whole day and night, I floated on a soft and dirgelike main. The unharming sharks, they glided by as if with padlocks on their mouths; the savage sea-hawks sailed with sheathed beaks. On the second day, a sail drew near, nearer, and picked me up at last. It was the devious-cruising Rachel, that in her retracing search after her missing children, only found another orphan."
## [5] "FINIS"
Třetí krok: tohle už známe, tady už jsme se XML zbavili a jde nám jen o tokenizaci a frekvenční tabulku.
odstavce_dohromady <- lapply(vnitrky_kapitol, paste, collapse = " ")
odstavce_dohromady[[137]]
## [1] "\"And i only am escaped alone to tell thee\" Job. The drama's done. Why then here does any one step forth?- Because one did survive the wreck. It so chanced, that after the Parsee's disappearance, I was he whom the Fates ordained to take the place of Ahab's bowsman, when that bowsman assumed the vacant post; the same, who, when on the last day the three men were tossed from out of the rocking boat, was dropped astern. So, floating on the margin of the ensuing scene, and in full sight of it, when the halfspent suction of the sunk ship reached me, I was then, but slowly, drawn towards the closing vortex. When I reached it, it had subsided to a creamy pool. Round and round, then, and ever contracting towards the button-like black bubble at the axis of that slowly wheeling circle, like another Ixion I did revolve. Till, gaining that vital centre, the black bubble upward burst; and now, liberated by reason of its cunning spring, and, owing to its great buoyancy, rising with great force, the coffin life-buoy shot lengthwise from the sea, fell over, and floated by my side. Buoyed up by that coffin, for almost one whole day and night, I floated on a soft and dirgelike main. The unharming sharks, they glided by as if with padlocks on their mouths; the savage sea-hawks sailed with sheathed beaks. On the second day, a sail drew near, nearer, and picked me up at last. It was the devious-cruising Rachel, that in her retracing search after her missing children, only found another orphan. FINIS"
words.lower.l <- lapply(odstavce_dohromady, strsplit, split = "\\W")
words.lower.2.l <- lapply(words.lower.l, unlist)
words.lower.3.l <- lapply(words.lower.2.l, tolower)
words.lower.4.l <- lapply(words.lower.3.l, function(x) {x[x != ""]} )
words.lower.4.l[[137]][1:3]
## [1] "and" "i" "only"
XPATH.chapter.raws.l <- lapply(words.lower.4.l, table)
XPATH.chapter.raws.l[[137]][1:20]
##
## a after ahab almost alone am and another any
## 3 2 1 1 1 1 10 2 1
## as assumed astern at axis beaks because black boat
## 1 1 1 2 1 1 1 2 1
## bowsman bubble
## 2 2