Szerkesztő:Bináris/Fixes and functions HOWTO

In English Magyarul
This page teaches how to use your own functions for improving the abilities of replace.py and fixes.py/user-fixes.py.

The syntax of the code follows that of user-fixes.py. Here the fixes may directly follow their functions. If you wish to use functions in fixes.py, the syntax will slightly differ. (All the fixes are given in a huge dictionary, and the functions should be in front of fixes — that means not next to their fix.) You should be familiar with Python and match objects at a basic level to write your own functions.

This introduction shows the use of functions through five typical examples:

  1. You search an expression, but still don't know what to replace it with (there are more possibilities).
  2. There are several similar lines in your fix, and you are fed up with modifying them each alone.
  3. The regex would be too complicated to write in one expression (both hard to read it and easy to spoil). This shows also how to use external functions and how to use a fix in different ways without rewriting a lot of lines each time and gives ideas for lookahead/lookbehind.
  4. You want to remove repeated links to the same page from an article. This means your decision depends not only on the match itself but on the remaining part of the text, too.
  5. You want to change the case of initial letters in a set of words. This is so simple that it can be solved with a lambda function.

Some of the examples are a bit Hungarian-specific because they go by my own needs (but you will understand the ideas), while some of them are international.

Ez a cikk arról szól, hogyan használhatod hatékonyabban saját függvények bevetésével a replace.py-t a fixes.py vagy a user-fixes.py modullal.

A leírt szintaxis a user-fixes.py-ban érvényes, ahol a fix közvetlenül a saját függvénye után állhat. A fixes.py-ban az összes fix egy nagy szótárban van felsorolva, és a függvényt ez elé a szótár elé kell írnod. A saját függvények használatához ismerned kell a Pythont és a match objektumokat.

Ebben a bevezetőben öt tipikus példán keresztül mutatom be a függvények használatát:

  1. Tudod, mit keresel, de nem tudod előre, mire akarod cserélni, mert többféle lehetőség van.
  2. Sok hasonló sor van a fixedben, és unod, hogy ugyanazt a javítást mindegyikben végig kell csinálni.
  3. Túl bonyolult volna egyetlen kifejezésben megírni a regexet (olvashatatlan is lesz, és elrontani is könnyebb). Ez a példa megmutatja a külső függvények használatát és azt is, hogyan lehet hol erre, hol arra használni a fixet anélkül, hogy át kéne írni egy rahedli sort benne, valamint ötleteket ad az előre- és visszaolvasáshoz.
  4. El akarod távolítani egy cikkből az ugyanarra a lapra mutató ismételt hivatkozásokat. Ez egy példa arra, amikor a csere nemcsak magától a találattól, hanem a szöveg többi részétől is függ.
  5. Kisbetűsre akarod javítani a személyekről elnevezett mértékegységeket. Sok szóban kell nagyon egyszerű javítást végezni, ezért a lambdafüggvény a kényelmes megoldás.

Choosing between two replacements in runtime / Két csere közötti választás futásidőben szerkesztés

In English Magyarul
Have you ever wished if you could choose between two options in runtime? Here is the solution!

Hamár is a misspelled word in Hungarian, but we don't know what to replace it with when we write our fix. It may be namely either habár (another word which is often written with this typo) or ha már written in two words. With this development replace.py will first show us an environment of the word (50 characters before and after) for each occurence (the word itself will be shown in red), then it asks us to choose. With "yes" it will change hamár to habár, otherwise (with "no" or pressing enter) it will be replaced by ha már. At the end replace.py will show all the changes in the usual way and ask us to confirm. match.group(1) stands for "\1" (because the first character may be h or H as well).

The below picture shows the fix in work.

Note that the function name demofunction1 stands here without its parentheses! We give the function name instead of replacement regex, and textlib.replaceExcept() will call it.

Another similar example (combined with lambdas)

Előfordult már, hogy futás közben kellett volna választanod kétféle lehetőség közül? Íme, a megoldás:

A hamár szó gyakran a habár elgépelt változata, de lehet hibásan egybeírt ha már is. Ezt nem tudhatjuk előre, amikor a fixet megírjuk! Ezzel a fejlesztéssel a replace.py először egyenként megmutatja a szócikkben előforduló összes hamár ±50 karakteres környezetét (magát a szót pirossal), majd megkérdezi, hogy cseréljük-e habár-ra. Ha y-t nyomunk, kicseréli, ha n-et vagy entert, akkor ha már lesz belőle. Végül a szokott módon megmutatja az összes változtatást, és megerősítést kér. A match.group(1) itt ugyanazt a szerepet tölti be, mint a szokásos regexben a \1 (vagyis visszateszi a kis vagy nagy h-t, ahogy találta).

A lenti kép mutatja a fixet működés közben.

Figyeld meg, hogy a függvény neve (demofunction1) zárójelek nélkül áll, csak a függvénynévvel helyettesítjük a második regexet, és a textlib modul replaceExcept() függvénye fogja majd meghívni a függvényt.

Egy hasonló példa (lambdafüggvénnyel, ld. lent)

def demofunction1(match):
    text = match.string
    start = match.start()
    end = match.end()
    minus = min(start,50) #Don't go under zero
    pywikibot.output(u'%s\03{lightred}%s\03{default}%s' % 
        (text[start-minus:start], text[start:end], text[end:end+50]))
    choice = pywikibot.inputChoice(
            match.group() + u' legyen habár?',
            ['Yes', 'No'], ['y', 'N'], 'N')
    if choice == 'y':
        return match.group(1)+u'abár'
    else:
        return match.group(1)+u'a már'

fixes['demo1']= {
    'regex': True,
    'msg': {
           'hu':u'"Hamár" javítása valamire',
    },
    'replacements': [
        (ur'(h|H)amár', demofunction1),
    ],
    'exceptions': { #Your exceptions here
    }
}

 

Generating regexes by a function / A regexek elkészítése függvénnyel szerkesztés

In English Magyarul
This is a part of a rather complicated fix that corrects Hungarian dates. You may see that the complexity of the fix is moved to the function and the fix itself becomes very simple. However, this function is still simpler by far than the original fix was. Problems:
  • There were 3 lines for each month to replace abbreviated names with unabbreviated ones; alltogether 36 lines. Now there are only 3 lines and a loop on months.
  • Hungarian language uses suffixes, and last four month names have different suffixes than the first eight. This problem is solved by a nested function, thus we can keep the unity of lines.
  • There were several lines that contained all the 12 month names and a long repeated part. They were very long and hard to see the difference. Any change had also be made in all of the similar lines. Now the month names are in one variable, the repeated part of these lines in another one, and we can concentrate on the different part of the lines. I show the first 4 here for example.

This is not a runtime function! It will be called when replace.py starts; it substitutes the replacements value of the fixes dictionary. Therefore we must use it with parentheses! You could also write a function for exceptions that are repeated fix by fix, or for edit summary, or even generate the whole fix by a program. Unfortunately, we may not modify edit summary in runtime by this method.

Ez a fix kivonat a Szerkesztő:BinBot/munka/dátum címen látható, meglehetősen összetett dátumjavítóból. Ebben a megoldásban maga a fix nagyon leegyszerűsödik, mert a bonyolult rész a függvénybe költözött. A problémák:
  • Mindegyik rövidített hónapnév feloldására három sor szolgált, ez összesen 36; most három sor és egy, a hónapokon végigfutó ciklus oldja meg a feladatot, így az összes hónaphoz tartozó sorok egy helyen javíthatóak.
  • A négy utolsó hónapnév magas hangrendű toldalékot vonz, a többi mélyet; ezt egy beágyazott függvény oldja meg, így a sorok egyformák maradhatnak.
  • Az eredetiben egy csomó hasonló sor van, mindegyikben egy hosszú ismétlődő résszel és a hónapnevek felsorolásával. Most a hónapnevek egy változóba kerültek, az ismétlődő rész egy másikba, és így jobban látszik a sorok különböző része, és könnyebb azzal foglalkozni. Itt csak az első négy sor van példa gyanánt.

Ez nem futásidejű függvény, hanem a replace.py elindulásakor hívódik meg és gyártja le a helyettesítéseket; éppen ezért itt fontos a zárójel! Hasonlóképpen generálhatnánk kivételeket, szerkesztési összefoglalót vagy akár egész fixeket is (sajnos azonban futásidőben változó szerkesztési összefoglalót nem).

def demofunction2():
    """
    Various fixes with month names and dates
    Hónapnevek és dátumok javítása többféleképp
    """
    #This is just copied from an older version, I won't type it again
    longs = u'január|február|március|április|május|június|július|augusztus|szeptember|október|november|december'
    long = longs.split('|')
    short = u'jan|febr?|márc|ápr|máj|jún|júl|aug|szept?|okt|nov|dec'
    short = short.split('|')
    def told(i):
        #This makes something with Hungarian suffixes
        #Sept, Oct, Nov, Dec are different from others
        #Note that Sept is 8 etc. because list indices start from 0
        if i > 7:
            return u'(i|ben|től)'
        else:
            return u'(i|ban|tól)'
    repltext = []
    #Three types of replacements per month (originally 36 lines in fix)
    for i in range(12):
        repltext.append((ur'%s\.\s?(\d{1,2})' % short[i],ur'%s \1' % long[i]))
        repltext.append((ur'%s\.-%s' % (short[i],told(i)),ur'%s\1' % long[i]))
        repltext.append((ur'(\d|\.| |\;|\-|\–)%s\.' % short[i],ur'\1%s' % long[i]))
    s = ur'(\d{3,4}|\[\[\d{3,4}\]\])\.?\s?(|\[\[)(%s)(|\]\])\s?(\d{1,2})\.?' % longs
    #Four similar lines with the same beginning that is now in variable s.
    #Thus we can modify it in one line if necessary, and other lines will be shorter
    repltext.append((ur'%s ?(\[a-zA-Z])' % s, ur'\1. \2\3\4 \5. \6')) #linkelt is; ez itt a nem sorvégi dátum, betűvel követve, szóköz pótlása. (.=nem sorvégjel)
    repltext.append((ur'%s(\)|\/|\,|;|\<|\{|\})' % s, ur'\1. \2\3\4 \5.\6')) #linkelt is; ez itt a nem sorvégi dátum, nem pótlunk szóközt (pl. ")" van utána). "]" most nem lehet, mert akkor a ]]-n belülre tenne pontot, majd.
    repltext.append((ur'%s ?\n' % s, ur'\1. \2\3\4 \5.\n')) #linkelt is; a sorvégi dátumok után nem tesz szóközt.
    repltext.append((ur'%s\s?(\-|\–)\s?\)' % s, ur'\1. \2\3\4 \5.)')) #Ez az alatta levő sor melléklete, mert csukó zárójel elé nem kell szóköz, sőt gondolatjel sem.
    pywikibot.output('\n'.join('('+r[0]+', '+r[1]+')' for r in repltext))
    return repltext

fixes['demo2']= {
    'regex': True,
    'msg': {
           'hu':u'Dátumok javítása',
    },
    'replacements': demofunction2(),
    'exceptions': { #Your exceptions here
    }
}

Complicated replacements, variable fix / Bonyolult csere változó fixben szerkesztés

In English Magyarul
This example is a Swiss knife.   Basic task is to replace month numbers with their names and removing leading zeros from day numbers. Once we work on dates, we may put spaces where necessary.

The main reason to introduce user functions was the complexity of this task: it is too hard to decide if a dot is necessary at the end of the date, so it does not easily fix into one regex (not to say that the regex should be repeated for each month). Dot and space should sometimes be added and sometimes removed, and hyphen should often be replaced with ndash.

Once we are using a function to replace month numbers with month names, why not handle Roman numbers, too? This is a harder task, and we may use an outer function that I had written earlier. You may download this module from user:BinBot/roman.py (or vote here to have it in the framework). If the function cannot import the module, it will switch off handling Roman numbers.

Another idea also came from real life. I used this fix originally to link the dates, but for general use it is not always good. Nobody would be happy with the idea to put brackets in and out from 12 lines all the time. Good news: this is not necessary any more. If you write a function with a switch, you only have to change between True and False.

As you see, the fix itself (not regarding comments and exceptions) became very simple, one line that uses the function and one line of classical regex, while the function may be as complex as necessary.

Special thanks to Morten Wang for giving the idea to combine fixes and functions.

Ennek az összetett példának a kiindulópontja ez a javítás volt, amivel a számmal jelölt hónapokat nevesítettem. Eredetileg minden hónaphoz egy külön sor tartozott. Az általánosítás során bekerült a szóközök pótlása és a vezető nulla törlése a nap sorszámából, viszont kikerült a linkelés (ehhez 12 sorból kellett 4-4 zárójelet törölni). Persze később még szükség lehet rá, és nem nagy öröm ide-oda pakolgatni; innen jött az ötlet, hogy ha már függvényt írok, akkor legyen benne egy egyszerű kapcsoló. Egy másik ötlet: ha már van függvény, akkor a római számmal írt hónapokat is lehet nevesíteni, ami közönséges regexekkel elég bonyolult lenne (gyakorlatilag még 12 majdnem ugyanolyan sor). Egy korábban írt modul importálásával lehet megoldani a római számok konverzióját (user:BinBot/roman.py), de ha a függvény nem találja a helyén a modult, akkor egyszerűen továbblép.

A legbonyolultabb dolog annak eldöntése, hogy kell-e pontot tenni a dátum után vagy netán a meglévőt is le kell venni. Ez függ attól, hogy van-e kötőjel utána, s ha van, akkor valódi szerepében vagy rontott nagykötőjelként; mindkét esetben lehet fölösleges szóköz is előtte. Az is lehet, hogy a kötőjelet kell nagykötőjelre cserélni; ilyen eset lehet, ha szám, kérdőjel, [ vagy ) áll utána. Ez a bonyolult döntés meghaladta egy egyszerű reguláris kifejezés kereteit, pláne 12 példányban. Ehhez kerestem megoldást, amikor Morten Wang felhívta a figyelmemet a függvények beillesztésének lehetőségére. Így kezdtem kísérletezni, és ez lett ennek az egész útmutatónak a mozgatórugója.

Mint látható, maga a fix (leszámítva a megjegyzéseket és a kivételeket) nagyon leegyszerűsödött, egy függvényt használó és egy klasszikus reguláris kifejezést használó sora van, míg a függvény tetszőlegesen összetett lehet.

