# Source code (Python) for Kotbot------------

def doros(sta,sto):
    C=F('tclog')[sta:sto+1]; TY=F('tylog'); global g_logcon
    for L in C:
        cy=row(TY,L[1],2)[1]; g_logcon=L[0]; n=nam(L[0])
        tx=wpraw('en',L[0]); iib=iibox(L,cy)
        if 'nfobox' in tx:
            log('settlement' if 'nfobox settlement'\
                in tx else envirs(tx,'nfobox',2,25))
        elif 'ategory:Communes in '+cy not in tx or 'Romania' not in tx:
            log('*wrong page? '+iib)
        elif '{{bot' in tx or '{{nob' in tx or '{{Bot' in tx or '{{Nob' in tx:
            log('*bot exclusion '+iib)
        else:
            x=sf(tx,"'''"+n)
            if x<0 or x>100 or "'''"+n in tx[x+3:]:
                log('*no place for infobox: '+iib)
            else:
                ty=tx[:x]+iibox(L,cy)+'\n'+tx[x:]
                if '<ref>' in ty and '{{reflist}}' not in ty and \
                   '<references' not in ty and '{{Reflist}}' not in ty:
                    if 'eferenc' in ty or 'xternal' in ty or 'Notes' in ty:
                        log('*cant place reflist')
                    else:
                        xx=sf(ty,('\n{{Communes','\n{{communes',
                                  '\n{{DEFAULT','\n[[Category'))
                        if xx<0:
                            log('*cant put ref section')
                        else:
                            ty=ty[:xx]+'\n==References==\n{{reflist}}\n\n'+\
                                ty[xx:]
                            log('*put ref section')
                edit('en',L[0],'Force',ty,
                     'bot adding infobox')
    return sta,sto

def iibox(L,cou):
    t=('{{Infobox settlement\n|name='+nam(L[0])+
       '\n|settlement_type='+
       ('[[Municipiu|City]]' if I(L,5)=='M' else (u9('[[Orasx|Town]]'+
            '') if I(L,5)=='O' else '[[Communes of Romania|Commune]]'))+
       '\n|total_type=&nbsp;\n|image_map=\n|map_caption='+
       '\n|subdivision_type=Country\n|subdivision_name'+
       '={{flag|Romania}}\n|subdivision_type1=[[Counties of Romania|County]]'+
       '\n|subdivision_name1=[['+cou+']]\n|population_total='+L[2])
    if L[2]!='' and L[3]!='':
        t=(t+'\n|population_as_of=2002\n|population_footnotes=<ref>'+
           '[http://recensamant.referinte.transindex.ro/?pg=3&id='+
        L[3]+' Romanian census data, 2002]; retrieved on March 1, 2010</ref>')
    y=a2l(L[4],'|')
    if len(y)==6 and y[0]!='':
        t=(t+'\n|latd='+y[0]+'|latm='+y[1]+'|lats='+y[2]+'|latNS=N'+
           '|longd='+y[3]+'|longm='+y[4]+'|longs='+y[5]+'|longEW=E'+
           '\n|pushpin_map=Romania')
    t=(t+'\n|timezone=[[Eastern European Time|EET]]|utc_offset=+2'+
       '\n|timezone_DST=[[Eastern European Summer Time|EEST]]'+
       '|utc_offset_DST=+3\n}}')
    return t
    
def absss():
    """Coords"""
    T=F('ttlog')
    for L in T:
        tx=wpraw('en',L[0])
        if 'nfobox' in tx:
            ac=''
        else:
            acm=mgp(r'\{\{(?:C|c)oord(.*)\}',tx); ac=ascoo(acm)
        if len(L)==6:
            log(L[0],L[1],L[4],L[5],ac,L[2])
        else:
            log(L+[ac,'**'])
    return 9
        

def oldddoioi():
    TY=col(F('tylog'),2)
    for L in T:
        if len(L)==6:
            if '*' in L[0] or L[1] not in TY \
               or not isnin(L[4],200,1000000) or not isnin(L[5],1,2951) or \
                     S[eval(L[5])-1][3:5]!=[L[1],L[4]]:
                log('*',L[0])
        else:
            if len(L)!=4 or L[1] not in TY or L[3]!='*0':
                log('*',L[0])
    return 9

def abcrc(sta,sto):
    """Get"""
    S=F('sulog'); SV=F('svlog'); T=F('tclog'); TY=F('tylog')
    for L in T[sta:sto+1]:
        na=nam(L[0]); cy=row(TY,L[1],1)[2]
        A=rows(S,na,1)
        B=rows(A,cy,3)
        if len(B)==0:
            A=rows(SV,J2j(rom(na)).replace('-',' '),1)
            B=rows(A,cy,3)
        if len(B)==0:
            A=rows(SV,J2j(rom(na)).replace('-',' ').replace('a',''),1)
            B=rows(A,cy,3)
        if len(B)==1:
            log(L[0],cy,'C',B[0][2],B[0][4],B[0][0])
        else:
            log(L[0],cy,'C','*'+str(len(B)))
    return 9

def achch():
    ty=F('tylog'); c=F('tclog'); s=F('sulog'); R=['']*len(ty)
    for L in c:
        i=lf(ty,L[1],1)
        if R[i]=='':
            M=row(s,L[0],2)
            R[i]=I(M,4)
    for j in range(len(ty)):
        log(ty[j][1],R[j])
    return 9        

def an2():
    g=catcont('en','Communes and villages in Romania')[0]; L=[]
    for k in g:
        if starts(k,'Communes in ') and ends(k,' County'):
            kk=k[12:]
        elif starts(k,'Localities in ') and ends(k,' County'):
            kk=k[14:]
        else:
            kk=k
        log(kk)
        h=catcont('en',k)[1]
        for y in h:
            L=L+[[y,kk]]
    a2fi(L,'blog')
    return 9

#Localities in Ilfov County (incl. 9 towns)

def an1():
    K=F('sourcelog')
    for L in K:
        t=L[2]; c='**'
        if starts(t,'Municipiul '):
            t=t[11:]; c='m'
        elif ends(t,' municipiul'):
            t=t[:-11]; c='m'
        elif starts(t,'Comuna '):
            t=t[7:]; c='c'
        elif ends(t,' comuna'):
            t=t[:-7]; c='c'
        elif ends(t,' c.'):
            t=t[:-3]; c='c'
        elif ends(t,' o.'):
            t=t[:-3]; c='o'
        elif ends(t,' ora\xc5\x9f'):
            t=t[:-6]; c='o'
        elif ends(t,' ora\xc5\x9ful'):
            t=t[:-8]; c='o'
        elif starts(t,'Ora\xc5\x9e '):
            t=t[6:]; c='o'
        elif starts(t,'Ora\xc5\x9eul '):
            t=t[8:]; c='o'
        elif starts(t,'Ora\xc5\x9ful '):
            t=t[8:]; c='o'
        elif ' '!=t:
            c='x'
        log(L[1],noex(t.replace('  ',' ')),c,L[3],L[4])
    return 9

