wiki2html

Nombre: wiki2html
Autor: point_to_null
Origen: original
Lenguaje: python
Licencia: gpl
Propósito: Crea un compendio en html de un árbol de enlaces recursivos de un articulo Wiki, de todas las paginas a las que enlaza, y de estas a su vez, hasta que el limite de recursion es alcanzado, se llena el espacio disponible en memoria o todos los artículos abiertos son completamente duplicados.

Dependencias: python-creoleparser

Código:

#!/usr/bin/env python
#-*- coding: UTF-8 -*-
# Bugs conocidos:
#  * Se elimina todo el código en la misma linea donde se recursa
import re
import urllib2
import sys

def debug(mensaje):
    sys.stderr.write(str(mensaje) + "\n")

try:
    from creoleparser import text2html
## Uso text2html en lugar de creole2html porque no me importa usar caracteristicas
## no incluidas en Creoles 1.0
except ImportError:
    debug("""Este programa necesita de Creoleparser para funcionar.

Si estás usando Debian debes instalar el paquete python-creoleparser:
    # apt-get install python-creoleparser
   
Si estás utilizando cualquier otro sistema operativo debería poder instalar Creoleparser usando las setup tools de python (http://peak.telecommunity.com/DevCenter/setuptools):
    # easy_install Creoleparser

Si no tienes apt-get ni easyinstall puedes descargar manualmente Geshi de aquí (http://genshi.edgewall.org/wiki/Download) y Creoleparser de aquí (http://pypi.python.org/pypi/Creoleparser), luego de descomprimir los paquetes los instalas ejecutando setup.py con el argumento instalar (usando permisos de administrador, e.g.:
    # ./setup.py install
   
Si nada de lo anterior sirvio no te preocupes, aún puedes pasarte al lado debian de la vida...""")
    exit(15)

PRE_ORDEN = "/index.php?title="
POS_ORDEN = "&action="
RECURS = "  "
MAX_RECURS = 4

def convertir_direccion(direccion, recursiones=0):
    debug(RECURS * recursiones + "Convirtiendo: " + direccion)
    if direccion.startswith(WIKI):
        if direccion.count("/go/"):
            direccion = direccion.replace("/go/", PRE_ORDEN, 1) + POS_ORDEN + "edit"
        else:
            raise ReferenceError("No debe referenciar a la pagina raiz")
#            direccion = direccion[:-1] + PRE_ORDEN + "Dbnews" + POS_ORDEN + "edit"

       
    elif direccion.startswith("[[") and direccion.endswith("]]"):

        expresion_regular = r"""^\[\[(?P<pagina>.*?)(\|.*?)?\]\]"""
        objeto = re.search(expresion_regular, direccion, re.UNICODE)
        direccion = "[[" + objeto.group('pagina') + "]]"

        direccion = WIKI + PRE_ORDEN + direccion[2:-2].replace(" ", "_") \
            + POS_ORDEN + "edit"

    debug(RECURS * recursiones + "Nueva direccion: " + direccion)
    return direccion

def obtener_autores(articulo, recursiones=0):
    direccion = WIKI + PRE_ORDEN + articulo + POS_ORDEN + "history"
    debug()
#    pagina = urllib2.urlopen(direccion)
#    r'(target=|title="User:)(?P<usuario>.*?)"'

def obtener_codigo(direccion, recursiones=0):
    recursiones += 1

    debug(RECURS * recursiones + "Intentando obtener el codigo en " + direccion)

    if not direccion.endswith("&action=edit"):
        direccion = convertir_direccion(direccion, recursiones + 1)

    try:
        pagina = urllib2.urlopen(direccion)
        html = "".join(pagina.readlines())
    except urllib2.URLError:
        raise OSError("No se pudo descargar la web: %s" % direccion)

    expresion_regular = r"""<textarea(.|\s)*?>(?P<codigo>(.|\s)*?)</textarea>"""
    procesador = re.compile(expresion_regular, re.MULTILINE)
    codigo = procesador.search(html)

    return codigo.group("codigo")

def procesar(codigo, recursiones=0):
    debug(RECURS * recursiones + "Procesando el codigo obtenido")
    expresion_regular = "|".join([
        "^.*?(?P<recursar>\[\[(.|\s)*?\]\]).*?$",
        "^(?P<resto>.*?)$",
    ])

    procesador = re.compile(expresion_regular, re.MULTILINE)
    iterador = procesador.finditer(codigo)

    salida = u""

    for elemento in iterador:
        grupos = elemento.groupdict()

        if grupos["recursar"]:
            if recursiones < MAX_RECURS:
                codigo = obtener_codigo(grupos["recursar"], recursiones)
                salida += procesar(codigo, recursiones + 1)

            else:
                debug(RECURS * recursiones \
                    + "Enlace ignorado; maximo nivel de recursion alcanzado" )

        else:             
            for clase in grupos:
                if grupos[clase]:
                    try:
                        salida += unicode(grupos[clase],'utf-8', 'replace')
                    except UnicodeDecodeError:
                        raise

        salida += "\n"

    return salida

if __name__ == "__main__":

    debug("Fun time!")

    if len(sys.argv) == 2:
        if  sys.argv[1].startswith("http://"):
            re_raiz = "^\s*?(?P<raiz>http://.*?\..*?)/(?P<resto>.*?)$"
            procesador = re.compile(re_raiz)

            if procesador.findall(sys.argv[1]):
                WIKI = procesador.findall(sys.argv[1])[0][0]
                PAGINA = sys.argv[1]
            else:
                raise ReferenceError("Debe especificar un articulo dentro del wiki")

            codigo_wiki = procesar(obtener_codigo(PAGINA, -1))

# *Corregimos errores de formato ya que la comprobacion de cierre de marcador
#  que hace coreoleparser es más estricta que la de la mayoria de las wikis
#
# **Aplico las modificaciones de formato que coreole no aplica
# **Separamos el texto en parrafos
            parrafos = codigo_wiki.split("\n\n")
# **Chequeamos la paridad de los marcadores de formato por parrafo
            for i in xrange(len(parrafos)):
# ***negritas (''')
                if parrafos[i].count("""'''""") % 2:
                    parrafos[i] =  parrafos[i] + """'''"""
# ***cursivas ('')
                if parrafos[i].count("""''""") % 2:
                    parrafos[i] =  parrafos[i] + """''"""
# ***h3 (===)
                if parrafos[i].count("""===""") % 2:
                    parrafos[i] =  parrafos[i] + """==="""
# ***h2 (==)
                if parrafos[i].count("""==""") % 2:
                    parrafos[i] =  parrafos[i] + """=="""
# ***h1 (=)
#                if parrafos[i].count("""=""") % 2:
#                    parrafos[i] =  parrafos[i] + """="""

            codigo_wiki = u"\n\n".join(parrafos)

#*Modificaciones de código que no realiza coreoleparser
#**que no identa los parrafos que comienzan con ":"
            p = re.compile(r"""^:::""", re.MULTILINE|re.UNICODE)
            codigo_wiki = p.sub("\n" + "&nbsp;" * 12, codigo_wiki)
            p = re.compile(r"""^::""", re.MULTILINE|re.UNICODE)
            codigo_wiki = p.sub("\n" + "&nbsp;" * 8, codigo_wiki)
            p = re.compile(r"""^:""", re.MULTILINE|re.UNICODE)
            codigo_wiki = p.sub("\n" + "&nbsp;" * 4, codigo_wiki)

#*Aplicacion de guia de estilo
#**Resalto en rojo las comillas '"'
            codigo_wiki = codigo_wiki.replace('&quot;', '<font size=8 color=red>&quot;</font>')
#**Delatores del uso de traductor "auto>> "
            codigo_wiki = codigo_wiki.replace('auto>> ', '<font size=8 color=red>auto>> </font>')
#**Aplico cursiva al texto entre cosos "«" y "»"
            codigo_wiki = codigo_wiki.replace(u'«', u"""<em>«""")
            codigo_wiki = codigo_wiki.replace(u'»', u"""»</em>""")

            debug("Convirtiendo wiki > html")
            html = text2html(codigo_wiki)

#*Esto no debería ser necesario, es paleativo para errores en creoleparser
#
#**que escapa "&" en el código generado... lo que no es muy logíco.
            html = html.replace("""&amp;""", """&""")
#**que escapa "<" en el código generado
            html = html.replace("""&lt;""", """<""")
#**que escapa ">" en el código generado
            html = html.replace("""&gt;""", """>""")
