Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: XML-filen parsiminen regular expressioneilla

Rode [06.09.2019 09:13:22]

#

Olisi tämmöinen kinkkisempi ongelma liittyen XML-dokumentin parsintaan regular expression-lauseilla Linux-palvelimella. Tiedän, että Linuxilla olisi esim. xmllint ja siinä xpath, mutta tällä ko. palvelimella ei ole tuota xpathia. Lisäksi järjestelmä, joka lukee XML-dokkareita, käyttää vain regexp-lauseita, joten niillä on nyt pärjättävä.

Ongelma on siis seuraava. Alla on malli XML-dokumentista, josta pitäisi saada luettua esim. tuon ACCESSORIES Entryn Name-kentän sisältö regexpejä käyttäen.

<?xml version="1.0" encoding="UTF-8"?>
<Records>
	<Record>
		<Entry>
			<Type>TOOLS</Type>
			<Code>3015</Code>
			<DepotCode>T-2345-1</DepotCode>
			<Name>Product name`3015</Name>
		</Entry>
		<Entry>
			<Type>ACCESSORIES</Type>
			<Code>1002</Code>
			<DepotCode>A-3345-1</DepotCode>
			<Name>Product name 1002</Name>
		</Entry>
		<Entry>
			<Type>MACHINES</Type>
			<Code>3321</Code>
			<DepotCode>M-2123-1</DepotCode>
			<Name>Product name 3321</Name>
		</Entry>
	</Record>
</Records>

Olen testannut XML-dokumentin parsintaa komentorivillä seuraavasti:

cat record.xml | tr -d '\n\r' |grep -i -E -o '(<Type>ACCESSORIES</Type>).*(</Name></Entry>)' |sed -n -e 's/.*<Name>\(.*\)<\/Name>.*/\1/p'

Eli aluksi rivitän koko dokumentin tuolla tr -d komennolla. Sen jälkeen olisi tarkoitus grepata tuo ACCESSORIES Entry ja lukea se lopputagiin saakka. Sitten sediä käyttäen haluaisin "riisua" tuon Name-kentän, jotta jäljelle jää vain sen sisältö.

Tuo nyt ei kuitenkaan ihan toimi, sillä tuloksena on vain viimeisimmän Entryn, eli MACHINES-entryn Name-kentän sisältö. Miten saisin poimittua/tulostettua pelkästään tuon haluamani entryn Name-kentän sisällön?

Grez [06.09.2019 10:25:30]

#

Jos käytössä on GNU grep, niin koko homma onnistuisi seuraavasti:

grep -izoP '(?s)<Type>ACCESSORIES</Type>.*?<Name>\K[^<]*' record.xml

Tulostaa:

Product name 1002

Eli:
-i ignore-case (omasta esimerkistäsi, eli ilmeisesti koet tarvitsevasi tätä)
-z käsitellään rivivaihdot nullina (tämä ja (?s) yhdessä mahdollistavat ettei tr -komentoa tarvita)
-o tulostetaan vain osuma
-P käytetään Perl tyylisiä regexejä

(?s) aktiovoi PCRE_DOTALL:in, jolloin . vastaa mitä vaan merkkiä ja rivivaihtoa
<Type>ACCESSORIES</Type> Pitää täsmätä
.*? ottaa mahdollisimman vähän merkkejä
<Name> Pitää täsmätä
\K[^<]* "Osuma" on tästä eteenpäin [^<]* eli kaikki < -merkkiin asti.

Rode [06.09.2019 10:49:21]

#

Kiitos vastauksesta! Valitettavasti tuo ei näytä toimivan ko. Linux-palvelimella, sillä saan oheisen virheilmoituksen. Eli ilmeisesti GNU grep:ä ei ole käytössä palvelimella.

grep: The -P and -z options cannot be combined

Kokeilin ajaa komentoa myös ilman noita -P ja -z optioita, eli lisäsin tr -d komennon ennen grepiä, mutta tämä ei tulostanut mitään.

Grez [06.09.2019 10:54:49]

#

Joo, Linuxissa (Debian) itsekin ajoin. Toi -P ja -z optioiden yhtäaikaisuuden mahdottomuus kielii ehkä siitä että -P tarkoittaa tuossa sinun versiossasi jotain muuta.

Itsellä grepin man-sivusta relevantti kohta:

man grep kirjoitti:

-P, --perl-regexp
Interpret PATTERN as a Perl regular expression (PCRE, see below). This is highly experimental and grep -P may warn of unimplemented features.

Yleisesti ottaen menee vähän tylsemmäksi tuon tekeminen tuolla versiolla grepistä, jossa ei ole PCRE tukea. Edes lazy modifierit ei toimi :( Muutenhan olis helppo tuupata tuohon omaan riviisi <Type>ACCESSORIES</Type>.*?<Name>.*?</Name>

Lisäys:

Tässä nyt vielä toimiva versio noilla käyttämilläsi työkaluilla.

Koska lazy modifieria ei ollut käytössä, niin annoin tuon ennen <Name> tulevan osuuden sisältää vain:
- [^<] muita merkkejä kuin tagin aloittavia
- <[^/] tagin alkuja, jotka ei ole lopputageja tai
- </[^E] lopputageja, jotka alkaa muulla kuin E.

Eli näin löytyvä (ahne) pätkä ei voi sisältää </Entry> :ä.

cat record.xml | tr -d '\n\r' |grep -iEo '<Type>ACCESSORIES</Type>([^<]|<[^/]|</[^E])*<Name>[^<]*</Name>' | sed -ne 's/.*<Name>\(.*\)<\/Name>.*/\1/p'

Rode [06.09.2019 15:56:06]

#

Kokeilin tuota komentoa/riviä ja se toimii. Kiitokset avusta!

Grez kirjoitti:

cat record.xml | tr -d '\n\r' |grep -iEo '<Type>ACCESSORIES</Type>([^<]|<[^/]|</[^E])*<Name>[^<]*</Name>' | sed -ne 's/.*<Name>\(.*\)<\/Name>.*/\1/p'

Nyt se tosiaan hakee vain tuon ACCESSORIES-entryn ja palauttaa sen <Name>-elementin sisällön. Kyllähän näitä varten olisi kätevämpiäkin työkaluja, mutta, vanha systeemi ja vanhat menetelmät.. :/

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta