Textteile ersetzten...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Code: Alles auswählen

text = """Als Blindtext wird Text bezeichnet,
den man bei der Gestaltung von
Publikationen verwendet, wenn der eigentliche
Text noch nicht vorliegt."""

lexicon_data = {
    "Blindtext" : "1",
    "der Gestaltung": "2",
    "der": "3",
}

def insert_links(text, lexicon_data):
    keys = lexicon_data.keys()
    keys.sort(cmp=lambda x, y: cmp(len(y), len(x)))
    for key in keys:
        if key in text:
            text = text.replace(key, '<a href="%s">%s</a>' % (lexicon_data[key], key))
    return text


print insert_links(text, lexicon_data)
das kommt raus:

Code: Alles auswählen

Als <a href="1">Blindtext</a> wird Text bezeichnet,
den man bei <a href="2"><a href="3">der</a> Gestaltung</a> von
Publikationen verwendet, wenn <a href="3">der</a> eigentliche
Text noch nicht vorliegt.
soll aber so sein:

Code: Alles auswählen

Als <a href="1">Blindtext</a> wird Text bezeichnet,
den man bei <a href="2">der Gestaltung</a> von
Publikationen verwendet, wenn <a href="3">der</a> eigentliche
Text noch nicht vorliegt.
Hat jemand eine Idee, wie man statt <a href="2"><a href="3">der</a> Gestaltung</a> nur das ersetzt: <a href="2">der Gestaltung</a>

