.NET et XHTML 1.0 ?

.NET et XHTML 1.0 ? - C#/.NET managed - Programmation

Marsh Posté le 02-09-2005 à 23:37:21    

Salut, j'ai un léger souci.
 
De base, .NET génère du code HTML 4.0 tout pourri.
 
Je voudrais me faire une class permettant de modéliser une page XHTML 1.0 correctement.
 
Sauf que déjà, ça part mal...
 

Code :
  1. System.Xml.XmlDocument doc = new XmlDocument();
  2. doc.CreateXmlDeclaration("1.0", "utf-8", null);
  3. doc.CreateDocumentType("html", "public", "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" );


 
Ca plante :

Code :
  1. Erreur du serveur dans l'application '/testdotnet'.
  2. --------------------------------------------------------------------------------
  3. Le balisage DTD attendu est introuvable. Ligne 1, position 67.
  4. Description : Une exception non gérée s'est produite au moment de l'exécution de la demande Web actuelle. Contrôlez la trace de la pile pour plus d'informations sur l'erreur et son origine dans le code.
  5. Détails de l'exception: System.Xml.XmlException: Le balisage DTD attendu est introuvable. Ligne 1, position 67.
  6. Erreur source:
  7. Une exception non gérée s'est produite lors de l'exécution de la demande Web actuelle. Les informations relatives à l'origine et l'emplacement de l'exception peuvent être identifiées en utilisant la trace de la pile d'exception ci-dessous. 
  8. Trace de la pile:
  9. [XmlException: Le balisage DTD attendu est introuvable. Ligne 1, position 67.]
  10.    System.Xml.XmlScanner.ScanDtdContent() +918
  11.    System.Xml.Schema.DtdParser.ScanDtdContent() +141
  12.    System.Xml.Schema.DtdParser.ParseDtdContent() +56
  13.    System.Xml.Schema.DtdParser.ParseDocTypeDecl() +474
  14.    System.Xml.Schema.DtdParser.Parse() +61
  15.    System.Xml.XmlValidatingReader.UpdatePartialContentDTDHandling() +602
  16.    System.Xml.XmlValidatingReader.ReadWithCollectTextToken() +19
  17.    System.Xml.XmlValidatingReader.Read() +26
  18.    System.Xml.XmlLoader.ParseDocumentType(XmlDocumentType dtNode, Boolean bUseResolver, XmlResolver resolver) +321
  19.    System.Xml.XmlLoader.ParseDocumentType(XmlDocumentType dtNode) +50
  20.    System.Xml.XmlDocumentType..ctor(String name, String publicId, String systemId, String internalSubset, XmlDocument doc) +198
  21.    System.Xml.XmlDocument.CreateDocumentType(String name, String publicId, String systemId, String internalSubset) +41
  22.    xhtml11.Page..ctor() in g:\documents and settings\magicbuzz\vswebcache\magicweb.manga-torii.com\testdotnet\xhtml.cs:19
  23.    testdotnet.WebForm1.Page_Load(Object sender, EventArgs e) in g:\documents and settings\magicbuzz\vswebcache\magicweb.manga-torii.com\testdotnet\webform1.aspx.cs:20
  24.    System.Web.UI.Control.OnLoad(EventArgs e) +67
  25.    System.Web.UI.Control.LoadRecursive() +35
  26.    System.Web.UI.Page.ProcessRequestMain() +750
  27. --------------------------------------------------------------------------------
  28. Informations sur la version : Version Microsoft .NET Framework :1.1.4322.2300; Version ASP.NET :1.1.4322.2300


 
Avec ça, je ne vais pas aller loin !
 
