Seite 1 von 1

Textteile ersetzten...

Verfasst: Mittwoch 3. März 2010, 12:48
von jens

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)

Verfasst: Mittwoch 3. März 2010, 13:32
von Hyperion
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?

Verfasst: Mittwoch 3. März 2010, 13:47
von jens
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.

Verfasst: Mittwoch 3. März 2010, 13:59
von jens
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?

Verfasst: Mittwoch 3. März 2010, 14:32
von jens
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.

Verfasst: Mittwoch 3. März 2010, 16:35
von jens
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 ;)

Verfasst: Mittwoch 3. März 2010, 19:06
von vespe

Verfasst: Mittwoch 3. März 2010, 19:40
von jerch
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:

Verfasst: Donnerstag 4. März 2010, 08:10
von jens
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:

Verfasst: Donnerstag 4. März 2010, 11:58
von jerch
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)