(Das ganze ist für http://www.python-forum.de/topic-19964.html , aber hier geht es nicht um den HTML Teil)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du müßtest Einfach über den Text iterieren und bei jedem Wort prüfen, ob es durch einen Link ersetzt wird. Letztlich könntest Du dann nach der ersten Ersetzung zum nächsten Wort springen. So wäre der beschriebene Fall lösbar.

Aber: Woher weißt Du denn, welches Wort Du verlinken willst?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ne, so einfach ist es nicht. Denn ich will nicht nur ein Wort ersetzten (Das geht jetzt schon) Sondern ich will auch kombinationen aus zwei oder mehreren Wörtern ersetzten.
Dabei soll "eins zwei" vor dem einzelnen "eins", "zwei" bevorzugt werden.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab was, das macht genau das was ich möchte:

Code: Alles auswählen

PLACEHOLDER = "<%s>"

def insert_links(text, lexicon_data):
    keys = lexicon_data.keys()
    keys.sort(cmp=lambda x, y: cmp(len(y), len(x)))
    temp = []

    for key in keys:
        if key in text:
            id = len(temp)
            text = text.replace(key, PLACEHOLDER % id)
            temp.append(key)

    for id, key in enumerate(temp):
        lexicon_data[key]
        text = text.replace(
            PLACEHOLDER % id,
            '<a href="%s">%s</a>' % (lexicon_data[key], key)
        )

    return text
Die Idee: Damit keine doppelten Ersetzungen passieren, mache ich einen zwischen schritt und ersetzte das gefundene Wort/Wörter mit einem Platzhalter. So kann man es nicht nochmal finden.

Aber irgendwie umständlich, oder nicht?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab eine bessere Lösung:

Code: Alles auswählen

import re

def insert_links(text, lexicon_data):
    keys = lexicon_data.keys()

    # Sort longest to shortest
    keys.sort(cmp=lambda x, y: cmp(len(y), len(x)))

    # match on all existing keys with ignorecase
    regex = re.compile("|".join(keys), re.IGNORECASE | re.UNICODE | re.MULTILINE)

    text = regex.sub(lexicon_data, text)

    return text

class LexiconData(dict):
    def __call__(self, matchobject):
        # "create link"
        word = matchobject.group(0)
        term = self[word.lower()]
        return term + word + term

lexicon_data = LexiconData({
    "blindtext" : "1",
    "der gestaltung": "2",
    "der": "3",
})

text = """ein BlindText wird dEr Gestaltung von DER eigentliche."""

print insert_links(text, lexicon_data)
raus kommt:
ein 1BlindText1 wird 2dEr Gestaltung2 von 3DER3 eigentliche.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Die re ist noch falsch. Es wird bei "foo" auch "fooX" und "Xfoo" getroffen.

Ich hab nun eine bessere RE, allerdings immer zwei Probleme:

Code: Alles auswählen

import re

html = '''foo <- should match
<a href="Foo Bar"><strong>Foo Bar</strong> Bar</a>
one Foo Bar two FOO three BaR four
Here not: Fooo or Xbar
This should match: foo. This too: foo, and this: bar:
and this: <strong>foo</strong>
should match -> bar'''

keys = ["foo bar", "foo", "bar"]

regex = re.compile("(?<=[\s\>])(%s)(?=[\s\<\.,:])" % "|".join(keys), re.IGNORECASE | re.UNICODE | re.MULTILINE)

def test(matchobject):
    return "*" + matchobject.group(0) + "*"

print regex.sub(test, html)
Ausgabe:

Code: Alles auswählen

foo <- should match
<a href="Foo Bar"><strong>*Foo Bar*</strong> *Bar*</a>
one *Foo Bar* two *FOO* three *BaR* four
Here not: Fooo or Xbar
This should match: *foo*. This too: *foo*, and this: *bar*:
and this: <strong>*foo*</strong>
should match -> bar
Problem: Das erste foo und das letzte bar werden nicht getroffen. Wie kann ich das machen?

Mir fällt spontan nurn ein ein Leerzeichen vorn und hinten dran zu packen und hinterher wieder ab zu ziehen. Aber das ja doof ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
vespe
User
Beiträge: 5
Registriert: Mittwoch 2. Dezember 2009, 12:20

jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Vielleicht hilft Dir diese Regex weiter:

Code: Alles auswählen

text = '''foo <- should match 
<a href="Foo Bar" href="Foo Bar"><strong>Foo Bar</strong> Bar</a> 
one Foo Bar two FOO three BaR four 
Here not: Fooo or Xbar 
This should match: foo. This too: foo, and this: bar: 
and this: <strong>foo</strong> 
should match -> bar''' 

data = {"foo bar":'1', "foo":'2', "bar":'3'}

def repl(m):
    if m.groupdict().get('tag'):
        return m.group(0)
    matches = filter(lambda i: i!=None, m.groups())
    return '{1}<a href="{0}">{2}</a>{3}'.format(data.get(matches[1].lower()),
                                                                      *matches)

regex = re.compile('(?P<tag>(\<\w+)((\s\w+=".*?")+)?)|'+"|".join('(^|\W)('+s+')(\W|$)'
                        for s in sorted(data.keys(), key=len, reverse=True)),
                                      re.IGNORECASE | re.UNICODE | re.MULTILINE)
print regex.sub(repl, text)
sollte das ergeben:

Code: Alles auswählen

<a href="2">foo</a> <- should match
<a href="Foo Bar" href="Foo Bar"><strong><a href="1">Foo Bar</a></strong> <a href="3">Bar</a></a>
one <a href="1">Foo Bar</a> two <a href="2">FOO</a> three <a href="3">BaR</a> four
Here not: Fooo or Xbar
This should match: <a href="2">foo</a>. This too: <a href="2">foo</a>, and this: <a href="3">bar</a>:
and this: <strong><a href="2">foo</a></strong>
should match -> <a href="3">bar</a>
Wirklich schön ist das nicht und ist ein bisschen von hinten durch die Brust gestrickt. Gerade das Auftreten Deiner Schlagwörter in html-Attributen ist umständlich gelöst, die werden mitgesucht um sie beim Ersetzen wieder zu verwerfen. Sollten die Texte zu sehr mit html-Markup zersetzt sein, ist wahrscheinlich ein Parser, der die Textknoten abläuft, sinnvoller.

Edit: Regex ausgebessert, um mit Tags und Attributen besser klar zu kommen. :evil:
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Aber genau das mache ich doch. Ich erhalte die Text Passagen aus HTMLParser.handle_data(), siehe: http://www.python-forum.de/post-162880.html#162880

Dennoch muss ich ja irgendwie die Text teile modifizieren. Einen richtigen DOM Baum zu haben und dann einzelne DOM Elemente einzufügen, um dann hinterher wieder html zu generieren, halte ich aber für Overkill. Ich wüßte zumindest nicht wie man das mal so eben macht.

Werde mir deine RE mal ansehen, denn z.Z. mache ich das mit dem Leerzeichen hinzufügen und wieder abziehen :oops:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

jens hat geschrieben:Aber genau das mache ich doch. Ich erhalte die Text Passagen aus HTMLParser.handle_data(), siehe: http://www.python-forum.de/post-162880.html#162880
Entschuldige, ich kannte den Thread nicht und hab Deinen Verweis darauf im Ausgangspost überlesen.
jens hat geschrieben:Dennoch muss ich ja irgendwie die Text teile modifizieren. Einen richtigen DOM Baum zu haben und dann einzelne DOM Elemente einzufügen, um dann hinterher wieder html zu generieren, halte ich aber für Overkill. Ich wüßte zumindest nicht wie man das mal so eben macht.
Mit einer der ET-Varianten geht das eigentlich recht einfach. Gerade dann, wenn ein Seitenschnispel noch viel "Baum" enthält und Du entscheiden musst, zu welchem Element der Textknoten gehört, hilft das ungemein, da RegEx hier alt aussieht (aber das weisst du ja selbst). Ich bevorzuge lxml, das ist sehr performant und bringt noch ein paar mehr nützliche Funktionen (XPath etc.) mit. Allerdings ist der Code dann nicht mehr mit anderen ETs nutzbar und man hat eine c-Bibliothek als Abhängigkeit.
Du hast vollkommen Recht, für eine "flache" Textpassage, in der maximal ein paar inline-Elemente nebeneinander vorkommen, ist der Einsatz eines Parsers Overkill.
jens hat geschrieben:Werde mir deine RE mal ansehen, denn z.Z. mache ich das mit dem Leerzeichen hinzufügen und wieder abziehen :oops:
Also RegEx ist wirklich gut für Knotenbildung im Hirn ;) Hier nochmal eine vereinfachte Version, was das Tag-Handling angeht:

Code: Alles auswählen

regex = re.compile('(?P<tag><[^>]*)|'+"|".join('(^|\W)('+s+')(\W|$)'
                        for s in sorted(data.keys(), key=len, reverse=True)),
                                      re.IGNORECASE | re.UNICODE | re.MULTILINE)
Antworten