def demofunction3(match):
    #Do you want to linkify dates? (True/False)
    linkify = False
    #Do you want to recognize Roman numbers? (True/False)
    #You must have the module from here:
    #http://hu.wikipedia.org/wiki/Szerkeszt%C5%91:BinBot/roman.py
    romans = True

    #Copied from the other fix
    longs = u'január|február|március|április|május|június|július|augusztus|szeptember|október|november|december'
    long = longs.split('|')
    day = match.group(3)
    tail = match.group(4)
    if romans:
        try:
            import roman #Multiple import is not a problem
        except ImportError:
            pywikibot.output('Module roman.py not found')
            romans = False
    try:
        month = int(match.group(2))
    except ValueError:
        if romans:
            month = roman.roman(match.group(2)).result #0 for invalid
        else:
            month = 0
    if month<1 or month>12: #Not a valid month, return the text untouched
        # print match.group() #only for debug
        return match.group()
    #We try to disclose version numbers which may appear as "small" dates
    #without spaces
    try:
        year = int(match.group(1))
    except ValueError:
        #The problem must have been the closing ]], no other case
        year = int(match.group(1)[:-2])
    if tail:
        date = match.group()[:-len(tail)]
    else:
        date = match.group()
    if year + month + int(day) < 20 and not ' ' in date:
        #Assume it is a version number and give it back untouched
        return match.group()
    #The next two ifs are kind of lookahead: we check the first character after the match.
    if not tail and match.string[match.end()] in '0123456789':
        #Exclude further false matches, this cannot be a date.
        return match.group()
    if tail =='.' and match.string[match.end()] in '0123456789':
        #Exclude further false matches, this cannot be a date.
        #We trust that match.string[match.end()] won't cause a problem,
        #since interwikis and categories are expected at the end of the article.
        return match.group()
   #From now on we assume the match contains a valid date.
    monthname = long[month-1]
    if linkify:
        open = '[['
        close = ']]'
    else:
        open = close = ''
    new = match.group(1) + u'. ' + open + monthname + ' ' + day
    
    #We process the last group. This was the point where the whole
    #experiment with funcions started from, and the reason why this
    #howto was created.
    dot = space1 = hyphen = ndash = suffix = space2 = next = ''
    if tail:
        puffer = list(tail)
        print(puffer) #only for debug
        #Parsing
        while puffer:
            c = puffer.pop(0)
            if c == '.':
                dot = c
            elif c == ' ' and not (hyphen or ndash):
                space1 = c
            elif c == '-':
                hyphen = c
            elif c == u'–': #This is alt+0150
                ndash = c
            elif c in u'aeáéijtv':
                suffix = c
            elif c == ' ' and (hyphen or ndash):
                space2 = c
            elif c in u'[?)0123456789':
                next = c
        #Construction
        if next == ')' or next == '':
            optspace = ''
        else:
            optspace = ' '
        if hyphen and suffix:
            if linkify:
                new += '.|' + monthname + day + close + hyphen + suffix + space2 + next
            else:
                new += hyphen + suffix + space2 + next
        elif hyphen and not suffix and next: #Should be ndash
            new += '.' + close + u' –' + optspace + next
        elif ndash and not suffix:
            new += '.' + close + u' –' + optspace + next
        elif not (hyphen or ndash): #Nothing after ndash may be true
            new += '.' + close + space1
        else:
            if dot:
                new += dot + close + space1 + hyphen +  ndash + suffix + space2 + next
            elif linkify:
                new += '.|' + monthname + day + close + tail
            else:
                new += tail
    else: 
        #If nothing of these is found, a dot is necessary
        new += '.' + close
    #This is a lookbehind:
    if match.string[match.start()-1] in u'.:,;?!)]':
        new = ' ' + new
    return new
    

fixes['demo3']= { #use with -recursive option!
    'regex': True,
    'msg': {
           'en':u'Your edit comment here',
           'hu':u'Dátumok előjavítása kézi botszerkesztéssel: számmal jelölt hónapnevek feloldása, szóköz és pont pótlása/törlése, nullátlanítás, nagykötőjel',
    },
    #Original replacements had this form, one line for each month:
    #(ur'(\d{1,4}(?:\]\])?)\. ?01\. ?(\d\d?)', ur'\1. január \2'),
    #and one more similar line for each in case we want to linkify the date.
    #However, 01 could be sometimes just 1.
    #Month numbers are sometimes written with Roman numbers in Hungarian,
    #such as 2011. VII. 2.; they will be replaced either
    #We simplify it to one line
    'replacements': [
        (ur'(\d{1,4}(?:\]\])?)\. ?(\d{1,2}|I?[VX]|[VX]?I{1,3})\. ?(\d\d?)(\.? ?([\-–][aeáéijtv]? ?[\[\?\)\d]?)?)', 
            demofunction3),
        (ur'(január|február|március|április|május|június|július|augusztus|szeptember|október|november|december) ?0(\d)', ur'\1 \2'),
    ],
    'exceptions': {
        'inside-tags': [
            'hyperlink', 
            'gallery',
            # 'table', 
            #Nem szabad cserélnünk, ha dátum szerint rendezhető táblázatban van számmal jelzett hónap.
        ],
        'title': [
            ur'Magyar LMBT-kronológia.*', #span id, stb. célra használt dátumok
            u'SQL', #A mintadátumnak az adatbázis formátumában kell maradnia.
        ],
        'inside': [
            ur'\[\[([Ii]mage|[fF]ile|[fF]ájl|[kK]ép)\:[^\]\|]+?\|', # Képek nevében ne
            ur'\| ?doi ?=.*?\}\}' #Ezek valami adatbázisos források (Vénusz, Merkúr, Gombák szócikk)
        ],
        'text-contains': [
            ur'(\{\{[Ss]zinnyei|\{\{[Pp]allas\}\}|Vályi András|Fényes Elek)',
        ],
    }
}