Pourquoi il ne comprend pas la DTD ? Je viens de mettre à jour les parseur XML sur le serveur et le PC de dev, mais j'ai toujours l'erreur :
MSXML 3 SP 7 (releasé la semaine dernière)
MSXML 4 SP 2 (releasé en 2003, et y'a pas de nouvelle version depuis)
 
Si je suis le lien avec IE, j'ai bien la même erreur : MSXML ne veut pas valider la DTD officielle !
Comment faire ?
 
http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd


Message édité par Arjuna le 02-09-2005 à 23:41:30
Reply

Marsh Posté le 02-09-2005 à 23:37:21   

Reply

Marsh Posté le 09-09-2005 à 00:43:14    

Bon, finalement, j'ai réécrit la lib de création de code XHTML 1.0 Strict
 
C'est loin d'être fini (quoique si on fait abstraction de la validation du document, là j'ai plus que quelques heures de boulot), mais ça fait déjà du XHTML 1.0 Strict :)
 
C'est déjà pas mal :D
 
http://validator.w3.org/check?uri= [...] form1.aspx
 
Voilà la bête... Dites-moi si vous voyez des trucs grossiers qui vous choquent :)
 
Génération d'une page de test :

Code :
  1. private void Page_Load(object sender, System.EventArgs e)
  2. {
  3. Xhtml1.Page page = new Xhtml1.Page("Test de page" );
  4. page.metas.Add(new Meta("keyword", "test, de, clé" ));
  5. page.metas.Add(new Meta("description", "test de description" ));
  6. Div premierDiv = new Div();
  7. premierDiv.attribues.Add(new Xhtml1.Attribute("style", "color: red;" ));
  8. premierDiv.content = "Ceci est mon premier div. Il est rouge !<br />test";
  9. page.innerTags.Add(premierDiv);
  10. Div secondDiv = new Div();
  11. secondDiv.attribues.Add(new Xhtml1.Attribute("style", "border: solid 1px black;" ));
  12. secondDiv.content = "Test avec un joli lien {0}. Et un autre {1}.";
  13. Anchor premierAnchor = new Anchor();
  14. premierAnchor.attribues.Add(new Xhtml1.Attribute("href", "http://www.google.com" ));
  15. premierAnchor.content = "vers google";
  16. secondDiv.contentTags.Add(premierAnchor);
  17. Anchor secondAnchor = new Anchor();
  18. secondAnchor.attribues.Add(new Xhtml1.Attribute("href", "http://forum.hardware.fr/forum2.php?config=hardwarefr.inc&cat=10&post=76662&page=1&p=1&sondage=0&owntopic=1&trash=&trash_post=&print=0#t1195655" ));
  19. secondAnchor.content = "vers mon topic sur HFR";
  20. secondDiv.contentTags.Add(secondAnchor);
  21. Div troisiemeDiv = new Div();
  22. troisiemeDiv.attribues.Add(new Xhtml1.Attribute("style", "border: solid 1px pink;" ));
  23. troisiemeDiv.content = "truc dans un cadre rose";
  24. secondDiv.innerTags.Add(troisiemeDiv);
  25. page.innerTags.Add(secondDiv);
  26. Response.Write(page.ToString());
  27. }


 
Le namespace Xhtml1

Code :
  1. using System;
  2. namespace Xhtml1
  3. {
  4. public class Information
  5. {
  6.  private const string VERSION = "0";
  7.  private const string SUB_VERSION = "0";
  8.  private const string BUILTIN = "1";
  9.  private const string STATUS = "alpha";
  10.  public static string version
  11.  {
  12.   get
  13.   {
  14.    return VERSION + "." + SUB_VERSION + "." + BUILTIN + " (" + STATUS + " )";
  15.   }
  16.  }
  17. }
  18. /// <summary>
  19. /// Represents a web page using XHTML 1.0 standard
  20. /// </summary>
  21. public class Page
  22. {
  23.  private const string XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
  24.  private const string DOCTYPE_DECLARATION = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
  25.  private const string HTML_OPEN = "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-US\" lang=\"en-US\">\n";
  26.  private const string HEAD_OPEN = "\t<head>\n";
  27.  private const string CONTENT_TYPE_OPEN = "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
  28.  private const string GENERATOR_OPEN = "\t\t<meta name=\"generator\" content=\"MagicWeb {0}\" />\n";
  29.  private const string TITLE_TAG = "\t\t<title>{0}</title>\n";
  30.  private const string HEAD_CLOSE = "\t</head>\n";
  31.  private const string BODY_OPEN = "\t<body>\n";
  32.  private const string BODY_CLOSE = "\t</body>\n";
  33.  private const string HTML_CLOSE = "</html>";
  34.  private string title;
  35.  public System.Collections.ArrayList metas = new System.Collections.ArrayList();
  36.  public System.Collections.ArrayList innerTags = new System.Collections.ArrayList();
  37.  /// <summary>
  38.  /// Create a XHTML 1.0 empty page
  39.  /// </summary>
  40.  public Page(string title)
  41.  {
  42.   this.title = title;
  43.  }
  44.  public override string ToString()
  45.  {
  46.   string output = XML_DECLARATION;
  47.   output += DOCTYPE_DECLARATION;
  48.   output += HTML_OPEN;
  49.   output += HEAD_OPEN;
  50.   output += string.Format(TITLE_TAG, this.title);
  51.   output += CONTENT_TYPE_OPEN;
  52.   output += string.Format(GENERATOR_OPEN, Information.version);
  53.   foreach (Meta meta in this.metas)
  54.   {
  55.    output += meta.toString;
  56.   }
  57.   output += HEAD_CLOSE;
  58.   output += BODY_OPEN;
  59.   foreach (Tag tag in this.innerTags)
  60.   {
  61.    output += tag.ToString(2);
  62.   }
  63.   output += BODY_CLOSE;
  64.   return output + HTML_CLOSE;
  65.  }
  66. }
  67. public class Meta
  68. {
  69.  public string toString;
  70.  public Meta(string name, string content)
  71.  {
  72.   this.toString = "\t\t<meta name=\"" + System.Web.HttpUtility.HtmlAttributeEncode(name) + "\" content=\"" + System.Web.HttpUtility.HtmlAttributeEncode(content) + "\" />\n";
  73.  }
  74. }
  75. public class Attribute
  76. {
  77.  public string toString;
  78.  public Attribute(string name, string content)
  79.  {
  80.   this.toString = " " + name + "=\"" + System.Web.HttpUtility.HtmlAttributeEncode(content) + "\"";
  81.  }
  82. }
  83. public class Tag
  84. {
  85.  protected string tagName;
  86.  protected bool autoClose;
  87.  protected bool canBeMultiLine = true;
  88.  public string content;
  89.  public System.Collections.ArrayList attribues = new System.Collections.ArrayList();
  90.  public System.Collections.ArrayList innerTags = new System.Collections.ArrayList();
  91.  public System.Collections.ArrayList contentTags = new System.Collections.ArrayList();
  92.  public string ToString(int offset)
  93.  {
  94.   return this.ToString(offset, true);
  95.  }
  96.  public string ToString(int offset, bool embededInMultiLine)
  97.  {
  98.   string output = "";
  99.   if (embededInMultiLine)
  100.   {
  101.    output += "".PadRight(offset, '\t');
  102.   }
  103.   output += string.Format("<{0}", this.tagName);
  104.   foreach (Attribute attribute in this.attribues)
  105.   {
  106.    output += attribute.toString;
  107.   }
  108.   if (autoClose)
  109.   {
  110.    output += " />";
  111.    if (embededInMultiLine)
  112.    {
  113.     output += "\n";
  114.    }
  115.   }
  116.   else
  117.   {
  118.    output += ">";
  119.    if (embededInMultiLine && this.canBeMultiLine)
  120.    {
  121.     output += "\n";
  122.    }
  123.    if (this.content != string.Empty)
  124.    {
  125.                     System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex("({\\d+})", System.Text.RegularExpressions.RegexOptions.Compiled);
  126.     string[] tabContent = regex.Split(this.content);
  127.     System.Text.RegularExpressions.MatchCollection matches = regex.Matches(this.content);
  128.     for (int i = 0; i < tabContent.Length; i += 2)
  129.     {
  130.      if (!(i == 0 && this.content.StartsWith("{" )))
  131.      {
  132.       if (embededInMultiLine && this.canBeMultiLine)
  133.       {
  134.        output += "".PadLeft(offset + 1, '\t');
  135.       }
  136.       output += System.Web.HttpUtility.HtmlEncode(tabContent[i].Trim());
  137.       if (embededInMultiLine && this.canBeMultiLine)
  138.       {
  139.        output += "\n";
  140.       }
  141.      }
  142.      if (!(i == tabContent.Length - 1 && !this.content.EndsWith("}" )))
  143.      {
  144.       string res = matches[i / 2].Result("$1" );
  145.       int innerTagIndex = System.Convert.ToInt32(res.Substring(1, res.Length - 2));
  146.       output += ((Tag) this.contentTags[innerTagIndex]).ToString(offset + 1, this.canBeMultiLine);
  147.      }
  148.     }
  149.    }
  150.    foreach (Tag tag in this.innerTags)
  151.    {
  152.     if (tag != null)
  153.     {
  154.      output += tag.ToString(offset + 1);
  155.     }
  156.    }
  157.    if (embededInMultiLine && this.canBeMultiLine)
  158.    {
  159.     output += "".PadRight(offset, '\t');
  160.    }
  161.    output += string.Format("</{0}>", tagName);
  162.    if (embededInMultiLine)
  163.    {
  164.     output += "\n";
  165.    }
  166.   }
  167.   return output;
  168.  }
  169. }
  170. public class Div : Tag
  171. {
  172.  public Div()
  173.  {
  174.   this.tagName = "div";
  175.   this.autoClose = false;
  176.  }
  177. }
  178. public class Anchor : Tag
  179. {
  180.  public Anchor()
  181.  {
  182.   this.tagName = "a";
  183.   this.autoClose = false;
  184.   this.canBeMultiLine = false;
  185.  }
  186. }
  187. }


 
Sortie :

Code :
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
  4. <head>
  5.  <title>Test de page</title>
  6.  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  7.  <meta name="generator" content="MagicWeb 0.0.1 (alpha)" />
  8.  <meta name="keyword" content="test, de, clé" />
  9.  <meta name="description" content="test de description" />
  10. </head>
  11. <body>
  12.  <div style="color: red;">
  13.   Ceci est mon premier div. Il est rouge !&lt;br /&gt;test
  14.  </div>
  15.  <div style="border: solid 1px black;">
  16.   Test avec un joli lien
  17.   <a href="http://www.google.com">vers google</a>
  18.   . Et un autre
  19.   <a href="http://forum.hardware.fr/forum2.php?config=hardwarefr.inc&amp;cat=10&amp;post=76662&amp;page=1&amp;p=1&amp;sondage=0&amp;owntopic=1&amp;trash=&amp;trash_post=&amp;print=0#t1195655">vers mon topic sur HFR</a>
  20.   .
  21.   <div style="border: solid 1px pink;">
  22.    truc dans un cadre rose
  23.   </div>
  24.  </div>
  25. </body>
  26. </html>


http://magicweb.manga-torii.com/te [...] form1.aspx
 
Comme vous le voyez, c'est assez dépouillé pour le moment, y'a aucun contrôle, et c'est pas forcément ce qu'il y a de plus facile à utiliser :)
Mais bon... Sorti de ça, vous en pensez quoi ?
 