#**que escapa "&" en el código generado... lo que no es muy logíco (reloaded).
            html = html.replace("""&amp;""", """&""")
#**que no formatea el texto en negritas ("'''")
            for i in xrange(html.count("""'''""") / 2):
                html = html.replace("""'''""", "<strong>", 1)
                html = html.replace("""'''""", "</strong>", 1)
#**que no formatea el texto en cursivas ("''")
            for i in xrange(html.count("""''""") / 2):
                html = html.replace("""''""", "<em>", 1)
                html = html.replace("""''""", "</em>", 1)
#**que no crea etiquetas de imagenes para direcciones de imagenes
#<a href="http://fqdn/ruta/nombre.ext">http://fqdn/ruta/nombre.ext</a>
#<img src="http://fqd/ruta/nombre.ext" alt="nombre" title="nombre">
            p = re.compile(r"""<a href="(?P<protocol>http|ftp|ftps|https)://(?P<fqdn>.*?)/(?P<ruta>.*)/(?P<nombre>.*)\.(?P<ext>png|gif|jpg|jpeg)">(?P=protocol)://(?P=fqdn).(?P=ruta)/(?P=nombre).(?P=ext)</a>""", re.MULTILINE|re.UNICODE|re.IGNORECASE)
            html = p.sub(r"""<img src="http://\g<fqdn>/\g<ruta>/\g<nombre>.\g<ext>" alt="\g<nombre>" title="\g<nombre>">""", html)
#**que crea de forma incorrecta enlaces de referencia (WTF!)
#[<a href="ruta%5D">ruta]</a>
#[<a href="ruta">ruta</a>]
            p = re.compile(r"""\[<a href="(?P<ruta>.*)\]">(?P=ruta)\]</a>""", re.MULTILINE|re.UNICODE)
            html = p.sub(r"""[<a href="\g<ruta>">\g<ruta></a>]""", html)
#**que no respeta los nombres en los enlaces de referencia
#[<a href="url">url</a> nombre]
#<a href="ruta">nombre</a>
            p = re.compile(r"""\[<a href="(?P<ruta>.*?)">(?P=ruta)</a> (?P<nombre>.*?)\]""", re.MULTILINE|re.UNICODE)
            html = p.sub(r"""<a href="\g<ruta>">\g<nombre></a>""", html)

#*Aqui aplico el parche esDebianizador
#**Creo espacios previos a h2 para la prolijidad en el foro
            html = html.replace("""<h2>""", """<br><h2>""")
            html = html.replace("""</h2>""", """<br></h2><img src="http://arnet.no-ip.org/iconos/barra_495x2.png">""")
#            html = html.replace("""</h2>""", """<br></h2><hr size=2 width="50%" align=left tabindex=-1>""")
#**"<h1>" -> "cabecera" porque en el foro no podemos usar el estilo h1
            html = html.replace("""<h1>""", """<br><h2><img src="http://arnet.no-ip.org/iconos/pre-titular.png">""")
            html = html.replace("""</h1>""", """<img src="http://arnet.no-ip.org/iconos/post-titular.png"></h2><img src="http://arnet.no-ip.org/iconos/barra_495x4.png"><br>""")
#            html = html.replace("""</h1>""", """<img src="http://arnet.no-ip.org/iconos/post-titular.png"></h2><hr size=4 width="50%" align=left tabindex=-1><br>""")

            print html

        else:
            raise ValueError("La direccion debe comenzar con http://")

    else:
        raise ReferenceError("Se debe especificar solo un argumento, la pagina Wiki a replicar")

Ejemplos de uso:
Solo se debe especificar la dirección del articulo a replicar. e.g.:

wiki2html http://misitio.miwiki/go/mi_articulo > pagina.html

Tened en cuenta que si la página pasada como argumento enlaza a varias otras la respuesta tardará, naturalmente, un poco en ser elaborada.

Comentarios:
Ya que el script fue escrito con un caso particular en mente no es compatible con las urls de las wikipedias que han modificado el comportamiento estándar de wikimedia o que nunca lo han usado (son la mayoría, pues las clearurl se pusieron de moda). Pero si alguien necesita trabajar con una de estas Wikis y necesita ayuda para extender el script no dude en avisarme. De no.. ya lo haré un fin de semana de estos.