Lookahead and lookbehind / Előre- és visszaolvasás szerkesztés

In English Magyarul
Use Ctrl F in the above demofunction3() to find the phrases "lookahead" and "lookbehind" in comments. These are also useful and sometimes simpler than inside regex. A lookbehind in a regular expression, either positive or negative, must be fixed width. For example, (?<!\*|==) or (?<!# ?) are illegal negative lookbehinds. Functions may be used to read forward or back easily, but we must beware of index errors (not checked in the above example).

Let's see an example that would be very complicated with pure regular expressions. Whenever we mention Trianoni béke/határ/szerződés/békeszerződés/békediktátum or such expressions in the middle of a sentence, the initial t should be lower case, but not in the beginning of a title-like occurence. (All these expressions refer to the Treaty of Trianon. It is unlikely to find them in the beginning of a sentence that is not a title.) We exclude two cases for the sake of efficiency, because they would give a lot of false positives:

  1. The expression "Trianoni béke" or "Trianoni békeszerződés" is followed by a pipe character (|). We assume it is in a link and only the text after the pipe will appear in the article. This can be solved by a regular negative lookahead. (Variable width is allowed here.)
  2. The word "Trianoni" is preceeded by an asterisk or hash mark (for unnumbered or numbered list), or by "==" (section title), which may be followed by one or more spaces and optionally two square brackets if the text is linked. This is not to be solved by a regular negative lookbehind for it has variable width; instead we give the whole string to the function that will decide what to do with it. If it founds both the outer and the inner parentheses, it gives back the whole string untouched as we have nothing to do. Otherwise the lower case word "trianoni" is returned with the content of the outer parenthesis, if there is any.
Keress rá a fenti demofunction3() függvényben a "lookahead" és "lookbehind" szavakra. Olykor egyszerűbb a függvényben elintézni ezeket, mint a reguláris kifejezésen belül. A pozitív vagy negatív visszaolvasás csak fix szélességű lehet, így például a (?<!\*|==) és a (?<!# ?) egyaránt hibát okozna. Függvényekkel rugalmasabban tudunk előre- és visszaolvasni, csak figyelni kell az indextúllépésre (amit itt nem tettem meg).

Vegyünk egy bonyolultabb példát (ami legalábbis függvény nélkül rendkívül bonyolult lenne). A Trianoni béke/határ/szerződés/békeszerződés/békediktátum stb. javítandó kis t-re, mivel a trianoni melléknév, és semmi oka nagybetűsnek lenni. Igen gyakori hiba, tankönyvi esete a botmunkának. Két esetben általában nem akarjuk javítani, és ezeket a hatékonyság érdekében érdemes kizárni:

  1. A „Trianoni béke” vagy „Trianoni békeszerződés” után egy függőleges vonal van, ami a link első részére utal. Nincs mit javítani, a vonal utáni rész látszik a cikkben. Ezt még a reguláris kifejezésen belül is meg tudjuk oldani egy negatív előreolvasással, mivel itt megengedett a változó hosszúság.
  2. A „Trianoni” szó előtt csillag vagy kettőskereszt (a felsorolás jelei) vagy két egyenlőségjel (szakaszcím) áll, ezt esetleg egy vagy több szóköz és talán két nyitó zárójel követi a link miatt (címszerű helyzet). Ezt nem lehet a reguláris visszaolvasással megcsinálni (vagy csak szörnyen rondán), ezért az egész csoportot átadjuk a függvénynek, és ott döntjük el, kell-e cserélni. Ha megtalálta a külső és a belső zárójelet is, akkor nem cserélünk, hanem visszaadjuk az egész találatot; ha csak a külsőt, akkor azt változatlanul visszaküldjük, utána a kijavított melléknévvel, ha pedig nincs zárójel, akkor csak a trianoni szót kell csereként beillesztenünk. Ezzel az egyszerű függvénnyel az esetek sokkal áttekinthetőbbé válnak, mint a reguláris kifejezés mindenáron való erőltetésével.
def vegyesjav1_Tri(m):
    if m.group(1):
        if m.group(2):
            return m.group() #
        else:
            return m.group(1) + u'trianoni' #
    else:
        return 'trianoni'
  (ur'((\*|#|==)? *(\[\[)?)Trianoni(?! béke(szerződés)?\|)', vegyesjav1_Tri), 
  #Most ki vannak zárva a *, #, == utáni Trianoni és [[Trianoni esetek.

Removing multiple links / Többszörös linkek eltávolítása szerkesztés

In English Magyarul
This is a simple unlinking tool that will leave only the first occurence of wikilinks. It is just a demo and far from perfect, but will usually work well if there is no infobox in the article and there are no links in image descriptions. (You may want to leave multiple links if one of them is in the infobox and the other in main text, or one in an image description text and the other in the main text.) Never use it automatically! (This one is not specific for huwiki and should work for all wikis.) Ez az egyszerű eszköz az ugyanazon cikkre mutató belső linkek közül csak az elsőt hagyja meg. Még messze nem tökéletes, nem kezeli külön az ismétlődéseket, ha azok az infoboxban vagy a képaláírásban vannak (ilyen esetekben meg szoktuk hagyni a duplázást), de jól fog működni, ha nincs a cikkben se infobox, se képaláírásban előforduló link. Csak kézi ellenőrzéssel használd!
def demofunction4(match):
    pretext = match.string[:match.start()]
    if '[[' + match.group(1) + ']]' in pretext or '[[' + match.group(1) + '|' in pretext:
        #This text was already linked.
        if match.group(2): #Is there a piped part? Return it without the pipe.
            return match.group(2)[1:]
        else: #Return the text among brackets
            return match.group(1)
    else:
        # This is the first linked occurence of this text, leave untouched.
        return match.group()

fixes['demo4']= {
    #This fix will remove multiple links of the same text in one article
    #TODO: Don't remove the link if the text has previously been linked only
    #      in an infobox at the beginning of the article, and this occurence
    #      is outside of that infobox.
    #TODO: exclude image description texts from pretext, handle nested brackets
    #      in image links
    'regex': True,
    'msg': {
           'en':u'Bot: unlinking multiple occurences with manual edit',
           'hu':u'Többszörös linkelések eltávolítása (kézi botszerkesztés)',
    },
    'replacements': [
        (ur'\[\[(.*?)(\|.*?)?\]\]', demofunction4),
    ],
    'exceptions': {
        'inside-tags': [
            'hyperlink',
            'interwiki',
        ],
        'inside': [
            r'\[\[([Ff]ile|[Kk]ép|[Ff]ájl):.*?\]\]', #Images, works partially
        ],
     }
}

Using lambdas / A lambdafüggvények használata szerkesztés

In English Magyarul
Units of measurement are written in lower case in Hungarian, even if they are named after a scientist. There are a lot of them, but only the first letter of each shall be changed. This is a simple task that you don't want to do as many times as the number of the involved words. So this is a big time to use lamda functions. (The word is likely to be a unit if it is preceeded by a numeral.) As a side effect, this replacement corrects "millió" and "milliárd" (Hungarian names for 106 and 109) which are also to be written in lower case.

m.group(1) + ' ' + m.group(2).lower() is very similar to r'\1 \2' except that \2 is put in lower case (which was the main purpose of the fix).

Advantages of lambdas:

  • The function is in place, you see it together with the expression to be replaced. It is easier to understand later.
  • They are especially useful in fixes.py, where you cannot put your function directly next to its fix as you can in user-fixes.py.
  • There may be several of them within one fix, without having a whole set of functions.

I don't write the whole fix here, only a line of replacements.

Another example (combined with the choice from the first section of this page)

A mértékegységek nevét akkor is kisbetűvel írjuk, ha személyről vannak elnevezve. Sajnos elég sokan elrontják. Sokféle szó jöhet számításba, de mindegyiknél ugyanaz a feladat: kicsire cserélni a kezdőbetűt. Kár lenne mindegyikre egy sort áldozni a fixben, ezt a feladatot az isten is a lambdafüggvényekre konfigurálta. (A mértékegységet úgy próbáljuk megkülönböztetni az adott szó egyéb előfordulásától, hogy számjegyet keresünk előtte. Mellékhatásként   a számok utáni hibás Millió/Milliárd szavakat is kisbetűsíti.) Nem másolom be az egész fixet, csak a mértékegységeket javító sorát.

A m.group(1) + ' ' + m.group(2).lower() nagyon hasonlít az r'\1 \2' kifejezéshez, csak éppen a \2 csoport kisbetűs lesz (éppen ezért kezdtünk hozzá a javításhoz).

A lamdafüggvények előnyei:

  • A függvény helyben van, könnyebben áttekinthető a cserélendő kifejezéssel együtt, és könnyebben érthető.
  • Különösen kényelmes a használatuk a fixes.py-ban, ahol – szemben a user-fixes.py-jal – nem lehet a függvényt közvetlenül a fix elé írni.
  • Egy fixen belül többet is használhatsz anélkül, hogy függvények garmadáját kéne együtt kezelni.

Egy másik példa (kombinálva az első szakaszban bemutatott kétféle választással)

(ur'(\d) *(Volt|Newton|Hertz|Amper|Ohm|Pascal|Joule|Watt|Farad|Kandela|Mól|Siemens|Coulomb|Angström|Kelvin|Radián|Mérföld|Mega|Giga|Kilo|Tera|Deka|Deci|Centi|Milli|Mikro)', 
    lambda m:(m.group(1) + ' ' + m.group(2).lower())),

An advanced version / Továbbfejlesztett változat szerkesztés

In English Magyarul
New features:
  • It also detects non-breaking space between the number and the unit. If found, it is preserved, otherwise 0 or more spaces are replaced with one space. (See P<nb>)
  • Linked occurences are also found. (See P<bra>)
  • Linked text is not found before a pipe (|) character. (See P<ket>)

Side effect:

  • Missing space is put even before a linked text that is before a pipe. For example, 15[[Mikrométer (mértékegység)|μm]] → 15 [[Mikrométer (mértékegység)|μm]].

New technical elements:

  • We use named groups to avoid confusion.
  • Python's conditional expression may be used in lambdas (from Python 2.5 only). Take care of parentheses around them!

m.group(2) means the \d group here, because parentheses are numbered from the outer inwards, and, since \d is compulsory, both of them will be found.

Amivel többet tud:
  • Megtalálja a nem törhető szóközt is a szám és a mértékegység között, és meghagyja; ha nincs, akkor 0 vagy sok szóköz helyett is egyet hagy. (P<nb> csoport)
  • A linkelt szöveget is megtalálja. (P<bra> csoport)
  • Kihagyja a linkelt szöveget, ha a függőleges vonal előtt áll. (P<ket> csoport)

Mellékhatás:

  • Az utóbbi esetben is pótolja a szóközt, pl. 15[[Mikrométer (mértékegység)|μm]] → 15 [[Mikrométer (mértékegység)|μm]].

Új elemek a függvényben:

  • Névvel jelölt csoportokat használunk (nehezebb összekeverni).
  • Feltételes kifejezéseket használunk (csak Python 2.5-től használható, és figyelni kell a zárójelekre!).

A m.group(2) kifejezés a \d csoportnak felel meg, mivel a zárójelek kívülről befelé számozódnak, és (lévén a \d kötelező) mindkettőt megtalálja a regex.

(ur'((\d) *(?P<nb> )?(?P<bra>\[\[)?)(?P<unit>Volt|Newton|Hertz|Amper|Ohm|Pascal|Joule|Watt|Farad|Kandela|Mól|Siemens|Coulomb|Angström|Kelvin|Radián|Mérföld|Mega|Giga|Kilo|Tera|Deka|Deci|Centi|Milli|Mikro)(?P<ket>[^\[]*?\|.*?\]\])?',
    lambda m:m.group(2) + (' ' if m.group('nb') else ' ') + 
           ('[[' if m.group('bra') else '') + 
           (m.group('unit') + m.group('ket') if m.group('ket') else m.group('unit').lower())),