PS: M'emmerdez pas avec l'indentation du code généré, j'en ai chié deux heures pour avoir ce résultat (celui que je voulais ;)), donc il ne risque pas de changer :D


Message édité par Arjuna le 09-09-2005 à 00:52:17
Reply

Marsh Posté le 09-09-2005 à 10:02:41    

Oublie pas dans tes url d'escaper les & en les remplaçant par &amp;, sinon tu sera pas valide ;)
 
Et si t'as pas envie de te faire chier avec tout ça, attend la version 2.0 du framework qui est entièrement valide :D

Reply

Marsh Posté le 09-09-2005 à 12:04:39    

c'est pourquoi je vais un attributehtmlencode et un htmlencode sur les attribues et les contenus des balises ;)
 
sinon, ouais, je peux attendre la 2.0, mais bon... j'ai envie de jouer un peu, et surtout, je ne panne rien aux objets de base du C# (y'a des comportements que je trouve étrange et je ne trouve pas d'explication)

Reply

Marsh Posté le 09-09-2005 à 12:50:41    

ta variable "output" déclarée en string, c'est mal, étant donné que t'arrètes pas de la concaténer.
une string est une chaine constante, donc tu peux pas la modifier. chaque fois que tu écris

Code :
  1. output += "n";


tu créé une copie de output pour rajouter le caractère souhaité. ce qui ruine totalement les performances.
utilise un StringBuilder pour ce genre de truc, c'est fait pour ça


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 09-09-2005 à 14:17:07    

C'est en effet un point que j'ai vu depuis le début.
J'avais commencé à jouer avec un textwriter, mais après je ne savais plus quoi en faire :D
 
Après un entretien par mail avec un ancien collègue, il s'avère que je peux faire en rendu dans la méthode Render du Load, et que là j'ai accès à une fonction "htmltextwriter", qui correspond au flux de données utilisé par "Response.Write", ce qui me sembkle parfait :)

Reply

Marsh Posté le 09-09-2005 à 14:17:48    