def romget(sta,sto):
    for n in range(sta,sto+1):
        u='http://recensamant.referinte.transindex.ro/?pg=3&id='+str(n)
        t=urlread(u)
        nl=mgps('margin-top.+>(.+?)<',t)
        pl=mgps('<th>Total.+\n.+>(\d+)<',t)
        if len(nl)!=1 or len(pl)!=1 or '>' in nl[0]+pl[0]:
            log(str(n)+'>**>'+str(nl+pl))
        else:
            nj=noex(upto(nam(nl[0]),'/')); ta=tag(nl[0])
            na=replaces(nj,[('\xe2','\xc3\xa2'),
                            ('\xe3','\xc4\x83'),
                            ('\xba','\xc5\x9f'),
                            ('\xfe','\xc5\xa3'),
                            ('\xce','\xc3\x8e'),
                            ('\xaa','\xc5\x9e'),
                            ('\xde','\xc5\xa2')])
            log(str(n),na,ta,pl[0])
    return sta,sto
    
#****************
global g_uni, g_coo, g_lang, g_logfile, g_logcon
global g_user, g_pass, g_jar, g_loggedon

#Unicode characters: lowercase, uppercase, keyboard, (reduction)
g_uni=(('pl',(('\xC4\x85','\xC4\x84','a'),('\xC4\x87','\xC4\x86','c'),
             ('\xC4\x99','\xC4\x98','e'),('\xC5\x82','\xC5\x81','l'),
             ('\xC5\x84','\xC5\x83','n'),('\xC3\xB3','\xC3\x93','o'),
             ('\xC5\x9B','\xC5\x9A','s'),('\xC5\xBA','\xC5\xB9','x','z'),
             ('\xC5\xBC','\xC5\xBB','z'))),
       ('cs',(('\xc3\xa1','\xc3\x81','a'),('\xc4\x8d','\xc4\x8c','c'),
             ('\xc4\x8f','\xc4\x8e','d'),('\xc3\xa9','\xc3\x89','e'),
             ('\xc4\x9b','\xc4\x9a','f','e'),('\xc3\xad','\xc3\x8d','i'),
             ('\xc5\x88','\xc5\x87','n'),('\xc3\xb3','\xc3\x93','o'),
             ('\xc5\x99','\xc5\x98','r'),('\xc5\xa1','\xc5\xa0','s'),
             ('\xc5\xa5','\xc5\xa4','t'),('\xc3\xba','\xc3\x9a','u'),
             ('\xc5\xaf','\xc5\xae','v','u'),('\xc3\xbd','\xc3\x9d','y'),
             ('\xc5\xbe','\xc5\xbd','z'))),
       ('ro',(('\xc3\xa2','\xc3\x82','a'),('\xc4\x83','\xc4\xa3','b','a'),
             ('\xc3\xae','\xc3\x8e','i'),
             ('\xc5\x9f','\xc5\x9e','s'),
             ('\xc5\xa3','\xc5\xa2','t'))))

#Max and min degree 
g_coo=(('pl',(49,54,14,24)),('cs',(48,51,12,18)),('ro',(43,48,20,29)))
g_lang='ro'
g_logfile='blog.txt'
g_logcon=''
g_jar=0
g_loggedon=[]

def I(x,n,df=''):
    """x[n] if exists else default"""
    try:
        return x[n]
    except:
        return df

def lty(a):
    """Returns 2 if a t/list containing t/list(s), 1 if other t/l, else 0"""
    if type(a) not in (tuple,list):
        return 0
    for b in a:
        if type(b) in (tuple,list):
            return 2
    return 1

def l2s(L,sep='>'):
    """Converts list to string, > default separator"""
    os=''
    for x in L[:-1]:
        os=os+str(x)+sep
    os=os+str(I(L,-1))
    return os

def a2s(a,sep='>'):
    """Converts any to string, > and \n default seps"""
    if lty(a)<2:
        return str(a) if lty(a)==0 else l2s(a,sep)
    s=''
    for b in a:
        s=s+(str(b) if lty(b)==0 else l2s(b,sep))+'\n'
    return s

def a2p(a):
    """If list or tuple, returns tuple. Else returns 1-tuple."""
    return tuple(a) if lty(a)>0 else (a,)

def a2pp(a):
    """Makes tuple of tuples"""
    if lty(a)<2:
        return (a2p(a),)
    pp=()
    for b in a:
        pp=pp+(a2p(b),)
    return pp

def a2l(a,seps='>'):
    """Converts any to list, > default sep (can be alternative seps)"""
    if lty(a)>0:
        return list(a)
    s=str(a)
    if I(s,-1)=='\n' and '\n' in seps:
        s=s[:-1]
    if s=='' and '>' not in seps:
        return []
    l=[]; t=''
    for ch in s:
        if ch in seps:
            l.append(t); t=''
        else:
            t=t+ch
    l.append(t)
    return l

def a2ll(a,seps='>'):
    """Converts to list of lists, > and \n default separators"""
    if lty(a)>1:
        return list(a)
    LL=[]
    for k in a2l(a,'\n'):
        LL.append(a2l(k,seps))
    return LL

def reesc(st):
    """Escapes string to regex or removes initial !R"""
    import re; s=str(st)
    return s[2:] if re.match('!R',s) else re.escape(s)

def a2re(a,get=0):
    """String/tuple to regex: init. !R for no escape, get=1 for ()"""
    os='(' if get else '(?:'; p=a2p(a)
    if len(p)==1:
        return '('+reesc(p[0])+')' if get else reesc(p[0])
    for x in p:
        os=os+reesc(x)+'|'
    return os[:-1]+')'

def mgps(r,s):
    """Returns all matched groups from regex matched to s, or []"""
    ol=[]; import re
    l=re.search(r,s)
    if not l:
        return []
    for x in l.groups():
        if type(x)==str:
            ol.append(x)
    return ol

def mgp(r,s):
    """First of matched groups, or '' """
    return I(mgps(r,s),0)

def newunifile(finame,intro=''):
    """Creates unicode file with intro as first line, if file nonexistent"""
    """Returns 1 if created, else -1"""
    full=finame if '.' in finame else finame+'.txt'
    try:
        fi=open(full)
    except:
        try:
            nf=open(full,'w')
        except:
            return -1
        nf.write('\xef\xbb\xbf'+(intro if intro!='' else finame+' file')+'\n')
        nf.close()
        return 1
    fi.close()
    return -1

def fi2l(finame):
    """Gets list of lines; returns -1 if no such file"""
    L=[]; fo=finame if 'log' in finame or '/' in finame or\
       '.' in finame else 'jdata/'+finame
    full=fo if '.' in fo else fo+'.txt'
    try:
        fi=open(full)
    except:
        return -1
    for line in fi:
        L.append(line[:-1] if I(line,-1)=='\n' else line)
    fi.close()
    return L[1:]

def fi2ll(finame,seps='>'):
    """File with >-sep. lines to list of lists. -1 if no such file"""
    a=fi2l(finame)
    return -1 if a==-1 else a2ll(a,seps)

def F(*fis):
    """Multiple fi2ll"""
    j=()
    for fi in fis:
        j=j+(fi2ll(fi),)
    return j[0] if len(j)==1 else j

def fi2s(finame):
    """Converts file to single string with \n's. -1 if no such file"""
    a=fi2l(finame)
    return -1 if a==-1 else l2s(a,'\n')+'\n'

def a2fi(A,finame):
    """Writes anything to file"""
    if A=='':
        return
    full=finame if '.' in finame else finame+'.txt'
    fi=open(full,'a')
    for L in a2pp(A):
        fi.write(l2s(L)+'\n')
    fi.close()
    return 0

def log(*f):
    """Logs g_logcon plus series of items, to g_logfile"""
    op=(g_logcon,) if g_logcon else ()
    for a in f:
        op=op+(a2s(a),)
    return a2fi(op,g_logfile)

def allin(a,b):
    """?Are all chars/els of a in b"""
    for x in a:
        if x not in b:
            return False
    return True

def reps(L,M=0):
    """List of repetitions in L / things in L also in M"""
    OL=[]
    for i in range(len(L)):
        if L[i] in (M if M!=0 else L[:i]):
            OL.append(L[i])
    return OL

def replaces(st,L):
    """Does non-reiterated replaces acc. to list of pairs"""
    for ab in L:
        st=st.replace(ab[0],ab[1])
    return st

def subs(st,L):
    """Applies list of regex substitution pairs (triples)"""
    import re
    for l in L:
        st=re.sub(l[0],l[1],st,I(l,2,0))
    return st

def has(S,st,wh=0,se=''):
    """?Does S contain a(any of) st (can be !R+regex) (se for start/end)"""
    """If wh>0: +which(2=last non-overlap.)+start+stop(3=how many)"""
    import re; of=0; s=str(S); r=a2re(st,1)
    if wh>1:
        M=re.split(r,s)
        if len(M)>1:
            of=len(s)-len(M[-1])-len(M[-2]); s=s[of:]
    m=re.search(('^' if 's' in se else '')+r+('$' if 'e' in se else ''),s)
    if wh==0:
        return True if m else False
    if m:
        return True, m.group(), m.start()+of, m.end()+of if wh<3 else len(M)/2
    return False,'',-1,0

def starts(S,st,wh=0):
    """?Does S start with (any of) st (wh=1 also returns which)"""
    return has(S,st,wh,'s')

def ends(S,st,wh=0):
    """?Does S end with (any of) st (wh=1 also returns which)"""
    return has(S,st,wh,'e')

def isas(S,st,wh=0):
    """?Is S (any of) st (wh=1 also returns which)"""
    return has(S,st,wh,'se')

def spl(S,st):
    """Split: if st in S, returns triple, else S,'',''"""
    h=has(S,st,1)
    return (S[:h[2]],h[1],S[h[3]:]) if h[0] else (S,'','')

def mspl(sl,r,ch):
    """Splits 1st of strings; codes the result (code is !;(ch)(n); )"""
    import re
    m=re.split('('+r+')',sl[0]); os=''
    for n in range(len(m)):
        if n%2==0:
            os=os+m[n]
        else:
            sl.append(m[n]); os=os+'!;'+ch+str(len(sl)-1)+';'
    return [os]+sl[1:]

def subslist(sl):
    """List for subs use based on sl"""
    L=[]
    for n in range(1,len(sl)):
        L.append([r'!;[^0-9]*'+str(n)+';',sl[n]])
    return L
        
def sf(S,m,st='',fl=''):
    """Finds st in S (or rel. index m), fl=j,l,1. -1 if m=-1 or not found"""
    if type(m)!=int:
        fl=st; st=m; m=0
    if m<0:
        return (-1,'') if '1' in fl else -1
    K=has(S[m:],st,(2 if 'l' in fl else 1))
    if not K[0]:
        return (-1,'') if '1' in fl else -1
    i=K[3] if 'j' in fl else K[2]
    return (i+m,K[1]) if '1' in fl else i+m

def nin(S,st):
    """How many of (non-overlapping) (any of) st in S"""
    N=0
    if type(S)!=str:
        for s in S:
            if isas(s,st):
                N=N+1
        return N
    return has(S,st,3)[3]

def compare(a,b):
    """If a, b not identical, returns index and differing items""" 
    for n in range(max(len(a),len(b))):
        if I(a,n)!=I(b,n):
            return n,I(a,n),I(b,n)
    return -1,'',''

def envirs(S,st,back,forth):
    """Returns all environments (back and forth define how big) of st in S"""
    L=[]
    for i in range(len(S)):
        si,wh=starts(S[i:],st,1)[:2]
        if si:
            L.append(S[max(0,i-back):i+len(wh)+forth])
    return a2s(L).replace('\n','&')

def per(ST,PARM,THIS):
    """Applies st (column of THIS, -1 gives PARM; or eval string)"""
    if ST==-1:
        return PARM
    if ST==-2:
        return THIS
    if type(ST)==int:
        return I(a2p(THIS),ST)
    return eval(ST.replace('!!','THIS').replace('!%','PARM'))
                
def rows(L,st,col=0,rcols=-2,ret=-1):
    """Gets rcols (-2=all, -1=ind.) of ret (-1=all,0=NOT) rows: st match col"""
    """col can be eval string. Single rcol will debracket"""
    OL=[]; n=0
    while n<len(L) and (ret<1 or len(OL)<ret):
        K=L[n]
        if isas(per(col,n,K),st)==(ret!=0):
            O=[]
            for c in a2p(rcols):
                O=O+(list(a2p(K)) if c==-2 else [per(c,n,K)])
            OL.append(O[0] if type(rcols)==int and rcols>-2 or
                      type(L[n]) not in (list,tuple) and rcols==-2 else O)
        n=n+1
    return OL

def row(L,st,col=0,rcols=-2):
    """First of rows, or []"""
    return I(rows(L,st,col,rcols,1),0,[])

def lf(L,st,col=0):
    """Index of first of rows, or -1"""
    return I(rows(L,st,col,-1,1),0,-1)

def col(L,col):
    """Column(s) from L"""
    return rows(L,'a','"a"',col)

def j2J(st,no=0):
    """All to uppercase; no=1 means only initial"""
    inil=1
    L=[('a','A'),('b','B'),('c','C'),('d','D'),('e','E'),('f','F'),('g','G'),
       ('h','H'),('i','I'),('j','J'),('k','K'),('l','L'),('m','M'),('n','N'),
       ('o','O'),('p','P'),('q','Q'),('r','R'),('s','S'),('t','T'),('u','U'),
       ('v','V'),('w','W'),('x','X'),('y','Y'),('z','Z')]
    for k in g_uni:
        for a in k[1]:
            L.append((a[0],a[1]))
            inil=len(a[0]) if st[:len(a[0])]==a[0] else inil
    if no==1:
        return replaces(st[:inil],L)+st[inil:]
    return replaces(st,L)

def st2St(st):
    """Capitalises first character of string"""
    return j2J(st,1)
            
