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:
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:
|
Choosing between two replacements in runtime / Két csere közötti választás futásidőben
szerkesztésIn 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ésIn 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:
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:
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ésIn 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ésIn 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:
|
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:
|
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ésIn 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ésIn 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 lambda 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:
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:
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ésIn English | Magyarul |
---|---|
New features:
Side effect:
New technical elements:
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:
Mellékhatás:
Új elemek a függvényben:
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())),