Ouais, à chaque concaténation, le .NET va générer un StringBuilder (c'est fait lors de la compilation). Autant utiliser un StringBuilder direct, ça évitera d'en créer un à chaque concaténation

Reply

Marsh Posté le 09-09-2005 à 17:29:55    

FlorentG a écrit :

Ouais, à chaque concaténation, le .NET va générer un StringBuilder (c'est fait lors de la compilation). Autant utiliser un StringBuilder direct, ça évitera d'en créer un à chaque concaténation


euh, tu parles de la compilation JIT ? parce que la compilation en IL remplace operator+= par un appel à String.Concat()


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 09-09-2005 à 20:00:57    

Wouala.
J'ai modifié le code pour utiliser directement le HtmlWriter de l'objet Response :
 

Code :
  1. using System;
  2. using Xhtml1;
  3. namespace testdotnet
  4. {
  5. /// <summary>
  6. /// Summary description for WebForm1.
  7. /// </summary>
  8. public class WebForm1 : System.Web.UI.Page
  9. {
  10.  Xhtml1.Page page;
  11.  private void Page_Load(object sender, System.EventArgs e)
  12.  {
  13.   page = new Xhtml1.Page("Test de page" );
  14.   page.metas.Add(new Meta("keyword", "test, de, clé" ));
  15.   page.metas.Add(new Meta("description", "test de description" ));
  16.   Div premierDiv = new Div();
  17.   premierDiv.attribues.Add(new Xhtml1.Attribute("style", "color: red;" ));
  18.   premierDiv.content = "Ceci est mon premier div. Il est rouge !<br />test";
  19.   page.innerTags.Add(premierDiv);
  20.   Div secondDiv = new Div();
  21.   secondDiv.attribues.Add(new Xhtml1.Attribute("style", "border: solid 1px black;" ));
  22.   secondDiv.content = "Test avec un joli lien {0}. Et un autre {1}.";
  23.   Anchor premierAnchor = new Anchor();
  24.   premierAnchor.attribues.Add(new Xhtml1.Attribute("href", "http://www.google.com" ));
  25.   premierAnchor.content = "vers google";
  26.   secondDiv.contentTags.Add(premierAnchor);
  27.   Anchor secondAnchor = new Anchor();
  28.   secondAnchor.attribues.Add(new Xhtml1.Attribute("href", "http://forum.hardware.fr/forum2.php?config=hardwarefr.inc&cat=10&post=76662&page=1&p=1&sondage=0&owntopic=1&trash=&trash_post=&print=0#t1195655" ));
  29.   secondAnchor.content = "vers mon topic sur HFR";
  30.   secondDiv.contentTags.Add(secondAnchor);
  31.   Div troisiemeDiv = new Div();
  32.   troisiemeDiv.attribues.Add(new Xhtml1.Attribute("style", "border: solid 1px pink;" ));
  33.   troisiemeDiv.content = "truc dans un cadre rose";
  34.   secondDiv.innerTags.Add(troisiemeDiv);
  35.   page.innerTags.Add(secondDiv);
  36.  }
  37.  protected override void Render(System.Web.UI.HtmlTextWriter output)
  38.  {
  39.   page.Render(output);
  40.  }
  41.  #region Web Form Designer generated code
  42.  override protected void OnInit(EventArgs e)
  43.  {
  44.   //
  45.   // CODEGEN: This call is required by the ASP.NET Web Form Designer.
  46.   //
  47.   InitializeComponent();
  48.   base.OnInit(e);
  49.  }
  50.  /// <summary>
  51.  /// Required method for Designer support - do not modify
  52.  /// the contents of this method with the code editor.
  53.  /// </summary>
  54.  private void InitializeComponent()
  55.  {   
  56.   this.Load += new System.EventHandler(this.Page_Load);
  57.  }
  58.  #endregion
  59. }
  60. }


 

Code :
  1. using System;
  2. namespace Xhtml1
  3. {
  4. public class Information
  5. {
  6.  private const string VERSION = "0";
  7.  private const string SUB_VERSION = "0";
  8.  private const string BUILTIN = "1";
  9.  private const string STATUS = "alpha";
  10.  public static string version
  11.  {
  12.   get
  13.   {
  14.    return VERSION + "." + SUB_VERSION + "." + BUILTIN + " (" + STATUS + " )";
  15.   }
  16.  }
  17. }
  18. /// <summary>
  19. /// Represents a web page using XHTML 1.0 standard
  20. /// </summary>
  21. public class Page
  22. {
  23.  private const string XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
  24.  private const string DOCTYPE_DECLARATION = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
  25.  private const string HTML_OPEN = "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-US\" lang=\"en-US\">\n";
  26.  private const string HEAD_OPEN = "\t<head>\n";
  27.  private const string CONTENT_TYPE_OPEN = "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
  28.  private const string GENERATOR_OPEN = "\t\t<meta name=\"generator\" content=\"MagicWeb {0}\" />\n";
  29.  private const string TITLE_TAG = "\t\t<title>{0}</title>\n";
  30.  private const string HEAD_CLOSE = "\t</head>\n";
  31.  private const string BODY_OPEN = "\t<body>\n";
  32.  private const string BODY_CLOSE = "\t</body>\n";
  33.  private const string HTML_CLOSE = "</html>";
  34.  private string title;
  35.  public System.Collections.ArrayList metas = new System.Collections.ArrayList();
  36.  public System.Collections.ArrayList innerTags = new System.Collections.ArrayList();
  37.  /// <summary>
  38.  /// Create a XHTML 1.0 empty page
  39.  /// </summary>
  40.  public Page(string title)
  41.  {
  42.   this.title = title;
  43.  }
  44.  public void Render(System.Web.UI.HtmlTextWriter output)
  45.  {
  46.   output.Write(XML_DECLARATION);
  47.   output.Write(DOCTYPE_DECLARATION);
  48.   output.Write(HTML_OPEN);
  49.   output.Write(HEAD_OPEN);
  50.   output.Write(TITLE_TAG, this.title);
  51.   output.Write(CONTENT_TYPE_OPEN);
  52.   output.Write(GENERATOR_OPEN, Information.version);
  53.   foreach (Meta meta in this.metas)
  54.   {
  55.    output.Write(meta.toString);
  56.   }
  57.   output.Write(HEAD_CLOSE);
  58.   output.Write(BODY_OPEN);
  59.   foreach (Tag tag in this.innerTags)
  60.   {
  61.    tag.Render(2, output);
  62.   }
  63.   output.Write(BODY_CLOSE);
  64.   output.Write(HTML_CLOSE);
  65.  }
  66. }
  67. public class Meta
  68. {
  69.  public string toString;
  70.  public Meta(string name, string content)
  71.  {
  72.   this.toString = "\t\t<meta name=\"" + System.Web.HttpUtility.HtmlAttributeEncode(name) + "\" content=\"" + System.Web.HttpUtility.HtmlAttributeEncode(content) + "\" />\n";
  73.  }
  74. }
  75. public class Attribute
  76. {
  77.  public string toString;
  78.  public Attribute(string name, string content)
  79.  {
  80.   this.toString = " " + name + "=\"" + System.Web.HttpUtility.HtmlAttributeEncode(content) + "\"";
  81.  }
  82. }
  83. public class Tag
  84. {
  85.  protected string tagName;
  86.  protected bool autoClose;
  87.  protected bool canBeMultiLine = true;
  88.  public string content;
  89.  public System.Collections.ArrayList attribues = new System.Collections.ArrayList();
  90.  public System.Collections.ArrayList innerTags = new System.Collections.ArrayList();
  91.  public System.Collections.ArrayList contentTags = new System.Collections.ArrayList();
  92.  public void Render(int offset, System.Web.UI.HtmlTextWriter output)
  93.  {
  94.   this.Render(offset, output, true);
  95.  }
  96.  public void Render(int offset, System.Web.UI.HtmlTextWriter output, bool embededInMultiLine)
  97.  {
  98.   if (embededInMultiLine)
  99.   {
  100.    output.Write("".PadRight(offset, '\t'));
  101.   }
  102.   output.Write("<{0}", this.tagName);
  103.   foreach (Attribute attribute in this.attribues)
  104.   {
  105.    output.Write(attribute.toString);
  106.   }
  107.   if (autoClose)
  108.   {
  109.    output.Write(" />" );
  110.    if (embededInMultiLine)
  111.    {
  112.     output.Write("\n" );
  113.    }
  114.   }
  115.   else
  116.   {
  117.    output.Write(">" );
  118.    if (embededInMultiLine && this.canBeMultiLine)
  119.    {
  120.     output.Write("\n" );
  121.    }
  122.    if (this.content != string.Empty)
  123.    {
  124.                     System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex("({\\d+})", System.Text.RegularExpressions.RegexOptions.Compiled);
  125.     string[] tabContent = regex.Split(this.content);
  126.     System.Text.RegularExpressions.MatchCollection matches = regex.Matches(this.content);
  127.     for (int i = 0; i < tabContent.Length; i += 2)
  128.     {
  129.      if (!(i == 0 && this.content.StartsWith("{" )))
  130.      {
  131.       if (embededInMultiLine && this.canBeMultiLine)
  132.       {
  133.        output.Write("".PadLeft(offset + 1, '\t'));
  134.       }
  135.       output.Write(System.Web.HttpUtility.HtmlEncode(tabContent[i].Trim()));
  136.       if (embededInMultiLine && this.canBeMultiLine)
  137.       {
  138.        output.Write("\n" );
  139.       }
  140.      }
  141.      if (!(i == tabContent.Length - 1 && !this.content.EndsWith("}" )))
  142.      {
  143.       string res = matches[i / 2].Result("$1" );
  144.       int innerTagIndex = System.Convert.ToInt32(res.Substring(1, res.Length - 2));
  145.       ((Tag) this.contentTags[innerTagIndex]).Render(offset + 1, output, this.canBeMultiLine);
  146.      }
  147.     }
  148.    }
  149.    foreach (Tag tag in this.innerTags)
  150.    {
  151.     if (tag != null)
  152.     {
  153.      tag.Render(offset + 1, output);
  154.     }
  155.    }
  156.    if (embededInMultiLine && this.canBeMultiLine)
  157.    {
  158.     output.Write("".PadRight(offset, '\t'));
  159.    }
  160.    output.Write("</{0}>", tagName);
  161.    if (embededInMultiLine)
  162.    {
  163.     output.Write("\n" );
  164.    }
  165.   }
  166.  }
  167. }
  168. public class Div : Tag
  169. {
  170.  public Div()
  171.  {
  172.   this.tagName = "div";
  173.   this.autoClose = false;
  174.  }
  175. }
  176. public class Anchor : Tag
  177. {
  178.  public Anchor()
  179.  {
  180.   this.tagName = "a";
  181.   this.autoClose = false;
  182.   this.canBeMultiLine = false;
  183.  }
  184. }
  185. }


 
Et en plus ça marche :D

Reply

Marsh Posté le 09-09-2005 à 23:52:10    

Harkonnen a écrit :

euh, tu parles de la compilation JIT ? parce que la compilation en IL remplace operator+= par un appel à String.Concat()


Ah mince, effectivement :jap: Donc en fait le problème de String.Concat, c'est qu'il alloue une nouvelle string à chaque fois. Alors pour quelques concaténations ça va, mais au dessus le StringBuilder est mieux je crois bien...

Reply

Marsh Posté le 09-09-2005 à 23:52:10   

Reply

Marsh Posté le 10-09-2005 à 13:14:08    

Dans tous les cas, un TextWriter est toujours mieu, et maintenant c'est ce que j'utilise :p ;)

Reply

Marsh Posté le 10-09-2005 à 22:04:29    

Salut.
 
Bon, j'ai modifié mon code pour que la class Page contienne directement des tags plutôt que du code en dur.
En bref, j'ai deux lignes en dur (doctype et déclaration xml), puis un tag private "htmlTag", qui contient deux tags visibles publiquement (head et body), qui contiennent les metas (maintenant gérées comme de symples tags) et le contenu de la page.
 
Maintenant, j'attaque mon application en tant que telle.
 
Je veux créer des pages "templates", à savoir qu'elles vont contenir un certain nombre prédéfini de tags, dont les valeurs sont allimentées par la base de données.
 
Je fais ma class "HomePage". Elle est dérivée de Xhtml1.Page.
Seulement, le contructeur de Page est plutôt long, et je ne veux en aucun cas le recopier, car il se pourrait bien que ça change (et puis de toute façon c'est plus propre sans recopier).
 
J'ai bien la solution batarde, à la fin du constructeur, d'appeler une méthode privée "OnInitialize()" appelée depuis le constructeur de Page, vide dans Page, mais remplie dans les classes dérivées. Seulement, je trouve ça plutôt gore.
 
Je voudrais donc que mon constructeur déclenche un évènement "Initialize" que je pourrai récupérer dans ma classe dérivée (enfin... je sais pas trop comment).
Mais de toute façon, je ne comprends rien aux évènement, malgré la lecture des 3 exemples de la doc (je pige pas une ligne !)
 
En résumé, j'ai :
 

Code :
  1. public class Page
  2. {
  3.     public Page()
  4.     {
  5.         // Fait plein de trucs importants
  6.     }
  7. }
  8. public class HomePage : Page
  9. {
  10.      public HomePage()
  11.      {
  12.           // Nan, y'a pas moyen de recopier ce que fait le constructeur
  13.           // Mais j'ai plein de trucs à ajouter à la fin !
  14.      }
  15. }


 
Je ne veux pas que le lancement de ces instructions supplémentaires soient visibles pour le développeur, donc il faut que ça se lance à l'instanciation de la class.

Reply

Marsh Posté le 10-09-2005 à 22:09:09    

Trouvé une solution
 
Autre problème (ça ne veut pas dire que j'ai résolu le précédent)
 
Un tag <meta> ne peut en aucun cas contenir des tags.
Seulement, le fait qu'il soit dérivé de ma class générique Tag fait qu'il a une propriété public "innerTags".
J'ai pas envie de la cacher, ça ferait trop de boulot, et je doute que ce soit possible.
 
Par contre, je voudrais bien que lorsque j'ajoute un tag (je peux passer "innerTags" en private, et faire deux méthodes "addTag()" et "removeTag()" dans la class mère, je teste le type du tag ajouter, et que je contrôle en fonction de la liste des tags autorisés par le doctype (ce sera à la main, j'ai pas envie de me faire un parseur de DocType juste pour faire cette vérif).
 
Ainsi, je pourrai ne rien mettre dans la liste, ou mettre la liste des tags acceptés, et vérifier que les tags ajoutés correspondent.
 
Comment comparer le type d'un objet à... un type ?
 
En bref :
 

Code :
  1. public class Head : Tag
  2. {
  3.     public Head()
  4.     {
  5.         this.tagName = "head";
  6.         this.canBeMultiline = true;
  7.         this.autoClose = false;
  8.     }
  9.     public void addTag(Tag tag)
  10.     {
  11.         if (tag.type != Meta && tag.type != Title && tag.type != Link)
  12.         {
  13.             throw new System.Exception(string.Form("Balise <{0}> interdite dans la balise <{1}>.", tag.tagName, this.tagName));
  14.         }
  15.         this.innerTags.add(tag);
  16.     }   
  17. }


 
 
 
Solution trouvée :
 

Code :
  1. if (this.GetType() == System.Type.GetType("Xhtml1.Tags.Div" ))
  2.     {
  3.      this.content += "je suis un div";
  4.     }


 
J'ai mis ça dans ma class "Tag", dans le Render(). Et ça marche, y'a que mes objets de type "Div : Tag" qui ont ce texte à la fin de leur contenu :bounce:


Message édité par Arjuna le 10-09-2005 à 22:23:31
Reply

Marsh Posté le 10-09-2005 à 23:24:54    

Vous pensez que je peuc faire mieu comme vérif ?
 

Code :
  1. public void AddInnerTag(Tag tag)
  2.   {
  3.    bool canInsert = false;
  4.    string[] tabVerif = authorizedChildren.Split(';');
  5.    foreach (string name in tabVerif)
  6.    {
  7.     if (name != string.Empty)
  8.     {
  9.      if (tag.GetType() == System.Type.GetType("Xhtml1.Tags." + name))
  10.      {
  11.       canInsert = true;
  12.       break;
  13.      }
  14.     }
  15.    }
  16.    if (canInsert)
  17.    {
  18.     this.innerTags.Add(tag);
  19.    }
  20.    else
  21.    {
  22.     throw new System.Exception(string.Format("Tag <{0}> can't be embeded in tag <{1}>.", tag.tagName, this.tagName));
  23.    }
  24.   }

Reply

Marsh Posté le 10-09-2005 à 23:38:09    

euh.... là, j'ai un autre problème... :heink:
 
Fichier "xhtml1.cs" :

Code :
  1. using System;
  2. namespace Xhtml1
  3. {
  4. /// <summary>
  5. /// Represents a web page using XHTML 1.0 standard
  6. /// </summary>
  7. public class Page
  8. {
  9.  private const string XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
  10.  private const string DOCTYPE_DECLARATION = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
  11.  private System.Collections.ArrayList allTags = new System.Collections.ArrayList();
  12.  public Tags.Head head = new Tags.Head();
  13.  public Tags.Body body = new Tags.Body();
  14.  private Tags.Html htmlTag = new Tags.Html();
  15.  /// <summary>
  16.  /// Create a XHTML 1.0 empty page
  17.  /// </summary>
  18.  public Page(string title)
  19.  {
  20.   // Fait plein de trucs
  21.  }
  22. }
  23. }


 
Fichier "magicweb.cs" :

Code :
  1. using System;
  2. using System.Data.SqlClient;
  3. using Xhtml1;
  4. namespace MagicWeb
  5. {
  6. namespace Templates
  7. {
  8.  public class HomePage : Xhtml1.Page
  9.  {
  10.   public void test()
  11.   {
  12.   }
  13.  }
  14. }
  15. }


 
Quand je compile, j'ai l'erreur :
G:\Documents and Settings\MagicBuzz\VSWebCache\magicweb.manga-torii.com\testdotnet\magicweb.cs(33): No overload for method 'Page' takes '0' arguments
 
J'ai aussi l'erreur si je rajoute dans "MagicWeb.Templates.HomePage le contructeurs suivant :

Code :
  1. public HomePage(string title)
  2.   {
  3.   }


 
Rigoureusement la même erreur !
 
Alors que deux lignes plus loin, toujours dans le namespace "MagicWeb" j'ai :

Code :
  1. namespace Module
  2. {
  3.  public class Title : Xhtml1.Tags.Header1
  4.  {
  5.   public Title(string title)
  6.   {
  7.    this.content = title;
  8.   }
  9.   public void truc()
  10.   {
  11.    if (this.GetType() == System.Type.GetType("Xhtml1.Tags.Meta" ))
  12.    {
  13.    }
  14.   }
  15.  }
  16. }


 
 
Et ça plante pas !
A noter que Tag dont hérite Header1 n'a pas de constructeur.
Mais Header1 a un constructeur.


Message édité par Arjuna le 10-09-2005 à 23:39:28
Reply

Marsh Posté le 10-09-2005 à 23:42:45    

Grrrr, corrigé avec l'ajout d'un constructeur sans paramètre qui appelle une méthode privée "Init" et déplacement du code du contructeur dans cette méthode.
 
Pfffff, y'a des fois, je trouve ça vraiment étrange ce truc !

Reply

Marsh Posté le 10-09-2005 à 23:43:09    

Vous oubliez pas ce problème hein ? :D
 
Parcequ'en fait, c'est celui qui me bloque vraiment ;)
http://forum.hardware.fr/forum2.ph [...] 0#t1196793

Reply

Marsh Posté le 10-09-2005 à 23:50:28    

Grrrr ! Cette histoire de constructeur vide obligatoire m'a mis la puce à l'oreille.
 
Finalement, un nouveau constructeur dans la classe dérivée ne vient pas écraser le constructeur de la class mère. Et zou ! Problème corrigé :D

Reply

Marsh Posté le 11-09-2005 à 00:01:27    

Bon, vous allez me prendre pour un gogole mais c'est pas grave :D
Finalement, j'ai viré mon test sur les types (qui m'en a pourtant pris du temps !) et remplacé par un test sur le tagName (évident depuis le départ...) pour deux raisons :
 
Mes "modules" (groupes de tags genre "menu", "news", etc.) ne sont pas des types reconnus lors de l'insertion d'un tag (donc je ne peux pas savoir si c'est valide ou non), et aussi tout bêtement parceque si je me base sur le tagName, rien ne m'empêche par la suite de tenter de rendre compatible mon bordel avec la lecture d'un DOCTYPE... Donc c'est mieu :D


Message édité par Arjuna le 11-09-2005 à 00:04:18
Reply

Marsh Posté le 11-09-2005 à 01:34:10    

Yes !
 
Bon, c'est cool, ça avance bien :)
 
Maintenant, je vais pouvoir m'atteler à la construction des templates et de la base, le code côté ASPX marche 100% :)
 
http://magicweb.manga-torii.com/te [...] form1.aspx
 
Généré uniquement à partir de :

Code :
  1. cnx.Open();
  2. MagicWeb.Application.Run(cnx, this);
  3. cnx.Close();


 
:sol:
 
Il va tout chercher à partir du nom de domaine et du numéro de page passé en paramètre (ici c'est vide, donc la HP est prise par défaut) dans la base pour savoir quel template utiliser, le contenu de la page, les méta, et s'il faut mettre ou non le copyright :)
 
Chuis trop fort (pourriez m'aider un peu, c'est pas facile de se jeter des roses à soit-même :o)


Message édité par Arjuna le 11-09-2005 à 01:35:25
Reply

Marsh Posté le 11-09-2005 à 01:36:55    

Le point noir, ça va être pour gérer les styles en dynamique...
J'hésite entre les fournir à la volée dans le HEAD, ou appeler une page dynamique avec un link, sâchant que pour la mise en cache, ça ne marchera pas...

Reply

Marsh Posté le 11-09-2005 à 11:30:08    

Page dynamique avec un lien [:dawa] Pourquoi ça marchera pas la mise en cache ?

Reply

Marsh Posté le 11-09-2005 à 12:46:18    

Normalement, les navigateur ne mettent pas un cache les pages ayant un paramètre.
 
Donc la seule solution envisageable, c'est de faire une page dynamique ne prenant pas de lien, et qui renvoie le style global de tout le site.
Moi j'aurais préféré n'envoyer que les styles spécifiques à la page en cours.
 
M'enfin c'est pas bien grave.

Reply

Marsh Posté le 11-09-2005 à 19:16:25    

Fait de la réécriture d'URL [:dawa]

Reply

Marsh Posté le 11-09-2005 à 19:30:09    

et la marmotte...

Reply

Marsh Posté le 11-09-2005 à 19:30:53    

j'en chie assez déjà en faisant des trucs basiques ;)
 
je viens de passer l'après-midi à réarranger mon code et avoir une gestion des pages 404 correcte alors... ;)

Reply

Marsh Posté le 11-09-2005 à 19:44:52    

Je crois bien que je l'ai d'ailleurs trop bien gérée, maintenant on n'est jamais dans le répertoire de l'appli mais à la racinde du site (qui ne supporte que l'ASP, pas .NET) :D

Reply

Marsh Posté le 11-09-2005 à 19:45:20    

Mais ça marche quand même :sol:
 
MagicBuzz, le premier gars qui arrive à faire tourner un site web en dehors de son répertoire :D

Reply

Marsh Posté le 12-09-2005 à 22:23:59    

Alors...
 
Maintenant, j'ai un souci d'héritage multiple ou je sais pas quoi. Appellez-le comme vous voulez :)
 
En gros :
 

Code :
  1. public abstract class Tag
  2. {
  3.     public string tagName;
  4. }
  5. public class Div : Tag
  6. {
  7.     public Div()
  8.     {
  9.         this.tagName = "div";
  10.     }
  11. }
  12. public abstract class Module : ?1
  13. {
  14.     public bool linkedToDatabase;
  15. }
  16. public class News : ?2
  17. {
  18.     public News()
  19.     {
  20.         this.linkedToDatabase = true;
  21.     }
  22. }


 
En résumé :
 
J'ai une class générique Tag qui représente un tag xhtml. Dedans, y'a une série de propriétés et méthodes relatives au rendu du tag et stockages/rendu de son contenu/ses fils.
 
La class Div est hérité de Tag, et initialise un certain nombre de propriétés hérités de Tag aux valeurs spécifiques de Tag.
 
Maintenant, mon module News correspond à un Div. Je l'avais au départ fait hériter de Div donc. Dedans, il y a le code qui permet d'aller cherches les infos spécifiques à une news. Ensuite, j'ajoute mon module News dans un array de Tag qui est dans ma page afin d'en faire le rendu, tous modules confondus.
 
Seulement, dans l'optique de rendre mon code complètement générique au niveau de l'ajout des modules dans ma page, j'ai besoin de faire une boucle sur un type abstrait. Je désire donc faire une class Module, dont tous mes modules vont hériter.
 
Seulement, là j'ai un problème : Pour que ça marche Module doit hériter de Tag. Mais si News hérite de Module, alors il perd l'information comme quoi c'est un Div ! Comment faire ?
 
J'ai bien la solution de boucler avec le type abstrait Tag, mais je ne veux pas : mes modules ont des propriétés et des méthodes partagées spécifiques à un Module, et je ne veux pas polluer mon Tag avec, d'autant plus qu'ils ne sont pas dans le même namespace !
Notamment, un Module a une propriété "linkedToDatabase" qui va me permettre, lors du rendu des informations, de savoir si je dois aller chercher sont contenu dans la base de données ou si j'ai déjà tout initilisé dans son contructeur.
 
Comment faire ?


Message édité par Arjuna le 12-09-2005 à 22:28:24
Reply

Marsh Posté le 12-09-2005 à 22:30:54    

Si c'est pas possible, j'ai toujours la possibilité de stocker cette info dans ma table des modules, mais dans l'absolu, je préfèrerais que ce soit fait proprement dans le code plutôt qu'une info bidon stockée dans la base.

Reply

Marsh Posté le 12-09-2005 à 22:35:38    

Arf, je crois que j'ai trouvé tout seul comme un grand...
 

Code :
  1. public class News : Mod, Tag


 
:)