def J2j(st,no=0):
    """All to lower case; no=1 means only initial"""
    inil=1
    L=[('A','a'),('B','b'),('C','c'),('D','d'),('E','e'),('F','f'),('G','g'),
       ('H','h'),('I','i'),('J','j'),('K','k'),('L','l'),('M','m'),('N','n'),
       ('O','o'),('P','p'),('Q','q'),('R','r'),('S','s'),('T','t'),('U','u'),
       ('V','v'),('W','w'),('X','x'),('Y','y'),('Z','z')]
    for k in g_uni:
        for a in k[1]:
            L.append((a[1],a[0]))
            inil=len(a[1]) if st[:len(a[1])]==a[1] else inil
    if no==1:
        return replaces(st[:inil],L)+st[inil:]
    return replaces(st,L)

def St2st(st):
    """Makes first char. lower case"""
    return J2j(st,1)

def U(st,la=''):
    """From x+ format to Unicode"""
    la=la if la!='' else g_lang; d=row(g_uni,la,0,1)
    for a in d:
        st=st.replace(a[2]+'+',a[0]).replace(j2J(a[2])+'+',a[1])
    return st

def rom(st,la=''):
    """Unicode to reduced format"""
    la=la if la!='' else g_lang; d=row(g_uni,la,0,1)
    for a in d:
        r=I(a,3,a[2])
        st=st.replace(a[0],r).replace(a[1],j2J(r))
    return st  

def rplus(st,la=''):
    """Unicode to x+ format"""
    la=la if la!='' else g_lang; d=row(g_uni,la,0,1)
    for a in d:
        st=st.replace(a[0],a[2]+'+').replace(a[1],j2J(a[2])+'+')
    return st  

def notrail(s,a):
    """Removes (any of) a from end of string, iteratively"""
    t=''; import re
    while ends(s,a) and t!=s:
        t=s; s=re.sub(a2re(a)+'$','',s)
    return s
    
def noinit(s,a):
    """Removes (any of) a from start of string, iteratively"""
    t=''; import re
    while starts(s,a) and t!=s:
        t=s; s=re.sub('^'+a2re(a),'',s)
    return s

def noex(st):
    """Removes spaces and newlines from start and end"""
    return notrail(noinit(st,(' ','\n')),(' ','\n'))

def repblank(t):
    """Removes double blank lines from t"""
    while '\n\n\n' in t:
        t=t.replace('\n\n\n','\n\n')
    return t

def upto(s,a):
    """If s contains (any of) a, returns noex of what goes before (else s)"""
    return noex(spl(s,a)[0])

def after(s,a):
    """If s contains a, returns what comes after (else '')"""
    return spl(s,a)[2]

def insbef(s,a,t,T0='',T2=''):
    """puts t before a in s, T0/T2 logged if not found/found twice"""
    K=has(s,a,1)
    if not K[0]:
        log(T0) if T0!='' else ''; return s
    if has(s[K[3]:],a):
        log(T2) if T2!='' else ''
    return s[:K[2]]+t+s[K[2]:]

def insaft(s,a,t,T0='',T2=''):
    """puts t after a in s, T0/T2 logged if not found/found twice"""
    K=has(s,a,1)
    if not K[0]:
        log(T0) if T0!='' else ''; return s
    if has(s[K[3]:],a):
        log(T2) if T2!='' else ''
    return s[:K[3]]+t+s[K[3]:]

def l2txt(L,sep=', ',lsep=', and ',osep=' and ',do=0):
    """Makes a text list. Normal separator, last, only, operation on items"""
    os=''; k=len(L)
    for n in range(k):
        os=os+per(do,n,L[n])+\
            (osep if n==0 and k==2 else (lsep if n==k-2 else (sep if
                                                            n<k-1 else '')))
    return os

def isn(st):
    """Is st a pure integer?"""
    return isas(st,r'!R0|\-?[1-9][0-9]*') 

def isnin(st,a,b):
    """Is st a pure int in the range a to b?"""
    return isn(st) and eval(st)>=a and eval(st)<=b

def isx(st):
    """Is st a pure English int or decimal?"""
    return isas(st,r'!R\-?(0|[1-9][0-9]*)(\.[0-9]+)?')

def no0(s):
    """Removes redundant initial zero from number"""
    return s[1:] if I(s,0)=='0' and I(s,1,'X') in '0123456789' else s

def nco(s):
    """Puts commmas into English number string"""
    z=spl(s,'.'); import re
    return re.sub(r'(?<=[0-9])([0-9]{3})(?=([0-9]{3})*$)',r',\1',
                  z[0])+z[1]+z[2]

def sround(xs,n=0):
    """Rounds a string number to n dec places, 0 gives an int"""
    z=spl(xs,'.')
    if not isx(xs) or len(z[2])<=n:
        return xs
    if z[2][n] in '01234':
        az='0' if z[0]=='-0' and z[2][:n]=='0'*n else z[0]
        return az+('.' +z[2][:n] if n>0 else '')
    zz=str(eval('1'+z[2][:n])+1)
    if zz[0]=='1':
        return z[0]+('.'+zz[1:] if n>0 else '')
    return str(eval(z[0])+(-1 if z[0][0]=='-' else 1))+\
           ('.'+zz[1:] if n>0 else '')

def nword(nn):
    """Converts to a word if integer 1-9, else just to a string"""
    n=eval(nn) if type(nn)==str and isn(nn) else nn
    if n in range(1,10):
        return ['one','two','three','four','five','six','seven','eight',
                    'nine'][n-1]
    return str(n)
    
def ifpl(n,plst='s',sst=''):
    """returns s or other pl if n not 1, else empty or other sing"""
    return sst if n==1 else plst

def less(x,y):
    """?Is x less than y (nos. by value then strings wo diacritics)"""
    x=eval(x) if type(x)==str and isx(x) else x
    y=eval(y) if type(y)==str and isx(y) else y
    if type(y)==str and type(x)!=str:
        return True
    if type(x)==str and type(y)!=str:
        return False
    if type(y)!=str:
        return x<y
    a=rom(x); b=rom(y); c=J2j(a); d=J2j(b)
    if a==b:
        return x<y
    if c==d:
        return a<b
    return c<d

def lless(p,q):
    """?Is list p less than q"""
    p=a2p(p); q=a2p(q)
    for n in range(max(len(p),len(q))):
        if n==len(p):
            return True
        if n==len(q):
            return False
        if less(p[n],q[n]):
            return True
        if less(q[n],p[n]):
            return False
    return False

def rless(p,q,col=-2,m=0,n=0):
    """?Is p less than q by col (m and n: params if col an eval str"""
    return lless(per(col,m,p),per(col,n,q))

def ins(L,p,col=-2,cp=-2):
    """Inserts p (changed per cp) in list, in order defined by col"""
    m=0; n=len(L); p=per(cp,0,p)
    while m!=n:
        h=(m+n)/2
        if rless(p,L[h],col):
            n=h
        else:
            m=h+1
    return L[:n]+[p]+L[n:]

def sort(L,col=-2,cp=-2,topn=0):
    """Returns cp-ed list of lists/tuples sorted by columns cs (top n only)"""
    OL=[]; n=len(L) if topn==0 else topn
    for p in L:
        OL=ins(OL,p,col)[:n]
    return OL

