Coloration de textes en html et React

Il est facile d'ajouter du balisage au texte avec vos mains. Vous pouvez annoter le texte ici sur Habré, puis le copier sur le site. Vous pouvez faire une recherche avec un remplacement dans Notepad ++ ou dans Atom.

Si c'est 1 texte. S'il y a beaucoup de textes, je veux avoir un outil pour extraire des fragments de texte avec des balises html ou générer du code source pour React. Sur Python, ce n'est pas difficile (quelques lignes de code par couleur).



Si vous connaissez Python et les expressions régulières, suivez le lien.

Il existe des exemples et des codes source. Sous katom une description détaillée.

Balisage de texte, par exemple, coloration du code source Javascript


Considérez la fonction:

def jsToHtml(s):

En entrée, le texte source renvoie du html.

Nous définissons les variables qui définissent les attributs des blocs. Dans l'exemple, les styles sont utilisés pour plus de clarté, à l'avenir, il est plus logique de les remplacer par des classes.

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

Marquage.

La première chose à faire est d'échapper aux caractères '&', '<', '>'

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

'&' est échappé pour afficher les combinaisons de lettres '& lt;', '& gt;' et autres '& ...;', les

caractères '<' et '>' sont échappés afin de ne pas entrer en conflit avec les balises.

Vous pouvez protéger beaucoup de choses, mais à mon avis, en utf-8, cela suffit.

Algorithme de balisage:

  • Nous prenons un nouveau modèle et recherchons tous les fragments de texte qui le satisfont.
  • Nous découpons chaque fragment, y ajoutons du balisage et enregistrons le fragment marqué dans un tableau (en même temps, sauvegardez le texte original: il vous sera utile).
  • À sa place, insérez le talon avec le numéro.
  • Et donc pour chaque couleur.
  • Lorsque tout est peint, remplacez les talons par des fragments colorés du tableau.

Le talon doit être unique, mais dans notre texte, il n'y a pas un seul caractère '<' et pas un seul '>'.

Faire un talon:

f'<{i}>'

où i est le numéro de talon. Il n'y a certainement rien de tel dans le texte.

Que faire s'il y a déjà un talon à l'intérieur du fragment.

Par exemple, dans le texte source, il y avait des lignes:

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

Options 2:

  1. Ignorer. Dans ce cas, la chaîne aura une double couleur.
  2. Recherchez les talons imbriqués et remplacez-les par le texte d'origine (non marqué).

Nous faisons une fonction qui implémente tout cela (10 lignes de code, 10 lignes de commentaire):

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

Il s'agit d'une démonstration: le balisage des expressions régulières peut être incorrect, les caractères d'échappement (\ ', \ ") ne sont pas traités.

Pour référence: expression:

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

Les éditeurs Notepad ++ et Atom ont des couleurs différentes.

Maintenant qu'il y a unRe , la coloration des fragments se fait simplement. Un exemple de coloration des lignes dans les apostrophes et les guillemets:

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

Un exemple de coloration plus complexe. Besoin de js pour colorer les expressions sur plusieurs lignes
`    ${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

nous trouvons d'abord les

chaînes multiples: re.findall (r `` [\ s \ S] +? '', s) renvoie une liste de blocs de texte entre les caractères de guillemets.

Chaque bloc contient des espaces ou des non-espaces ([\ s \ S] c'est-à-dire n'importe quoi).

Longueur de bloc 1 ou plus (+).

Sans avidité ("?" Signifie qu'il n'y a pas de symbole "" dans le bloc).

Copiez le bloc trouvé (variable mStr ) dans la variable newFstr .
On trouve dans le bloc des sous-blocs avec les expressions $ {...}.

re.findall (r "\ $ \ {[\ s \ S] *? \}", mStr) renvoie une liste de ces sous-blocs. Nous enregistrons le balisage dans le tableau ls et remplaçons le sous-bloc par le stub dans la variable newFstr .
Lorsque les sous-blocs sont épuisés, remplacez dans la chaîne d'origine svaleur de bloc d'origine à nouveau.

L'ensemble n'est pas superflu. Si findall renvoie plusieurs blocs identiques, lors du traitement du premier bloc dans le code source, tous les blocs identiques sont remplacés par des stubs à la fois. Lors du traitement du deuxième même bloc, il ne sera plus dans le texte source. L'ensemble supprime la duplication.

Fichier 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 dans React


Convertissez du code HTML en code source React sur htmltoreact.com . Il existe également un lien vers GitHub.

Cela ne me convenait pas: premièrement, cela ne correspond pas exactement à ce dont j'ai besoin, et deuxièmement, comment je vais faire glisser ce miracle sur mon serveur.

J'ai écrit le mien.

Installez la bibliothèque lxml (pip install lxml ou pip3 install lxml).

Nous importons:

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

Convertissez le texte html en texte xhtml. C'est presque la même chose, mais toutes les balises sont fermées.

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

Le xhtml résultant parcourt la cabane en utilisant la mini-maison.

dom = parseString(ht)

Nous faisons une fonction qui saute récursivement sur les nœuds et génère le résultat sous la forme du code source React.

Après avoir appelé parseString, l'arborescence dom est un nœud papa qui a des nœuds enfants qui ont des enfants, etc.

Chaque nœud est un dictionnaire contenant sa description:

  • nodeName - nom du nœud, chaîne
  • childNodes - nœuds enfants, liste
  • attributs - attributs, dictionnaire
  • Un nœud appelé #text a nodeValue (chaîne)

Exemple:

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

Après les transformations on obtient:
{ 'nodeName':'div',
  'attributes': {'style': 'color: red;', 'class': 'A'},
  'childNodes': [
    {'nodeName':'#text', 'nodeValue': 'Red of course,'},
    {'nodeName':'br'},
    {'nodeName':'#text', 'nodeValue': ''},
  ],
}

La conversion de dom en chaîne est facile (il y a un pprint), lors de la génération du code React, j'ai remplacé la classe par className et refait l'attribut style.

Dans les nœuds de texte, '{', '}', '<', '>' sont échappés.

Fichier 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()}>'

# *** *** ***



Exemples et code source ici.

PS Sur Habré la coloration du code Python (et peut-être d'autres) n'est pas idéale. Si la chaîne python a la chaîne & amp; , il s'affiche sous la forme & . Dans mes codes, j'ai peaufiné pour que tout soit correct. Si Habr corrige une erreur, mes textes se croiseront.

All Articles