Reply

Marsh Posté le 12-09-2005 à 22:37:26    

Déjà on parle pas de "tag", mais d'éléments. Comme je l'ai dit ailleurs, on a un élément div, composé d'un tag ouvrant (<div> ) et d'un tag fermant (</div> ) ;) ;)
 
Sinon, j'aurais plutôt choisi une aggrégation pour la news : Une news n'est pas une div, elle contient une div. Par contre, c'est bien un module, donc news va bien dériver de module...


Message édité par FlorentG le 12-09-2005 à 22:38:14
Reply

Marsh Posté le 13-09-2005 à 00:16:46    

Merdoum...
Comment je fais pour imbriquer des requêtes dans des "while" ?
 
J'obtiens cette erreur :
 

Citation :

Un DataReader associé à cette connexion est déjà ouvert, il doit être tout d'abord fermé.

Reply

Marsh Posté le 13-09-2005 à 00:19:51    

Finalement, j'ai laissé tomber le problème de l'héritage multiple. En effet, en faisant calculer mes éléments par le constructeur du module, je me retrouve avec un objet compatible avec la class Tag.
 
Sinon, pour moi, chaque module sera effectivement un élément en tant que tel :
 
Si une news est constituée d'un titre, de deux paragraphes et d'une date, j'aurai en sortie :
 

