ubuntuusers.de

Python + Regular Expressions

Status: Ungelöst | Ubuntu-Version: Nicht spezifiziert
Antworten |

Evolis2k2

Anmeldungsdatum:
5. August 2007

Beiträge: 69

N'abend 😢

ich bin hier gerade etwas am verzweifeln:

Ich Programmiere gerade mit Django eine Webseite. Der Clou an der Sache ist ein Stichwortverzeichniss, welches in den Artikeln die jeweiligen Stichwörter automatisch durch einen Link zur "Stichwortbeschreibung" ersetzen soll.

string = '<a href="/test/foo123.html">foo bild</a>foo fooo <img src="/foo.gif" />'

In diesem String soll jetzt zum beispiel nur das eine foo durch einen Link ersetzt werden, alle anderen logischerweise nicht.

Ich hänge nun schon mehrere Stunden an diesem Problem, und komme nicht wirklich weiter.

Ich hab jetzt erstmal probiert, nur Link-Tags auszuschließen...

1
2
string = '<a href="/test/foo123.html">foo</a>foo fooo <img src="/foo.gif"/>'
string_neu = re.sub('(?!<a href[A-Za-z0-9[:punct:]]*)foo(?![A-Za-z0-9[:punct:]]*</a>)', 'bar', string)

Ausgabe:

1
<a href="/test/bar123.html">bar</a>bar baro <img src="/bar.gif"/>

😢

Wär von euch jemand bereit, mich auf den richtigen Weg zu leiten? Diese Regex-Geschichte sind für mich das totale Grauen & die Dokus machen mich nicht wirklich schlauer...

Danke schonmal,

Evo

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Versuche nicht, XML mit regulären Ausdrücken zu parsen, das ist kompliziert und fehleranfällig. Es gibt mit XPath eine einfache Möglichkeit, Knoten in einem XPath-Dokument auszuwählen. Beispielsweise kannst Du mit dem XPath-Ausdruck //text()[not(ancestor::a)] alle Textknoten im Dokument auswählen, die nicht in einem a-Element enthalten sind. Darin ersetzt Du dann einfach die jeweiligen Stichworte durch Links und fertig.

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

danke für den Tipp, bin schon etwas weiter damit gekommen

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Mit etree.parse und der StringIO-Klasse kannst Du einen String als XML parsen.

Was ich mit meinen eher bescheidenen Python-Kenntnissen aber noch nicht herausgefunden habe, ist, wie man mit Hilfe der Liste der Textknoten, die der XPath-Ausdruck beschreibt, das Originaldokument verändern kann. Aber hier laufen ein paar Leute herum, die die eine oder andere Sache von Python verstehen, daher denke ich, dass sich das schon klären wird.

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

genau das ist nämlich jetzt mein problem... ich hab ne schöne liste mit den zu ändernden objekten, aber kann sie nicht einfach wieder im string richtig positionieren.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4658

Wohnort: Berlin

@Hello World: Mit etree.fromstring() kann man sich StringIO sparen.

@Evolis2k2: Element-Exemplare haben die Attribute text und tail. text enthält den Text innerhalb des Tags bis zum nächsten Tag, falls da eines verschachtelt enthalten ist, und tail enthält den Text nach einem Tag. Die originale ElementTree-API ist für Dein Vorhaben aber wohl nicht so gut geeignet, weil man für den tail-Text eventuell neue Knoten in das Elternelement einfügen muss und an das kommt man mit der originalen API nicht heran. Das müsste mit lxml.etree gehen.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2128

Wohnort: Gelsenkirchen

Mit einem anderen Parser - nämlich BeautifulSoup - würde ich es so lösen:

1
2
3
4
5
6
7
8
9
>>> from BeautifulSoup import BeautifulSoup
>>> string = '<a href="/test/foo123.html">foo</a>foo fooo <img src="/foo.gif"/>'
>>> soup = BeautifulSoup(string)
>>> print soup(text=True)
[u'foo', u'foo fooo ']
>>> link = '<a href="/murks/bla.html">foo</a> fooo '
>>> soup(text=True)[1].replaceWith(link)
>>> print soup
<a href="/test/foo123.html">foo</a><a href="/murks/bla.html">foo</a> fooo <img src="/foo.gif" />

Sicherlich nicht optimal. Aber ich wüsste nicht, dass BeautifulSoup das splitten eines Strings unterstützt.

//edit: Man könnte sich aber natürlich ne Funktion wie diese schreiben:

1
2
3
4
5
6
7
8
9
def replace_splitted(soup, n, repl):
    '''replace_splitted(soup, n, repl)

    Split the soup using space chars as seperator, replace the nth element and 
    return the new soup.
    '''
    split = soup.split(' ')
    split[n] = repl
    return soup.replaceWith(' '.join(split))

Anwendung:

1
2
3
4
5
6
7
8
>>> import souptools
>>> from BeautifulSoup import BeautifulSoup
>>> string = '<a href="/test/foo123.html">foo</a>foo fooo <img src="/foo.gif"/>'
>>> soup = BeautifulSoup(string)
>>> link = '<a href="/murks/bla.html">foo</a>'
>>> souptools.replace_splitted(soup(text=True)[1], 0, link)
>>> soup
<a href="/test/foo123.html">foo</a><a href="/murks/bla.html">foo</a> fooo <img src="/foo.gif" />

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

Ich habe jetzt etwas mit lxml.html, lxml.etree und beautifulsoup rumprobiert...

mit beautifulsoup bin ich bisher am nächsten herrangekommen (auch wenn's wirklich dreckig von mir programmiert ist)

Das ist bisher dabei rausgekommen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
tagliste = ['link1', 'link2']

from BeautifulSoup import BeautifulSoup
string = '<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>link2 <a href=/bar/bar.html>bar</a></p>'
soup = BeautifulSoup(string)
for tag in tagliste:
	soup = BeautifulSoup(string)
	if find(string, tag) > -1:
		i = 0
		link_soup = soup.findAll('a')
		for item in soup.findAll('a'):
			soup.find('a').replaceWith('[[[LINK]]]')
			i = i+1
		i_2 = 0
		for item in soup(text=True):
			text = replace(item, tag, '<a href=/bar/bar.html>bar</a>')
			soup(text=True)[i_2].replaceWith(text)
			i_2 = i_2+1
		#print link_soup
		i_3 = 0
		soup = str(soup)
		for item in link_soup:
				soup = replace(soup, '[[[LINK]]]', str(item), 1)
				i_3 = i_3+1
		string = soup	
	else :
		print 'nicht gefunden'
print str(soup)

Soweit läuft es erstmal... danke für die ganzen Hinweise ☺ (Verbessungsvorschläge werden dankend angenommen) 😬

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2128

Wohnort: Gelsenkirchen

Dein Code funkioniert bei mir nicht:

1
2
3
4
5
6
7
8
>>> import testding

Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    import testding
  File "C:\Python25\testding.py", line 8, in <module>
    if find(string, tag) > -1:
NameError: name 'find' is not defined

Ich finde ihn ohne etwas komisch. Was genau willst du denn erreichen? Sag doch mal wie dein String am Ende aussehen soll.

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

hier der komplette Code mit import...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from string import find, replace

tagliste = ['link1', 'foo']

from BeautifulSoup import BeautifulSoup
string = '<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>foo <a href=/bar/foo.html>bar</a></p>'
soup = BeautifulSoup(string)
for tag in tagliste:
	soup = BeautifulSoup(string)
	if find(string, tag) > -1:
		i = 0
		link_soup = soup.findAll('a')
		for item in soup.findAll('a'):
			soup.find('a').replaceWith('[[[LINK]]]')
			i = i+1
		i_2 = 0
		for item in soup(text=True):
			text = replace(item, tag, '<a href=/bar/bar.html>bar</a>')
			soup(text=True)[i_2].replaceWith(text)
			i_2 = i_2+1
		#print link_soup
		i_3 = 0
		soup = str(soup)
		for item in link_soup:
				soup = replace(soup, '[[[LINK]]]', str(item), 1)
				i_3 = i_3+1
		string = soup	
print str(soup)

Hiermit erreiche ich, dass alle Einträge der Tagliste im String durch einen Link '<a href=/bar/bar.html>bar</a>' ersetzt werden, wenn sie nicht in einem <a>-Tag oder <img>-Tag liegen.

Dadurch wird aus:

<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>foo <a href=/bar/foo.html>bar</a></p>

Ein:

<p><a href="link1.html">link1</a><a href="/bar/bar.html">bar</a><a href=/bar/bar.html>bar</a> <a href="/bar/foo.html">bar</a></p>

DasIch

Avatar von DasIch

Anmeldungsdatum:
2. November 2005

Beiträge: 1130

Normalerweise benutzt man nicht string.find oder string.replace sondern str.find bzw. str.replace. In Zeile 10 wäre das z.B.

1
if string.find(tag) > -1:

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

danke, ich habs umgeändert ☺

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2128

Wohnort: Gelsenkirchen

Evolis2k2 schrieb:

Hiermit erreiche ich, dass alle Einträge der Tagliste im String durch einen Link '<a href=/bar/bar.html>bar</a>' ersetzt werden, wenn sie nicht in einem <a>-Tag oder <img>-Tag liegen.

Dadurch wird aus:

<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>foo <a href=/bar/foo.html>bar</a></p>

Ein:

<p><a href="link1.html">link1</a><a href="/bar/bar.html">bar</a><a href=/bar/bar.html>bar</a> <a href="/bar/foo.html">bar</a></p>

So auch 😉 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def set_anchor(head, taglist):
    for elem in head.contents:
        if not str(elem).startswith(('<a', '<img')):
            split = str(elem).split(' ')
            i = 0
            for word in split:
                for entry in taglist:
                    if word == entry:
                        split[i] = '<a href=/bar/bar.html>bar</a>'
                i += 1
            elem.replaceWith(' '.join(split))
    return head
1
2
3
4
5
6
7
8
9
>>> from BeautifulSoup import BeautifulSoup
>>> import testding
>>> tagliste = ['link1', 'foo']
>>> tagliste = ['link1', 'foo', 'katze']
>>> string = '<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>foo <a href=/bar/foo.html>bar</a>die katze faucht</p>'
>>> soup = BeautifulSoup(string)
>>> head = soup.p
>>> testding.set_anchor(head, tagliste)
<p><a href="link1.html">link1</a><a href="/bar/bar.html">bar</a><a href=/bar/bar.html>bar</a> <a href="/bar/foo.html">bar</a>die <a href=/bar/bar.html>bar</a> faucht</p>

Es müsste nur noch implementiert werden, wenn auf dem Wort ein Satzzeichen folgt. Denn dann ist es ja nicht mehr identisch mit dem Eintrag in deiner Liste.

Evolis2k2

(Themenstarter)

Anmeldungsdatum:
5. August 2007

Beiträge: 69

Danke, ich werds mal mit deinem Ansatz ausprobieren ☺

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2128

Wohnort: Gelsenkirchen

Habe das ganze noch ein wenig erweitert:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import string


def set_anchors(soup, termlist):
    '''set_anchors(soup, termlist)
    
    Inspect all elements of a BeautifulSoup instance whether they are not 
    included by an a- or img-tag. In that case compare each word of the element 
    with the entries in termlist. Change each matching word to an anchor where 
    URL is /description/%s.html (%s = word). 
    '''
    for elem in soup.contents:
        if not str(elem).startswith(('<a', '<img')):
            elem.replaceWith(_anchors(elem, termlist))
    return soup


def _anchors(elem, termlist):
    split = str(elem).split(' ')
    i = 0
    for word in split:
        adj_word = _adjust(word)
        for entry in termlist:
            if adj_word == entry.lower():
                split[i] = '<a href="/description/%s.html">%s</a>' % (adj_word, 
                                                                      word)
        i += 1
    return ' '.join(split)


def _adjust(word):
    chars = [char for char in word.lower()]
    for char in chars:
        if char in string.punctuation:
            chars.remove(char)
    return ''.join(chars)

Beispiel, an dem hoffentlich die neue Funktionsweise deutlich wird:

1
2
3
4
5
6
7
>>> import bla
>>> from BeautifulSoup import BeautifulSoup
>>> string = '<p><a href="link1.html">link1</a><a href=/bar/bar.html>bar</a>foo <a href=/bar/foo.html>bar</a>die Katze, die im Garten ist, faucht</p>'
>>> soup = BeautifulSoup(string)
>>> termlist = ['foo', 'bar', 'Katze', 'garten']
>>> bla.set_anchors(soup, termlist)
<p><a href="link1.html">link1</a><a href="/bar/bar.html">bar</a><a href="/description/foo.html">foo</a> <a href="/bar/foo.html">bar</a>die <a href="/description/katze.html">Katze,</a> die im <a href="/description/garten.html">Garten</a> ist, faucht</p>

Jetzt fehlt eigentlich nur noch, dass das Komma hinter Katze nicht von Anker eingeschlossen wird.

Entspricht dies in etwa deinen Vorstellungen (um mal weg von den ganzen foobars zu kommen)?

Antworten |