Colorear textos en html y React

Es fácil agregar marcado al texto con las manos. Puede marcar el texto aquí en Habré y luego copiarlo en el sitio. Puede hacer una búsqueda con un reemplazo en Notepad ++ o en Atom.

Si es 1 texto. Si hay muchos textos, quiero tener una herramienta para extraer fragmentos de texto con etiquetas html o generar código fuente para React. En Python, esto no es difícil (unas pocas líneas de código por color).



Si conoce Python y expresiones regulares, siga el enlace.

Hay ejemplos y códigos fuente. Debajo de Katom una descripción detallada.

Marcado de texto, por ejemplo, para colorear el código fuente de Javascript


Considere la función:

def jsToHtml(s):

En la entrada, el texto fuente devuelve html.

Establecemos las variables que definen los atributos de los bloques. En el ejemplo, los estilos se usan por claridad, en el futuro es más lógico reemplazarlos por clases.

comm = 'style="color: red;"' #   
blue = 'style="color: blue;"' # 
...

Margen.

Lo primero que debe hacer es escapar de los caracteres '&', '<', '>'

s = s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

'&' se escapa para mostrar las combinaciones de letras '& lt;', '& gt;' y otros '& ...;', los

caracteres '<' y '>' se escapan para no entrar en conflicto con las etiquetas.

Puedes proteger muchas cosas, pero, en mi opinión, en utf-8 es suficiente.

Algoritmo de marcado:

  • Tomamos una nueva plantilla y buscamos todos los fragmentos de texto que la satisfacen.
  • Recortamos cada fragmento, le agregamos marcas y guardamos el fragmento marcado en una matriz (al mismo tiempo, guarda el texto original: será útil).
  • En su lugar, inserte el trozo con el número.
  • Y así para todos los colores.
  • Cuando todo esté pintado, reemplace los trozos con fragmentos de colores de la matriz.

El código auxiliar debe ser único, pero en nuestro texto no hay un solo carácter '<' ni un solo '>'.

Hacer un trozo:

f'<{i}>'

Donde yo es el número de talón. Definitivamente no hay tal cosa en el texto.

Qué hacer si ya hay un trozo dentro del fragmento.

Por ejemplo, en el texto fuente había líneas:

`  :   /*     */ `
/* , `     ` */

Opciones 2:

  1. Ignorar. En este caso, la cadena tendrá un doble color.
  2. Busque trozos anidados y reemplácelos con el texto original (sin marcar).

Hacemos una función que implementa todo esto (10 líneas de código, 10 líneas de comentario):

def oneRe(reStr, s, attr, ls, multiColor=False):
    '''
        s  ,          ls,
           (<0>, <1> ...<1528>...).
          .
    
    reStr - re
    s - 
    attr -  style/class/etc
    ls -   
    multiColor=False,     
    '''
    for block in set(re.findall(reStr, s)):
        toArr = block
        if not multiColor: #  multiColor==False,   
            for prev in set(re.findall(r'<[\d]+>', block)): #  : <0> ... <21>
                iPrev = int(prev[1:-1], 10)                #     
                toArr = toArr.replace(prev, ls[iPrev][1])  #   '<0> qwe <21>'  (<0>,<21>)  . 
        ls.append([f'<span {attr}>{toArr}</span>', toArr]) #     ls 2 :     
        s = s.replace(block, f'<{len(ls)-1}>')
    return s