Code :
  1. <div class="news">
  2.    <h1>Titre</h1>
  3.    <p>Premier paragraphe</p>
  4.    <p>Second paragraphe</p>
  5.    <span class="date">13/09/2005</span>
  6. </div>

Reply

Marsh Posté le 13-09-2005 à 00:21:06    

En fait, j'aurai systématiquement un conteneur pour chacun de mes modules.
 
D'un point de vue sémantique, c'est pas forcément super, mais c'est ce qui me garanti qu'il ne va pas y avoir de cafouillage de pinceaux lors du rendu... surtout lors de l'application de la CSS !

Reply

Marsh Posté le 13-09-2005 à 00:29:12    

Arjuna a écrit :

Merdoum...
Comment je fais pour imbriquer des requêtes dans des "while" ?
 
J'obtiens cette erreur :
 

Citation :

Un DataReader associé à cette connexion est déjà ouvert, il doit être tout d'abord fermé.



je suis quand même pas obligé de créer une nouvelle connection à chaque imbrication ? parceque sinon, bonjour les perfs :/

Reply

Marsh Posté le 13-09-2005 à 09:32:10    

Arjuna a écrit :

En fait, j'aurai systématiquement un conteneur pour chacun de mes modules.
 
D'un point de vue sémantique, c'est pas forcément super, mais c'est ce qui me garanti qu'il ne va pas y avoir de cafouillage de pinceaux lors du rendu... surtout lors de l'application de la CSS !