def getnos(s,decpts='.,'): #must be some decpts
    """Returns normed nos; number blocks; intervening blocks (ie 1 more)"""
    import re
    t=re.split(r'((?:[0-9]|['+reesc(decpts)+'](?=[0-9]))+)',s)
    p=rows(t,'1','str(!%%2)'); q=rows(t,'0','str(!%%2)'); k=[]
    for x in p:
        x=no0(x.replace(',','.'))
        if isx(x):
            k.append(x)
    return k,p,q

def normno(st,u,v,U,V,tolog=''):
    """norms a number string (or ''), w warning + error limits"""
    s=replaces(st,[('&nbsp;',' '),("'",' '),(' ,',' ;'),(' ','')])
    k=sf(s,('(','<','{','['))
    if k>0:
        s=noex(s[:k]); log('*'+tolog+' from '+st+' to: '+s)
    if s=='':
        return ''
    k=sf(s,',')
    if k>0:
        if '.' in s:
            s=s[:k].replace('.','')+'.'+s[k+1:]
            log('*'+tolog+' from '+st+' to: '+s)
        else:
            s=s[:k]+'.'+s[k+1:]
    if not isx(s):
        log('**'+tolog+' NOT NUMERICAL: '+st); return ''
    d=eval(s)
    if d<U or d>V:
        log('**'+tolog+' OUT OF RANGE: '+s+' ('+str(U)+','+str(V)+')')
        return ''
    if d<u or d>v:
        log('*'+tolog+' outside range?: '+s+' ('+str(u)+','+str(v)+')')
    if str(d)!=s:
        log('*'+tolog+' from: '+st+' got '+s)
    return s

def ascoo(s):
    """Converts string/list to six-coo string, or err/warn"""
    p=getnos(a2s(upto(s,('E|','source'))))[0]; g=row(g_coo,g_lang,0,1)
    if len(p)==2 and '.' in p[0] and '.' in p[1]:
            x=eval(p[0]); m=int(x); mm=int((x-m)*60.0+0.5)
            y=eval(p[1]); n=int(y); nn=int((y-n)*60.0+0.5)
            r=[str(m),str(mm),'',str(n),str(nn),'']
    elif len(p)==4 and '.' not in p[0]+p[2]:
        r=[str(p[0]),sround(str(p[1])),'',str(p[2]),sround(str(p[3])),'']
    elif len(p)==6 and '.' not in p[0]+p[1]+p[3]+p[4]:
        r=[str(p[0]),str(p[1]),sround(str(p[2])),
           str(p[3]),str(p[4]),sround(str(p[5]))]
    else:
        #log('**no coords from: '+s)
        return '|||||'
    for i in (2,5,1,4):
        if r[i]=='60':
            #log('*60 in coords: '+s)
            r[i]='0'; r[i-1]=str(eval(r[i-1])+1)
    if eval(r[0]) not in range(g[0],g[1]+1) or \
       eval(r[3]) not in range(g[2],g[3]+1) or \
       eval(r[1]) not in range(0,60) or eval(r[4]) not in range(0,60) or \
       r[2]!='' and eval(r[2]) not in range(0,60) or \
       r[5]!='' and eval(r[5]) not in range(0,60):
        log('**strange coords from: '+s)
        return '|||||'
    return a2s(r,'|')

def coo2xy(c):
    """Pipe-sep coor string to decimals or 0,0"""
    d=[]
    for k in a2l(c,'|'):
        d.append(0 if k=='' else eval(k))
    return d[0]+d[1]/60.0+d[2]/3600.0,d[3]+d[4]/60.0+d[5]/3600.0

def kmdir(x,y,X,Y):
    """Returns distance and direction of (x,y) from (X,Y) (within Poland)"""
    import math    
    vx=(x-X)*110.946
    vy=(y-Y)*111.319*math.cos(math.radians((x+X)/2))
    d=int(math.sqrt(vx*vx+vy*vy))+1
    if vx>2.42*abs(vy):
	pt='north'
    elif -vx>2.42*abs(vy):
	pt='south'
    elif vy>2.42*abs(vx):
	pt='east'
    elif -vy>2.42*abs(vx):
	pt='west'
    else:
	pta='north' if vx>0 else 'south'
	ptb='east' if vy>0 else 'west'
	pt=pta+'-'+ptb
    return d,pt

def km(c,d):
    """Distance between coord | strings"""
    x,y=coo2xy(c); X,Y=coo2xy(d)
    return kmdir(x,y,X,Y)[0]

def kmtxt(dc,dcb,u,v,U,V,abbr,tolog):
    """Makes text e.g. 8 km north, of dc from dcb (warning/error limits)"""
    x,y=coo2xy(dc); X,Y=coo2xy(dcb)
    d,pt=kmdir(x,y,X,Y)
    if d<U or d>V:
        log('**'+tolog+' DIST REJECTED: '+str(d)+' ('+str(U)+','+str(V)+')')
        return ''
    if d<u or d>v:
        log('*'+tolog+' distance wrong?: '+str(d)+' ('+str(u)+','+str(v)+')')
    return '{{convert|'+str(d)+'|km|mi|0'+('|abbr=on' if abbr else '')+'}} '+pt

def loctxts(name,c,*p):
    """Full text: name, coords, list of coo/name/text/uvUV/pri 8s (V2 16s)"""
    L=[]; M=[]; N=[]
    for x in p:
        if len(x)==16:
            A1=km(c,x[0]); A2=km(c,x[8])
            A3=km(x[0],x[8]); Ah=max(A1,A2); Ab=min(A1,A2)
            X1,X2=(x[:8],x[8:]) if Ab==A1 else (x[8:],x[:8])
            L=L+[X1] if 2*(Ah*Ah-Ab*Ab)>A3*A3 else L+[X1,X2]
        else:
            L.append(x)
    for K in L:
        if list(K)==sort(rows(L,K[1],1),7)[-1] and K[1]!=name:
            M.append(K)
    for K in M:
        t=kmtxt(c,K[0],K[3],K[4],K[5],K[6],K!=M[0],K[7])
        if t=='':
            return ''
        if not starts(t,'{{convert|1|'):
            N.append(t+' of '+K[2])
        else:
            log('*omitted: '+t+' of '+K[2])
    returnstr='approximately '+l2txt(N)
    return returnstr if len(N)>0 else ''

def st2date(s):
    """Converts string to proper date"""
    w=getnos(s,'@')[0]
    if len(w)==1 and isn(w[0]) and eval(w[0]) in range (2000,2010):
        return w[0]
    if len(w)==3 and isn(w[2]) and eval(w[2]) in range (2000,2010) and\
        isn(w[1]) and eval(w[1]) in range(1,13) and\
       isn(w[0]) and eval(w[0]) in range(1,30 if w[1]=='2' else (31 if
                    w[1] in ('4','6','9','11') else 32)):
        return w[0]+'&nbsp;'+('January','February','March','April','May','June',
                         'July','August','September','October','November',
                         'December')[eval(w[1])-1]+' '+w[2]
    return ''
    
def urlread(url,tries=50):
    """Gets url text"""
    import urllib
    n=0
    while n<tries:
        try:
            ur=urllib.urlopen(url)
            r=ur.read(); ur.close()
            return r
        except:
            n=n+1
    return 0/0

def wpraw(la,Art):
    return urlread('http://'+la+'.wikipedia.org/w/index.php?title='
                              +Art.replace(' ','_')+'&action=raw')

def wcraw(Art):
    return urlread('http://commons.wikimedia.org/w/index.php?title='+
                      Art.replace(' ','_')+'&action=raw')

def catcont(la,cat):
    """Returns subcats, members, subckeys, membkeys, sdates, mdates (->500)"""
    import re; login(la); SC=[]; M=[]; SCK=[]; MK=[]; SCD=[]; MD=[]
    xm=urlread('http://'+la+'.wikipedia.org/w/api.php?cmtitle=Category:'
        +cat.replace(' ','_')+'&action=query&list=categorymembers'+
        '&cmlimit=500&cmprop=title|sortkey|timestamp')
    rg=r'title=\&quot;(.*?)\&quot;.*?sortkey=\&quot;(.*?)\&quot;.*?'+\
        r'timestamp=\&quot;(.*?)\&quot;'
    for m in re.findall(rg,xm):
        if starts(m[0],'Category:'):
            SC.append(m[0][9:]); SCK.append(m[1]); SCD.append(m[2])
        else:
            M.append(m[0]); MK.append(m[1]); MD.append(m[2])
    return SC,M,SCK,MK,SCD,MD

def fullcatcont(la,cat,d=5):
    """Returns all pages below a cat to a depth of d, +cats they're in"""
    cs=[[cat,'0']]; ps=[]
    for k in range(d+1):
        for c in rows(cs,str(k),1,0):
            a,b,w,x,y,z=catcont(la,c)
            for p in b:
                u=lf(ps,p,0)
                if u<0:
                    ps.append([p,c])
                else:
                    ps[u].append(c)
            for s in a:
                if s not in col(cs,0):
                    if k<d:
                        cs.append([s,str(k+1)])
                    else:
                        ps.append(['Category:'+s,c])
    log([[cat,d]]+ps)
    return

def coca():
    """Investigate unmatched categories"""
    c=F('colog')[0]
    for a in c:
        x=catsin(wpraw('en',a)); y=catsin(wpraw('en','Category:'+a))
        os=''; ns=''
        for p in y:
            if p not in x:
                if 's ' in p+' ':
                    os=os+'[[Category:'+p+']]\n'
                else:
                    ns=ns+p+','
        log('\n'+a+' ('+ns+')\n'+os)
    return 9

def isim(name):
    """?Is ready-trimmed name an image in commons or wp"""
    if '.' not in name[1:-1] or sf(name,list('[]{}<>#|'))>-1:
        return False
    return wcraw('Image:'+name)!='' or wpraw('en','Image:'+name)!=''

def IFim(name):
    """Name if image (or Image: etc.), else empty"""
    if isim(name):
        return name
    if isim(after(name,':')):
        return after(name,':')
    return ''

def nocomm(text):
    """Removes comments from wikipedia text"""
    return subs(text,[(r'\<!--(?:.|\n)*?--\>','')])

def nam(st):
    """Main name part of string, less tag"""
    return upto(st,(',','('))

def tag(st):
    """Tag after comma or in brackets"""
    return mgp(r', (.*)|\((.*)\)',st)

def essnam(st):
    """Removes generic part of name"""
    p=(' ','Gmina','Wojew\xC3\xB3dztwo','Powiat','Park Narodowy',
         'Park Krajobrazowy','Okres')
    q=(' ','County','Voivodeship','National Park','Landscape Park',
         'Park Narodowy','Park Krajobrazowy','District','Region',' kraj')
    return notrail(noinit(nam(st),p),q)

def wpl(st):
    """Make WP (piped) link, not displaying the tag"""
    name=nam(st)
    return '[['+st+']]' if name==st else '[['+st+'|'+name+']]'

def essl(st):
    """Make WP (piped) link, not displaying the tag"""
    name=essnam(st)
    return '[['+st+']]' if name==st else '[['+st+'|'+name+']]'

def Title(na):
    """Makes WP title, w capital and no underscores"""
    return st2St(noex(na.replace('_',' ')))

def targlabel(link):
    """The _T_arget and display text of first wp link (removes extr spaces)"""
    m=mgps(r'\[\[(.+?)(?:\|(.*?))?\]\]',link)
    if len(m)==0:
        return '',link
    return Title(m[0]),I(m,1,m[0])

def targ(link):
    return targdisp(link)[0]

def label(link):
    return targdisp(link)[1]

def redtarg(txt):
    """redirect target in txt or empty string"""
    m=mgp(r'^[ \n]*\#(?:REDIRECT|redirect|Redirect)[ ]*(\[\[.*\]\])',nocomm(txt))
    return I(m,0)

def targpars(t):
    """Name and list of parameter pairs for a template call"""
    import re; m=re.split('\|',subs(t,[('^\{\{',''),('\}\}$','')])); L=[]; n=1
    if len(m)==0:
        return '',[]
    for x in m[1:]:
        mm=mgps(r'^(?:[ \n]*(.+?)[ \n]*\=)?[ \n]*((?:.|\n)*?)[ \n]*$',x)
        a,b=(mm[0],mm[1]) if len(mm)==2 else (str(n),I(mm,0))
        n=n if len(mm)==2 else n+1
        L=rows(L,a,0,-2,0)+[[a,b]]
    return Title(m[0]),L

def imnam(st):
    """Returns image filename only"""
    st2=noinit(st,(' ','[','Image:','image:','grafika:','Grafika:'))
    st3=noex(upto(st2,('|',']')))
    return st3 if '.' in st3 else ''

def esctext(tx):
    """Converts article text to string structure"""
    sl=[tx]
    sl=mspl(sl,r'\[\[[^\|\[\]\<\>\{\}\n]+(?:\|.*?)?\]\]','\n')
    sl=mspl(sl,r'\[\[(?:Image|image|Grafika|grafika)\:[^\|\[\]\<\>\{\}\n]+'+\
            r'(?:\|(?:.|(?<=!;)\n)*?)?\]\]','\n\n')
    sl=mspl(sl,r'\{\{[ \n]*[^\|\[\]\<\>\{\}\n]+?[ \n]*(?:\|[^\{\}]*?)?\}\}','')
    sl=mspl(sl,r'\{\{[ \n]*[^\|\[\]\<\>\{\}\n]+?[ \n]*(?:\|(?:.|\n)*?)?\}\}','')
    return sl

def ansl(sl):
    """Gets lists of links/templates/f values from string structure"""
    LL=[]; LT=[]; LF=[]; u=subslist(sl)
    for s in sl[1:]:
        if I(s,0)=='[':
            Q=targlabel(s); LL.append((subs(Q[0],u),subs(Q[1],u),subs(s,u)))
        else:
            Q,QL=targpars(s); LT.append((Q,subs(s,u)))
            for p in QL:
                LF.append((Q,p[0],subs(p[1],u)))
    return LL,LT,LF

def ant(t):
    """Links (targ,lab,tx), templates (targ,tx), fvals (tem,f,val,tx)"""
    return ansl(esctext(t))

def catsin(tx):
    """All categories explicitly in tx"""
    k=sf(tx,('[[Category:','[[category:'))
    if k<0:
        return []
    t=tx[max(0,k-10):]; L=[]
    for a in ant(t)[0]:
        if starts(a[0],'Category:'):
            L.append(a[0][9:])
    return L
    
def iwto(tx,la):
    """Target(s$) of iwlink to language la in tx, else empty"""
    k=sf(tx,'[['+la+':')
    if k<0:
        return ''
    t=tx[max(0,k-10):]
    return I(row(ant(t)[0],'!R'+st2St(la)+':.+',0),0)[3:]

def fv(tx,tem,*f):
    """Named field values"""
    L=[]; fl=rows(ant(tx)[2],tem,0)
    for x in f:
        L.append(I(row(fl,x,1),2))
    return L[0] if len(L)==1 else L

def IPA(st):
    """converts Polish string to IPA representation"""
    import re; s=' '+rplus(st).replace('-',' ')+' '
    if not re.match(r'( [A-PR-UWXZ]?[a-pr-uwxyz\+]+)+ $',s):
        return ''
    s=subs(J2j(s),[(' w ',' w'),(' z ',' z'),('ch','h'),(r'o\+','u'),
    ('ia','Ja'),('ie','Je'),('io','Jo'),('iu','Ju'),
    ('ai','aj'),('ei','ej'),('oi','oj'),('ui','uj'),('au','aL'),
    (r'sJ|s\+|s(?=i)','S'),(r'zJ|x\+|z(?=i)','X'),(r'cJ|c\+|c(?=i)','C'),
    (r'nJ|n\+','N'),(r'z\+','Z'),(r'l\+','L'),(r'a\+','o9'),(r'e\+','e9'),
    ('dZ','B'),('dX','D'),('dz','0'),('sz','T'),('cz','1'),('rz','R'),
    ('9(?=[bp])','m'),('9(?=[tcC1dBD0gk])','n'),('9l','l'),
    ('(?<=[aeiouy])L(?![aeouy])','U'),('(?<=[aeiouy])j(?![aeou])','I'),
                   ('ji|ii','i')])
    L=[]
    for n in range(11):
        L.append(('bd0BDg'[n]+'(?=[ c1CfhkpstTS4])','ptc1Ck'[n]) if n<6 else\
         ('wzZXR'[n-6]+'(?=[ c1CfhkpstTS4])|(?<=[c1CfhkpstTS4])'+'wzZXR'[n-6],
                 'fsTS4'[n-6]))
    for n in range(3):
        s=subs(s,[(' nad ',' na8d')]+L+[('8d','d '),('8t','t ')])
    s=subs(s,[('(((?<= )[^aeiouy ]*|[^aeiouy ]?)([aeiouy][^aeiouy ]*){1,2} )',
               r"'\1"),("([bdfghkpsSTtwzZX])'(?=[rR4])",r"'\1"),
              ("([bfghkpsSTwzZX])'(?=[lL])",r"'\1"),
              ("([^aeiouy ])'(?=[J])",r"'\1")])
    s=s.replace("'",'') if nin(s,"'")==1 and nin(s,list('aeiouy'))<2 else s
    s=subs(s,[("'nad ",",nad "),("'nat ",",nat "),('(?<=[SXCDN])e','E'),
        ("n(?='?[kg])",'5'),('(?<=[^kg])J','j'),('o9','A'),('e9|E9','Y')])
    mhl=[(' ','-'),('A',U('a+')),('Y',U('e+')),('B',U('dz+')),('C',U('c+')),
         ('D',U('dx+')),('E','e' if 'e' in s else 'e1'),('L',U('l+')),
         ('N',U('n+')),('R',U('z+')),('S',U('s+')),('T','sz'),('X',U('x+')),
         ('Z',U('z+')),('0','dz'),('1','cz'),('4','sz'),('5','N')]
    os='{{IPAc-pl|[]|'
    for n in range(1,len(s)-1):
        os=os+I(row(mhl,s[n],0),1,s[n])+'|'
        if n%28==0 and n<len(s)-2:
            os=os[:-1].replace(']','-')+'}}{{IPAc-pl|-]|'
    return os[:-1].replace('[]|','')+'}}'

def countystub(p):
    pp=p if essnam(p) in ('Opole','Lublin') else essnam(p)
    return pp.replace(' ','')+'-geo-stub'

def skey(st):
    """Makes standard category sort key (returns empty if comes to defa)"""
    s=rom(st); os=''; import re
    for a in re.split(r'[^a-zA-Z]+',s):
        os=os+j2J(I(a,0))+J2j(a[1:])+' '
    return noex(os)

def expandtem(s):
    """Expands s"""
    ur0='http://en.wikipedia.org/wiki/Special:ExpandTemplates'
    ht=doreq(ur0,[('contexttitle',''),('input',s),
                  ('removecomments','1'),('generate_xml','0')])
    return mup(mgp(r'id\="output".*\>((?:[ .]*?)\</textarea\>',ht))

#
# BOT
#

def Ne(x,y):
    return x==y or x==y+'\n' or y==x+'\n'

def formv(form,*ns):
    """Find the given value of a named input in an HTML form"""
    L=[]
    for nam in ns:
        L.append(mgp(r'\<[^\>]*(?:name\="'+nam+r'"[^\>]*value\="(.*?)"'+\
                     r'|value\="(.*?)"[^\>]*name\="'+nam+r'")',form))
    return L[0] if len(L)==1 else L       

def mup(html):
    """Marks up &#ref and &ref; chars. in an html string without tags"""
    global MuPsTr; MuPsTr=['']   #output string as 1-list
    import sgmllib
    pa=sgmllib.SGMLParser()
    pa.handle_data=writetoMuPsTr #how to handle input
    pa.feed(html)                #read and handle input
    pa.close()
    return MuPsTr[0]

def writetoMuPsTr(s):
    """Appends a string to sole element of the global list MuPsTr"""
    """Used in mup() as the data handler"""
    MuPsTr[0]=MuPsTr[0]+s
    return

def doreq(url,dl=''):
    """Makes an HTTP request and sends it; supplies and extracts cookies"""
    """Returns the response html content, or 'KBerrKB on failure"""
    """dl is the optional data list (of key/value pairs) for a POST"""
    """The global cookie jar is botjar"""
    import urllib, urllib2, cookielib
    req=urllib2.Request(url)                    #make Request object
    g_jar.add_cookie_header(req)               #add relevant cookies
    req.add_header('User-agent','Agent.Kotbot') #add User Agent header
    if dl!='':
        req.add_data(urllib.urlencode(dl))     #add data (changes type to POST)
    n=0
    while n<50:
        try:
            u=urllib2.urlopen(req)                 #send the request
            g_jar.extract_cookies(u,req)       #extract cookies from response
            html=u.read()                       #get content from response
            u.close()
            return html
        except:                                     #if error, retry
            n=n+1
    return 'KBerrKB'

def php(la):
    """Returns main part of generic php access string to Wikipedia"""
    return 'http://'+la+'.wikipedia.org/w/index.php?title='

def login(la):
    """Initializes global cookiejar, and logs in as the bot"""
    """Languages (las...) can include 'en' and/or 'pl'"""
    import cookielib; global g_jar
    if  g_jar==0:
        g_jar=cookielib.CookieJar()            #makes global cookie jar
    if la not in g_loggedon:
        ur0=php(la)+'Special:Userlogin'
        doreq(ur0)                                    #get login page
        ur1=ur0+'&action=submitlogin&type=login'
        dl1=[('wpName',g_user),('wpPassword',g_pass), 
             ('wpRemember','0'),('wpLoginattempt','Log+in')]
        rt=doreq(ur1,dl1)                        #submit form
        if rt=='KBerrKB':
            log('*FAILEDTOLOGON: '+la)
        else:
            a2fi('\nLOGGEDON: '+la,'botlog'); g_loggedon.append(la)
    return

def edit(la,Art,instr,*params):
    """Edits Wikipedia page according to instruction (and parameters)"""
    """   la is the language code, Art is the page name"""
    """   instr is the instruction (the function 'edit_'instr will be called)"""
    """Confirms if edit made (adds result to return message)"""
    """Botlogs la:Art:message, returns message and any output"""
    global g_logcon; g_logcon=la+':'+Art
    login(la); ur0=php(la)+Art.replace(' ','_')+'&action=edit' #edit page url
    html=doreq(ur0)                        #get edit page
    if html=='KBerrKB':
        log('*NETWORKFAULT1'); return
    if g_user not in html:
        log('*NOTLOGGEDON'); return
    m=mgps(r'name\=\"wpTextbox1\".*?\>((?:.|\n)*?)\<\/textarea\>',html)
    if len(m)==0:
        log('*BADEDITPAGE'); return
    old=mup(m[0])        #existing text or empty string
    FUNC=eval('edit_'+instr)            #function to be called
 #now call the function and get new text, edit summary, message and any output
    newtext,summ,mess=FUNC(la,Art,old,*params)
    if newtext=='':                     #end if no new text provided
        log(mess) if mess!='' else ''; return
    if Ne(newtext,old):
        log('*NEWTEXTSAMEASOLD'); return
 #now make data list (lp) for POST request
    lp=[]; n=sf(html,'id="editform"')  #find edit form
    for a in ['wpSection','wpStarttime','wpEdittime','wpScrolltop',
              'wpEditToken','wpAutoSummary']:
        val=formv(html[n:],a)            #find each named value on form
        lp=lp+[(a,val)]                   #add key/value pair to list
    lp=lp[:4]+[('wpTextbox1',newtext),('wpSummary',summ),
               ('wpSave','Save page')]+lp[4:]
 #now submit the form
    ur1=php(la)+Art.replace(' ','_')+'&action=submit'         #make the url
    htmr=doreq(ur1,lp)                   #submit the POST request, get response
    if htmr=='KBerrKB':
        log('*NETWORKFAULT2'); return
 #check if the edit was successful
    now=wpraw(la,Art)                          #this is the page text now
    conf='[OK]' if Ne(now,newtext) else '[*NOCONFIRM]'
    log(mess,conf) if '*' in mess+conf else a2fi((la,Art,mess,conf),'botlog')
    return 

def qedit(la,Art,instr,*params):
    """As edit, but starts by reading raw"""
    global g_logcon; g_logcon=la+':'+Art
    exi=wpraw(la,Art); FUNC=eval('edit_'+instr)
    newtext,summ,mess=FUNC(la,Art,exi,*params)
    if newtext=='':                     #end if no new text provided
        log(mess) if mess!='' else ''; return
    if Ne(newtext,exi):
        log('*NEWTEXTSAMEASOLD'); return
    login(la); ur0=php(la)+Art.replace(' ','_')+'&action=edit' #edit page url
    html=doreq(ur0)                        #get edit page
    if html=='KBerrKB':
        log('*NETWORKFAULT1'); return
    if g_user not in html:
        log('*NOTLOGGEDON'); return
    m=mgps(r'name\=\"wpTextbox1\".*?\>((?:.|\n)*?)\<\/textarea\>',html)
    if len(m)==0:
        log('*BADEDITPAGE'); return
    old=mup(m[0])                 #get and mark up content of text area
    if not Ne(old,exi):                      
        log('*UNSTABLEPAGE'); return
 #now make data list (lp) for POST request
    lp=[]; n=sf(html,'id="editform"')  #find edit form
    for a in ['wpSection','wpStarttime','wpEdittime','wpScrolltop',
              'wpEditToken','wpAutoSummary']:
        val=formv(html[n:],a)            #find each named value on form
        lp=lp+[(a,val)]                   #add key/value pair to list
    lp=lp[:4]+[('wpTextbox1',newtext),('wpSummary',summ),
               ('wpSave','Save page')]+lp[4:]
 #now submit the form
    ur1=php(la)+Art.replace(' ','_')+'&action=submit'         #make the url
    htmr=doreq(ur1,lp)                   #submit the POST request, get response
    if htmr=='KBerrKB':
        log('*NETWORKFAULT2'); return
 #check if the edit was successful
    now=wpraw(la,Art)                          #this is the page text now
    conf='[OK]' if Ne(now,newtext) else '[*NOCONFIRM]'
    log(mess,conf) if '*' in mess+conf else a2fi((la,Art,mess,conf),'botlog')
    return 

#
# BOT EDIT FUNCTIONS
# Each edit_xx function must take as arguments:
#  la (lang. code), Art (page name), old (old page text or empty), params...
# It must return:
#  new page text (empty=no edit), edit summary, b(ot)log message
# 

def edit_New(la,Art,old,text,summ='bot creating page',suppr='!DUMMY!'):
    """text, summary, list to suppress logging of text if art found"""
    if not Ne(old,''):
        if sf(old,suppr)>-1:
            return '','','*ARTEXISTS('+str(len(old))+')'
        else:
            return '','','*ARTEXISTS('+str(len(old))+') for:\n'+text+'\n'
    return text,summ,'CREATED'

def edit_Force(la,Art,old,text,summ='bot: standardizing'):
    """text, summary"""
    if Ne(old,''):
        log('*HADTOCREATE')
    return text,summ,'HADTOCREATE' if Ne(old,'') else 'REPLACEDARTICLE'

def edit_Subst(la,Art,old,lp,summ='bot: standardizing'):
    """list of replace pairs (or 6ples, with min/max, warn min/max)"""
    t=old
    for p in lp:
        k=nin(old,p[0])
        if k<I(p,2,0) or k>I(p,3,1000):
            print g_logcon, k, I(p,2,0), I(p,3,1000)
            return '','','*WRONGNUMBEROF '+p[0]
        if k<I(p,4,0) or k>I(p,5,1000):
            log(Art+';*NOTE NUMBER OF '+p[0]+':'+str(k))
        t=t.replace(p[0],p[1])
    return t,summ,'REPLACED'