Esta es una demostración: el marcado de expresión regular puede ser incorrecto, los caracteres escapados (\ ', \ ") no se procesan.

Para referencia: expresión:

s = s.replace(/A + B/g, 'A - B');

Notepad ++ y los editores Atom tienen diferentes colores.

Ahora que hay oneRe , los fragmentos para colorear se hacen de manera simple. Un ejemplo que colorea las líneas en apóstrofes y comillas:

s = oneRe(r"'[\s\S]*?'", s, green, ls, multiColor=False) #    ''   ls
s = oneRe(r'"[\s\S]*?"', s, green, ls, multiColor=False) #    ""   ls

Un ejemplo de coloración más compleja. Necesita js para colorear expresiones en líneas múltiples
`    ${A + B}  ${A - B}`


for mStr in set(re.findall(r'`[\s\S]+?`', s)): #    
    newFstr = mStr
    for val in set(re.findall(r"\$\{[\s\S]+?\}", mStr)):
        ls.append([f'<span {darkRed}>{val}</span>', val])
        newFstr = newFstr.replace(val, f'<{i}>')
        i += 1
    s = s.replace(mStr, newFstr)
    
s = oneRe(r'`[\s\S]+?`', s, green, ls, multiColor=True) #    ``   ls

primero encontramos las

cadenas múltiples: re.findall (r`` [\ s \ S] +? '', s) devuelve una lista de bloques de texto entre caracteres de comillas inversas.

Cada bloque contiene espacios o no espacios ([\ s \ S], es decir, cualquier cosa).

Longitud del bloque 1 o más (+).

Sin avaricia ("?" Significa que no hay símbolo "` "dentro del bloque).

Copie el bloque encontrado (variable mStr ) a la variable newFstr .
Encontramos en el bloque subbloques con las expresiones $ {...}.

re.findall (r "\ $ \ {[\ s \ S] *? \}", mStr) devuelve una lista de dichos subbloques. Guardamos el marcado en la matriz ls y reemplazamos el subbloque con el código auxiliar en la variable newFstr .
Cuando se agoten los subbloques, reemplácelos en la cadena original svalor de bloque original a nuevo.

El conjunto no es superfluo. Si Findall devuelve varios bloques idénticos, al procesar el primer bloque en el código fuente, todos los bloques idénticos se reemplazan con apéndices a la vez. Al procesar el segundo mismo bloque, ya no estará en el texto fuente. El conjunto elimina la duplicación.

Archivo JsToHtml.py
# -*- coding: utf-8 -*- 
'''
AON 2020

'''

import re

# *** *** ***

def oneRe(reStr, s, attr, ls, multiColor=False):
    '''
        s  ,          ls,
           (<0>, <1> ...<1528>...).
          .
    
    reStr - re
    s - 
    attr -  style/class/etc
    ls -   
    multiColor=False,     
    '''
    i = len(ls) #   
    for block in set(re.findall(reStr, s)):
        toArr = block
        if not multiColor: #  multiColor==False,   
            for prev in set(re.findall(r'<[\d]+>', block)): #  : <0> ... <21>
                iPrev = int(prev[1:-1], 10)                 #     
                toArr = toArr.replace(prev, ls[iPrev][1])   #   '<0> qwe <21>'  (<0>,<21>)  . 
        ls.append([f'<span {attr}>{toArr}</span>', toArr])  #     ls 2 :     
        s = s.replace(block, f'<{i}>')              #       
        i += 1
    return s

# *** *** ***

def operColor(s, ls, color):
    '''
     .
      ,       ,  
    '''
    i = len(ls)
    for c in ['&lt;=', '&gt;=', '=&lt;', '=&gt;', '&lt;', '&gt;', '&amp;&amp;', '&amp;',
              '===', '!==', '==', '!=', '+=', '-=', '++', '--', '||']:
        ls.append([f'<span {color}>{c}</span>',0])
        s = s.replace(c, f'<{i}>')
        i += 1
    for c in '!|=+-?:,.[](){}%*/':
        ls.append([f'<span {color}>{c}</span>',0])
        s = s.replace(c, f'<{i}>')
        i += 1
    return s

# *** *** ***

def jsToHtml(s):
    '''
      .
     ,        <span>.
    '''

    black = '''style="font-family: 'Courier New', monospace;
        background: #fff; 
        color: black;
        font-weight: bold;
        border: 1px solid #ddd;
        padding: 5px;
        text-align: left;
        white-space: pre;"'''
    comm = 'style="color: red;"'
    green = 'style="color: green; font-style: italic;"'
    blue = 'style="color: blue;"'
    red2 = 'style="color: #840;"'

    s = s.replace('&', '&amp;').replace('<', &'&lt;').replace('>', '&gt;')   #   '&', '<', '>'

    ls = []

    i = 0
    for mStr in set(re.findall(r'`[\s\S]+?`', s)): #    
        newFstr = mStr
        for val in set(re.findall(r"\$\{[\s\S]+?\}", mStr)):
            ls.append([f'<span {darkRed}>{val}</span>', val])
            newFstr =newFstr.replace(val, f'<{i}>')
            i += 1
        s = s.replace(mStr, newFstr)
        
    s = oneRe(r'`[\s\S]+?`', s, green, ls, multiColor=True) #    ``   ls
    s = oneRe(r"'[\s\S]*?'", s, green, ls, multiColor=False) #    ''   ls
    s = oneRe(r'"[\s\S]*?"', s, green, ls, multiColor=False) #    ""   ls
    s = oneRe(r'/[\s\S].*?/g\b', s, green, ls, multiColor=False) #    re-   ls
    s = oneRe(r'/[\s\S].*?/\.', s, green, ls, multiColor=False) #    re-   ls
    s = oneRe(r'/\*[\s\S]+?\*/', s, comm, ls, multiColor=False) #    /*  */   ls (  - )
    s = oneRe(r'//[\s\S]*?\n', s, comm, ls, multiColor=False) #    //    ls (  - )

    i = len(ls)

    #    
    for c in ['new', 'JSON', 'Promise', 'then', 'catch', 'let', 'const', 'var', 'true', 'false', 'class', 'from', 'import', 'set', 'list', 'for', 'in', 'if', 'else', 'return', 'null']:
        ls.append([f'<span {blue}>{c}</span>',0])
        s = re.sub (r'\b%s\b' % c, f'<{i}>', s)
        i += 1

    #     
    for c in ['window', 'doc', 'cmd', 'init','init2', 'recalc', 'hide', 'readOnly', 'validate']:
        ls.append([f'<span {darkRed}>{c}</span>',0])
        s = re.sub (r'\b%s\b' % c, f'<{i}>', s)
        i += 1

    s = operColor(s, ls, darkBlue) #    

    for j in range(len(ls), 0, -1):  #  , , ,    
        s = s.replace(f'<{j-1}>', ls[j-1][0])

    return f'<div {black}>{s}</div>'

# *** *** ***


HTML en reaccionar


Convierta html a código fuente React en htmltoreact.com . También hay un enlace a GitHub.

Esto no me convenía: en primer lugar, no forma exactamente lo que necesito, y en segundo lugar, cómo arrastraré este milagro a mi servidor.

Yo escribí el mío.

Instale la biblioteca lxml (pip install lxml o pip3 install lxml).

Importamos:

from xml.dom.minidom import parseString
from lxml import html, etree

Convierte texto html a texto xhtml. Es casi lo mismo, pero todas las etiquetas están cerradas.

doc = html.fromstring(htmlText)
ht = etree.tostring(doc, encoding='utf-8').decode()

El xhtml resultante aparece en la casa del árbol usando la mini-casa.

dom = parseString(ht)

Hacemos una función que salta recursivamente sobre los nodos y genera el resultado en forma del código fuente React.

Después de llamar a parseString, el árbol dom es un nodo papá que tiene nodos hijos que tienen hijos, etc.

Cada nodo es un diccionario que contiene su descripción:

  • nodeName: nombre de nodo, cadena
  • childNodes - nodos hijos, lista
  • atributos- atributos, diccionario
  • Un nodo llamado #text tiene nodeValue (string)

Ejemplo:

<div class="A" style="color: red;">Red of course,<br> </div>

Después de las transformaciones obtenemos:
{ 'nodeName':'div',
  'attributes': {'style': 'color: red;', 'class': 'A'},
  'childNodes': [
    {'nodeName':'#text', 'nodeValue': 'Red of course,'},
    {'nodeName':'br'},
    {'nodeName':'#text', 'nodeValue': ''},
  ],
}

Convertir dom en una cadena es fácil (hay un pprint), al generar el código React, reemplacé la clase con className y rehice el atributo de estilo.

En los nodos de texto, '{', '}', '<', '>' se escapan.

Archivo HtmlToReact.py
# -*- coding: utf-8 -*- 
# -*- coding: utf-8 -*- 

from xml.dom.minidom import parseString
from lxml import html, etree

# *** *** ***

_react = ''

def htmlToReact(buf):
    '''
    buf - html-
     ReactJS- 
    '''
    global _react
    _react = ''

    try:
        r = re.search('<[\\s\\S]+>', buf)
        if r:
            doc = html.fromstring(r.group(0))
            ht = etree.tostring(doc, encoding='utf-8').decode()
            xHtmlToReact(parseString(ht).childNodes[0], '')
            return _react
        else:
            return '<empty/>'
    except Exception as ex:
        s = f'htmlToReact: \n{ex}'
        print(s)
        return s

# *** *** ***

def sU(a, c):
    '''
    xlink:show   ->  xlinkShow
    font-weight  ->  fontWeight
    '''
    l, _, r = a.partition(c)
    return ( (l + r[0].upper() + r[1:]) if r else a).strip()

# *** *** ***

def xHtmlToReact(n, shift='\n    '):
    '''
       -
      ,        upperCase
       global  _react
    '''
    global _react

    if n.nodeName.lower() in ['head', 'script']:
        return
    
    _react += shift + '<' + n.nodeName.lower()
    if n.attributes:
        for k, v in n.attributes.items():
            if k == 'style':
                style = ''
                for s in v.split(';'):
                    if s.strip():
                        l, _, r = s.partition(':')
                        style += f'''{sU(l, '-')}: "{r.strip()}", '''
                if style:
                    _react += ' style={{' + style + '}}'
            elif k == 'class':
                _react += f' className="{v}"'
            else:
                kk = k.replace('xlink:href', 'href') # deprcated
                _react += f''' {sU( sU(kk, ':'), '-' )}="{v}"'''
        
    _react += '>'
    if n.childNodes:
        for child in n.childNodes:
            if  child.nodeName == '#text':
                tx = child.nodeValue
                for x in ['{', '}', '<', '>']:
                    tx = tx.replace(x, '{"' + x + '"_{_')
                tx = tx.replace('_{_', '}')
                if tx[-1] == ' ':
                    tx = tx[:-1] + '\xa0'
                _react += tx.replace('\n', '<br/>')
            else:
                xHtmlToReact(child)
                
    _react += f'{shift}</{n.nodeName.lower()}>'

# *** *** ***



Ejemplos y código fuente aquí.

PD: en Habré, la coloración del código de Python (y tal vez otros) no es ideal. Si la cadena de Python tiene la cadena & amp; , se muestra como & . En mis códigos modifiqué para que se vea bien. Si Habr corrige un error, mis textos se cruzarán.

All Articles