Pour la div news, utilise plutôt un id, non ?
 
Sinon d'un point de vue sémantique, c'est parfait. L'élément <div> servant à grouper des éléments et à donner de la structure à un doc XHTML, t'as parfaitement le droit d'en mettre un pour news. D'ailleurs pour un <h1> (ou <h2> ), la recommandation stipule bien que ça sert à donner un titre à la section dans laquelle il est, y'a un exemple donné où le h se trouve dans un div, justement pour "créer" une section...

Reply

Marsh Posté le 13-09-2005 à 14:00:10    

Ben j'essaie de faire un truc 100% full compliant XHTML non seulement d'un point de vue syntaxique, mais aussi sémantique.
 
Sinon, pour mon problème de requêtes imbriquées ?
 
En fait, j'ai une boucle qui va rechercher les modules d'une page (donc issue d'une requête).
Ensuite, pour chaque module, je l'instancie, et ce dernier peut nécessiter d'aller chercher dans la base (pour le module "Menu", je dois récupérer la liste des pages, pour le module "News", je dois chercher le contenu de la news, etc.).
Donc j'ai besoin de faire une requête à alors que j'ai déjà un dataset ouvert.
 
Pire : pour le module "ListNews", je dois aller chercher la liste des news, puis, dans leur contenu afin de trouver la date et le titre. Donc j'ai 3 niveaux d'imbrication...
 
J'avais donc l'idée de faire deux connections, mais dans l'absolu, le nombre d'imbrications n'est pas défini (et peut être infini). Pour cette raison, multiplier les connection dans le scope global ne marche pas.
Enfin, ouvrir et fermer des connections à chaque niveau, d'un point de vue perfs, je crains que ce soit totalement catastrophique !
 
Comment résoudre ce problème ?

Reply

Marsh Posté le 13-09-2005 à 14:49:26    

Normalement, t'as juste à clore le DataReader, et non la connexion :??:

Reply

Marsh Posté le 13-09-2005 à 17:11:42    

Oui, mais si je suis en train de le lire dans une boucle, j'ai pas trop envie de le fermer...
Donc pour ne pas avoir à le fermer, je dois ouvrir une nouvelle connection.
Hors ça, c'est hors de question.

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed