Mini générateur pour mettre en forme vos données avec un modèle

Bonjour,

Il y a eu déjà plusieurs initiatives pour mettre en vos formes vos données à partir d’un modèle en Markdown / HTML :

Cette nouvelle proposition s’appuie sur ces précédents travaux et ajoute les possiblités suivantes :

  • boucler sur les enregistrements d’une référence multiple avec {#each colonne#} {#end#}
  • rendre conditionnel l’affichage de certaine partie avec {#if condition#} {#end#}

Documentation sommaire :

  • {colonne}
  • {colonne.relation}

Boucles

  • {#each relation_multiple#} … {#end#}
  • {#each relation_multiple# sortby id_colonne1 id_colonne2 … {#end#}

Conditions

  • {#if {colonne}#} … {#end#}
  • {#if valeur#} … {#end#}
  • {#if ({colonne} | valeur) operateur ({colonne} | valeur) #} … {#end#}
    • operateur : == | != | > | >= | < | <=
  • {#if …#} … {#else#} … {#end#}

Petite démo :
Template engine

4 « J'aime »

Bravo c’est super ! Est-ce-que tu peux copier-coller le code de ton moteur de templates ici ?

C’est un premier jet. Le code est perfectible ! (EDIT: bugfix 2025-11-04)

La récupération du template se fait aux lignes 6 et 7 :

model = Models.lookupOne(id=1)
template = model.Model

Il y aussi la possiblité de référencé une URL d’image, dans la class FindData

super().init({« Logo »: model.Logo})

Voici le code complet :

import datetime
import numbers

# Retrieve Template
model = Models.lookupOne(id=1)
template = model.Model

def get(data, field):
  if isinstance(data, dict):
    return data[field]
  else:
    return getattr(data, field, None)

# Get attribute or related attribute
def getattrpath(record, key):
  if '.' in key:
    table_name, field = key.split('.', 1)
    related_record = get(record, table_name)
    if related_record:
      return getattrpath(related_record, field)
  else:
    return get(record, key)
  return None


# Data retrieval class
class FindData(dict):
  def __init__(self,record):
    super().__init__({"Logo": model.Logo})
    self.record = record
    
  def __missing__(self, key):
    value = getattrpath(self.record, key)
    
    # If still no value, return the placeholder
    return value if value is not None else f"{{{key}}}"

# Syntax parser
def parse(context, str):
  waitelse = False
  ifcond = False
  out=""
  while str:
    beg = str.find("{#")
    if beg>-1:
      if context:
        out = out + str[:beg].format_map(FindData(context))
      orig = str[beg:]
      str = str[beg+2:]
      end = str.find("#}")
      expr = str[:end]
      tokens = expr.split(" ")
      
      str = str[end+2:]
      match tokens[0]:
        case "if":
          ifcond = parse_expression(context, expr[3:])
          (do, str) = parse(context if ifcond else None, str)
          if ifcond:
            out = out + do
          
          waitelse = True
            
        case "else":
          if waitelse:
            waitelse = False
            (do, str) = parse(context if not ifcond else None, str)
            if not ifcond:
              out = out + do
          else:
            return out, orig
          
            
        case "each":
          if context:
            coll = get(context, tokens[1])
            if len(tokens) > 2:
              if tokens[2] == "sortby":
                for key in (tokens[3::])[::-1]:
                  reverse = key[0] == "-"
                  if reverse:
                    key = key[1::]
                  key = key.format_map(FindData(context))
                  if key != "":
                    coll = sorted(coll, reverse=reverse, key=lambda data: getattrpath(data, key))
              else:
                out = out + "## EACH SYNTAX ERROR ##"
          
            if len(coll):
              for item in coll:
                (data, nextstr) = parse(item, str)
                out = out + data
            else:
              (_, nextstr) = parse(None, str)
          else:
            (_, nextstr) = parse(None, str)
          str = nextstr
        case "end":
          return out, str
    elif context:
      return out + str.format_map(FindData(context)), ""
  return out, ""
  
# Expression parser
def parse_expression(context, str):
  tokens = list(map(lambda tok: parse_value(context, tok), str.split(" ")))
  match tokens:
    case [val]:
      try:
        len(val)
      except:
        return val
      else:
        return len(val)>1
        
    case [v1, "==", v2]:
      return v1 == v2
    case [v1, "!=", v2]:
      return v1 != v2
    case [v1, ">", v2]:
      return v1 > v2
    case [v1, ">=", v2]:
      return v1 >= v2
    case [v1, "<", v2]:
      return v1 < v2
    case [v1, "<=", v2]:
      return v1 <= v2
    case _:
      raise NameError("Syntax Error")

def parse_value(context, str):
  end = len(str)-1
  if str[0] == '{' and str[end]=='}':
    return getattrpath(context, str[1:end])
  if str[0] == '"' and str[end]=='"':
    return str[1:end]
  match str:
    case "True":
      return True
    case "False":
      return False
    case "None":
      return None
    case _:
      try:
        return float(str)
      except:
        return str
    

# Parse
(tree, _) = parse(rec, template)
return tree