AspElements.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. //
  2. // System.Web.Compilation.AspElements
  3. //
  4. // Authors:
  5. // Gonzalo Paniagua Javier ([email protected])
  6. //
  7. // (C) 2002 Ximian, Inc (http://www.ximian.com)
  8. //
  9. using System;
  10. using System.Collections;
  11. using System.Reflection;
  12. using System.Text;
  13. using System.Web.UI;
  14. using System.Web.UI.HtmlControls;
  15. using System.Web.UI.WebControls;
  16. namespace System.Web.Compilation
  17. {
  18. public enum ElementType
  19. {
  20. TAG,
  21. PLAINTEXT
  22. }
  23. public abstract class Element
  24. {
  25. private ElementType elementType;
  26. public Element (ElementType type)
  27. {
  28. elementType = type;
  29. }
  30. public ElementType GetElementType
  31. {
  32. get { return elementType; }
  33. }
  34. } // class Element
  35. public class PlainText : Element
  36. {
  37. private StringBuilder text;
  38. public PlainText () : base (ElementType.PLAINTEXT)
  39. {
  40. text = new StringBuilder ();
  41. }
  42. public PlainText (StringBuilder text) : base (ElementType.PLAINTEXT)
  43. {
  44. this.text = text;
  45. }
  46. public PlainText (string text) : this ()
  47. {
  48. this.text.Append (text);
  49. }
  50. public void Append (string more)
  51. {
  52. text.Append (more);
  53. }
  54. public string Text
  55. {
  56. get { return text.ToString (); }
  57. }
  58. public override string ToString ()
  59. {
  60. return "PlainText: " + Text;
  61. }
  62. }
  63. public enum TagType
  64. {
  65. DIRECTIVE,
  66. HTML,
  67. HTMLCONTROL,
  68. SERVERCONTROL,
  69. INLINEVAR,
  70. INLINECODE,
  71. CLOSING,
  72. SERVEROBJECT,
  73. PROPERTYTAG,
  74. CODERENDER,
  75. DATABINDING,
  76. NOTYET
  77. }
  78. /*
  79. * Attributes and values are stored in a couple of ArrayList in Add ().
  80. * When MakeHash () is called, they are converted to a Hashtable. If there are any
  81. * attributes duplicated it throws an ArgumentException.
  82. *
  83. * The [] operator works with the Hashtable if the values are in it, otherwise
  84. * it uses the ArrayList's.
  85. *
  86. * Why? You can have a tag in HTML like <a att="value" att="xxx">, but not in tags
  87. * marked runat=server and Hashtable requires the key to be unique.
  88. *
  89. */
  90. public class TagAttributes
  91. {
  92. private Hashtable atts_hash;
  93. private ArrayList keys;
  94. private ArrayList values;
  95. private bool got_hashed;
  96. public TagAttributes ()
  97. {
  98. got_hashed = false;
  99. keys = new ArrayList ();
  100. values = new ArrayList ();
  101. }
  102. private void MakeHash ()
  103. {
  104. atts_hash = new Hashtable (new CaseInsensitiveHashCodeProvider (),
  105. new CaseInsensitiveComparer ());
  106. for (int i = 0; i < keys.Count; i++)
  107. atts_hash.Add (keys [i], values [i]);
  108. got_hashed = true;
  109. keys = null;
  110. values = null;
  111. }
  112. public bool IsRunAtServer ()
  113. {
  114. return got_hashed;
  115. }
  116. public void Add (object key, object value)
  117. {
  118. if (key != null && value != null &&
  119. 0 == String.Compare ((string) key, "runat", true) &&
  120. 0 == String.Compare ((string) value, "server", true))
  121. MakeHash ();
  122. if (got_hashed)
  123. atts_hash.Add (key, value);
  124. else {
  125. keys.Add (key);
  126. values.Add (value);
  127. }
  128. }
  129. public ICollection Keys
  130. {
  131. get { return (got_hashed ? atts_hash.Keys : keys); }
  132. }
  133. private int CaseInsensitiveSearch (string key)
  134. {
  135. // Hope not to have many attributes when the tag is not a server tag...
  136. for (int i = 0; i < keys.Count; i++){
  137. if (0 == String.Compare ((string) keys [i], key, true))
  138. return i;
  139. }
  140. return -1;
  141. }
  142. public object this [object key]
  143. {
  144. get {
  145. if (got_hashed)
  146. return atts_hash [key];
  147. int idx = CaseInsensitiveSearch ((string) key);
  148. if (idx == -1)
  149. return null;
  150. return values [idx];
  151. }
  152. set {
  153. if (got_hashed)
  154. atts_hash [key] = value;
  155. else {
  156. int idx = CaseInsensitiveSearch ((string) key);
  157. keys [idx] = value;
  158. }
  159. }
  160. }
  161. public int Count
  162. {
  163. get { return (got_hashed ? atts_hash.Count : keys.Count);}
  164. }
  165. public bool IsDataBound (string att)
  166. {
  167. if (att == null || !got_hashed)
  168. return false;
  169. return (att.StartsWith ("<%#") && att.EndsWith ("%>"));
  170. }
  171. public override string ToString ()
  172. {
  173. string ret = "";
  174. string value;
  175. foreach (string key in Keys){
  176. value = (string) this [key];
  177. value = value == null ? "" : value;
  178. ret += key + "=" + value + " ";
  179. }
  180. return ret;
  181. }
  182. }
  183. public class Tag : Element
  184. {
  185. protected string tag;
  186. protected TagType tagType;
  187. protected TagAttributes attributes;
  188. protected bool self_closing;
  189. protected bool hasDefaultID;
  190. private static int ctrlNumber = 1;
  191. internal Tag (Tag other) :
  192. this (other.tag, other.attributes, other.self_closing)
  193. {
  194. this.tagType = other.tagType;
  195. }
  196. public Tag (string tag, TagAttributes attributes, bool self_closing) :
  197. base (ElementType.TAG)
  198. {
  199. if (tag == null)
  200. throw new ArgumentNullException ();
  201. this.tag = tag;
  202. this.attributes = attributes;
  203. this.tagType = TagType.NOTYET;
  204. this.self_closing = self_closing;
  205. this.hasDefaultID = false;
  206. }
  207. public string TagID
  208. {
  209. get { return tag; }
  210. }
  211. public TagType TagType
  212. {
  213. get { return tagType; }
  214. }
  215. public bool SelfClosing
  216. {
  217. get { return self_closing; }
  218. }
  219. public TagAttributes Attributes
  220. {
  221. get { return attributes; }
  222. }
  223. public string PlainHtml
  224. {
  225. get {
  226. StringBuilder plain = new StringBuilder ();
  227. plain.Append ('<');
  228. if (tagType == TagType.CLOSING)
  229. plain.Append ('/');
  230. plain.Append (tag);
  231. if (attributes != null){
  232. plain.Append (' ');
  233. foreach (string key in attributes.Keys){
  234. plain.Append (key);
  235. if (attributes [key] != null){
  236. plain.Append ("=\"");
  237. plain.Append ((string) attributes [key]);
  238. plain.Append ("\" ");
  239. }
  240. }
  241. }
  242. if (self_closing)
  243. plain.Append ('/');
  244. plain.Append ('>');
  245. return plain.ToString ();
  246. }
  247. }
  248. public override string ToString ()
  249. {
  250. return TagID + " " + Attributes + " " + self_closing;
  251. }
  252. public bool HasDefaultID
  253. {
  254. get { return hasDefaultID; }
  255. }
  256. protected void SetNewID ()
  257. {
  258. if (attributes == null)
  259. attributes = new TagAttributes ();
  260. attributes.Add ("ID", GetDefaultID ());
  261. hasDefaultID = true;
  262. }
  263. public static string GetDefaultID ()
  264. {
  265. return "_control" + ctrlNumber++;
  266. }
  267. }
  268. public class CloseTag : Tag
  269. {
  270. public CloseTag (string tag) : base (tag, null, false)
  271. {
  272. tagType = TagType.CLOSING;
  273. }
  274. }
  275. public class Directive : Tag
  276. {
  277. private static Hashtable directivesHash;
  278. private static string [] page_atts = { "AspCompat", "AutoEventWireup ", "Buffer",
  279. "ClassName", "ClientTarget", "CodePage",
  280. "CompilerOptions", "ContentType", "Culture", "Debug",
  281. "Description", "EnableSessionState", "EnableViewState",
  282. "EnableViewStateMac", "ErrorPage", "Explicit",
  283. "Inherits", "Language", "LCID", "ResponseEncoding",
  284. "Src", "SmartNavigation", "Strict", "Trace",
  285. "TraceMode", "Transaction", "UICulture",
  286. "WarningLevel" };
  287. private static string [] control_atts = { "AutoEventWireup", "ClassName", "CompilerOptions",
  288. "Debug", "Description", "EnableViewState",
  289. "Explicit", "Inherits", "Language", "Strict", "Src",
  290. "WarningLevel" };
  291. private static string [] import_atts = { "namespace" };
  292. private static string [] implements_atts = { "interface" };
  293. private static string [] assembly_atts = { "name", "src" };
  294. private static string [] register_atts = { "tagprefix", "tagname", "Namespace",
  295. "Src", "Assembly" };
  296. private static string [] outputcache_atts = { "Duration", "Location", "VaryByControl",
  297. "VaryByCustom", "VaryByHeader", "VaryByParam" };
  298. private static string [] reference_atts = { "page", "control" };
  299. static Directive ()
  300. {
  301. InitHash ();
  302. }
  303. private static void InitHash ()
  304. {
  305. CaseInsensitiveHashCodeProvider provider = new CaseInsensitiveHashCodeProvider ();
  306. CaseInsensitiveComparer comparer = new CaseInsensitiveComparer ();
  307. directivesHash = new Hashtable (provider, comparer);
  308. // Use Hashtable 'cause is O(1) in Contains (ArrayList is O(n))
  309. Hashtable valid_attributes = new Hashtable (provider, comparer);
  310. foreach (string att in page_atts) valid_attributes.Add (att, null);
  311. directivesHash.Add ("PAGE", valid_attributes);
  312. valid_attributes = new Hashtable (provider, comparer);
  313. foreach (string att in control_atts) valid_attributes.Add (att, null);
  314. directivesHash.Add ("CONTROL", valid_attributes);
  315. valid_attributes = new Hashtable (provider, comparer);
  316. foreach (string att in import_atts) valid_attributes.Add (att, null);
  317. directivesHash.Add ("IMPORT", valid_attributes);
  318. valid_attributes = new Hashtable (provider, comparer);
  319. foreach (string att in implements_atts) valid_attributes.Add (att, null);
  320. directivesHash.Add ("IMPLEMENTS", valid_attributes);
  321. valid_attributes = new Hashtable (provider, comparer);
  322. foreach (string att in register_atts) valid_attributes.Add (att, null);
  323. directivesHash.Add ("REGISTER", valid_attributes);
  324. valid_attributes = new Hashtable (provider, comparer);
  325. foreach (string att in assembly_atts) valid_attributes.Add (att, null);
  326. directivesHash.Add ("ASSEMBLY", valid_attributes);
  327. valid_attributes = new Hashtable (provider, comparer);
  328. foreach (string att in outputcache_atts) valid_attributes.Add (att, null);
  329. directivesHash.Add ("OUTPUTCACHE", valid_attributes);
  330. valid_attributes = new Hashtable (provider, comparer);
  331. foreach (string att in reference_atts) valid_attributes.Add (att, null);
  332. directivesHash.Add ("REFERENCE", valid_attributes);
  333. }
  334. public Directive (string tag, TagAttributes attributes) :
  335. base (tag, attributes, true)
  336. {
  337. CheckAttributes ();
  338. tagType = TagType.DIRECTIVE;
  339. }
  340. private void CheckAttributes ()
  341. {
  342. Hashtable atts;
  343. if (!(directivesHash [tag] is Hashtable))
  344. throw new ApplicationException ("Unknown directive: " + tag);
  345. atts = (Hashtable) directivesHash [tag];
  346. foreach (string att in attributes.Keys){
  347. if (!atts.Contains (att))
  348. throw new ApplicationException ("Attribute " + att +
  349. " not valid for tag " + tag);
  350. }
  351. }
  352. public static bool IsDirectiveID (string id)
  353. {
  354. return directivesHash.Contains (id);
  355. }
  356. public override string ToString ()
  357. {
  358. return "Directive: " + tag;
  359. }
  360. }
  361. public class ServerObjectTag : Tag
  362. {
  363. public ServerObjectTag (Tag tag) :
  364. base (tag.TagID, tag.Attributes, tag.SelfClosing)
  365. {
  366. tagType = TagType.SERVEROBJECT;
  367. if (!attributes.IsRunAtServer ())
  368. throw new ApplicationException ("<object> without runat=server");
  369. if (attributes.Count != 3 || !SelfClosing || ObjectID == null || ObjectClass == null)
  370. throw new ApplicationException ("Incorrect syntax: <object id=\"name\" " +
  371. "class=\"full.class.name\" runat=\"server\" />");
  372. }
  373. public string ObjectID
  374. {
  375. get { return (string) attributes ["id"]; }
  376. }
  377. public string ObjectClass
  378. {
  379. get { return (string) attributes ["class"]; }
  380. }
  381. }
  382. public class HtmlControlTag : Tag
  383. {
  384. private Type control_type;
  385. private bool is_container;
  386. private static Hashtable controls;
  387. private static Hashtable inputTypes;
  388. private static void InitHash ()
  389. {
  390. controls = new Hashtable (new CaseInsensitiveHashCodeProvider (),
  391. new CaseInsensitiveComparer ());
  392. controls.Add ("A", typeof (HtmlAnchor));
  393. controls.Add ("BUTTON", typeof (HtmlButton));
  394. controls.Add ("FORM", typeof (HtmlForm));
  395. controls.Add ("IMG", typeof (HtmlImage));
  396. controls.Add ("INPUT", "INPUT");
  397. controls.Add ("SELECT", typeof (HtmlSelect));
  398. controls.Add ("TABLE", typeof (HtmlTable));
  399. controls.Add ("TD", typeof (HtmlTableCell));
  400. controls.Add ("TH", typeof (HtmlTableCell));
  401. controls.Add ("TR", typeof (HtmlTableRow));
  402. controls.Add ("TEXTAREA", typeof (HtmlTextArea));
  403. inputTypes = new Hashtable (new CaseInsensitiveHashCodeProvider (),
  404. new CaseInsensitiveComparer ());
  405. inputTypes.Add ("BUTTON", typeof (HtmlInputButton));
  406. inputTypes.Add ("SUBMIT", typeof (HtmlInputButton));
  407. inputTypes.Add ("RESET", typeof (HtmlInputButton));
  408. inputTypes.Add ("CHECKBOX", typeof (HtmlInputCheckBox));
  409. inputTypes.Add ("FILE", typeof (HtmlInputFile));
  410. inputTypes.Add ("HIDDEN", typeof (HtmlInputHidden));
  411. inputTypes.Add ("IMAGE", typeof (HtmlInputImage));
  412. inputTypes.Add ("RADIO", typeof (HtmlInputRadioButton));
  413. inputTypes.Add ("TEXT", typeof (HtmlInputText));
  414. inputTypes.Add ("PASSWORD", typeof (HtmlInputText));
  415. }
  416. static HtmlControlTag ()
  417. {
  418. InitHash ();
  419. }
  420. public HtmlControlTag (string tag, TagAttributes attributes, bool self_closing) :
  421. base (tag, attributes, self_closing)
  422. {
  423. SetData ();
  424. if (attributes == null || attributes ["ID"] == null)
  425. SetNewID ();
  426. }
  427. public HtmlControlTag (Tag source_tag) :
  428. this (source_tag.TagID, source_tag.Attributes, source_tag.SelfClosing)
  429. {
  430. }
  431. private void SetData ()
  432. {
  433. tagType = TagType.HTMLCONTROL;
  434. if (!(controls [tag] is string)){
  435. control_type = (Type) controls [tag];
  436. if (control_type == null)
  437. control_type = typeof (HtmlGenericControl);
  438. is_container = (0 != String.Compare (tag, "img", true));
  439. } else {
  440. string type_value = (string) attributes ["TYPE"];
  441. if (type_value== null)
  442. throw new ArgumentException ("INPUT tag without TYPE attribute!!!");
  443. control_type = (Type) inputTypes [type_value];
  444. //TODO: what does MS with this one?
  445. if (control_type == null)
  446. throw new ArgumentException ("Unknown input type -> " + type_value);
  447. is_container = false;
  448. self_closing = true; // All <input ...> are self-closing
  449. }
  450. }
  451. public Type ControlType
  452. {
  453. get { return control_type; }
  454. }
  455. public string ControlID
  456. {
  457. get { return (string) attributes ["ID"]; }
  458. }
  459. public bool IsContainer
  460. {
  461. get { return is_container; }
  462. }
  463. public override string ToString ()
  464. {
  465. string ret = "HtmlControlTag: " + tag + " Name: " + ControlID + "Type:" +
  466. control_type.ToString () + "\n\tAttributes:\n";
  467. foreach (string key in attributes.Keys){
  468. ret += "\t" + key + "=" + attributes [key];
  469. }
  470. return ret;
  471. }
  472. }
  473. public enum ChildrenKind
  474. {
  475. NONE,
  476. /*
  477. * Children must be ASP.NET server controls. Literal text is passed as LiteralControl.
  478. * Child controls and text are added using AddParsedSubObject ().
  479. */
  480. CONTROLS,
  481. /*
  482. * Children must correspond to properties of the parent control. No literal text allowed.
  483. */
  484. PROPERTIES,
  485. /*
  486. * Special case used inside <columns>...</columns>
  487. * Only allow DataGridColumn and derived classes.
  488. */
  489. DBCOLUMNS,
  490. /*
  491. * Special case for list controls (ListBox, DropDownList...)
  492. */
  493. LISTITEM,
  494. /* For HtmlSelect children. They are <option> tags that must
  495. * be treated as ListItem
  496. */
  497. OPTION
  498. }
  499. // TODO: support for ControlBuilderAttribute that may be used in custom controls
  500. public class AspComponent : Tag
  501. {
  502. private Type type;
  503. private string alias;
  504. private string control_type;
  505. private bool is_close_tag;
  506. private bool allow_children;
  507. private ChildrenKind children_kind;
  508. private string defaultPropertyName;
  509. private ChildrenKind GuessChildrenKind (Type type)
  510. {
  511. object [] custom_atts = type.GetCustomAttributes (true);
  512. foreach (object custom_att in custom_atts){
  513. if (custom_att is ParseChildrenAttribute){
  514. /* FIXME
  515. * When adding full support for custom controls, we gotta
  516. * bear in mind the pca.DefaultProperty value
  517. */
  518. ParseChildrenAttribute pca = custom_att as ParseChildrenAttribute;
  519. defaultPropertyName = pca.DefaultProperty;
  520. /* this property will be true for all controls derived from
  521. * WebControls. */
  522. if (pca.ChildrenAsProperties == false)
  523. return ChildrenKind.CONTROLS;
  524. else if (defaultPropertyName == "")
  525. return ChildrenKind.PROPERTIES;
  526. else
  527. return ChildrenKind.LISTITEM;
  528. }
  529. }
  530. return ChildrenKind.NONE;
  531. }
  532. private static bool GuessAllowChildren (Type type)
  533. {
  534. PropertyInfo controls = type.GetProperty ("Controls");
  535. if (controls == null)
  536. return false;
  537. MethodInfo getm = controls.GetGetMethod ();
  538. object control_instance = Activator.CreateInstance (type);
  539. object control_collection = getm.Invoke (control_instance, null);
  540. return (!(control_collection is System.Web.UI.EmptyControlCollection));
  541. }
  542. public AspComponent (Tag input_tag, Type type) :
  543. base (input_tag)
  544. {
  545. tagType = TagType.SERVERCONTROL;
  546. this.is_close_tag = input_tag is CloseTag;
  547. this.type = type;
  548. this.defaultPropertyName = "";
  549. this.allow_children = GuessAllowChildren (type);
  550. if (input_tag.SelfClosing)
  551. this.children_kind = ChildrenKind.NONE;
  552. else if (type == typeof (System.Web.UI.WebControls.DataGridColumn) ||
  553. type.IsSubclassOf (typeof (System.Web.UI.WebControls.DataGridColumn)))
  554. this.children_kind = ChildrenKind.PROPERTIES;
  555. else if (type == typeof (System.Web.UI.WebControls.ListItem))
  556. this.children_kind = ChildrenKind.CONTROLS;
  557. else
  558. this.children_kind = GuessChildrenKind (type);
  559. int pos = input_tag.TagID.IndexOf (':');
  560. alias = tag.Substring (0, pos);
  561. control_type = tag.Substring (pos + 1);
  562. if (attributes == null || attributes ["ID"] == null)
  563. SetNewID ();
  564. }
  565. public Type ComponentType
  566. {
  567. get { return type; }
  568. }
  569. public string ControlID
  570. {
  571. get { return (string) attributes ["ID"]; }
  572. }
  573. public bool IsCloseTag
  574. {
  575. get { return is_close_tag; }
  576. }
  577. public bool AllowChildren
  578. {
  579. get { return allow_children; }
  580. }
  581. public ChildrenKind ChildrenKind
  582. {
  583. get { return children_kind; }
  584. }
  585. public string DefaultPropertyName
  586. {
  587. get { return defaultPropertyName; }
  588. }
  589. public override string ToString ()
  590. {
  591. return type.ToString () + " Alias: " + alias + " ID: " + (string) attributes ["id"];
  592. }
  593. }
  594. public class PropertyTag : Tag
  595. {
  596. private Type type;
  597. private string name;
  598. public PropertyTag (Tag tag, Type type, string name)
  599. : base (tag)
  600. {
  601. tagType = TagType.PROPERTYTAG;
  602. SetNewID ();
  603. this.name = name;
  604. this.type = type;
  605. }
  606. public Type PropertyType
  607. {
  608. get { return type; }
  609. }
  610. public string PropertyID
  611. {
  612. get { return (string) attributes ["ID"]; }
  613. }
  614. public string PropertyName
  615. {
  616. get { return name; }
  617. }
  618. }
  619. public class CodeRenderTag : Tag
  620. {
  621. private string code;
  622. private bool isVarName;
  623. public CodeRenderTag (bool isVarName, string code) : base ("", null, false)
  624. {
  625. tagType = TagType.CODERENDER;
  626. this.isVarName = isVarName;
  627. this.code = code.Trim ();
  628. }
  629. public string Code
  630. {
  631. get { return code; }
  632. }
  633. public bool IsVarName
  634. {
  635. get { return isVarName; }
  636. }
  637. public string AsText
  638. {
  639. get { return "<%" + (IsVarName ? "=" : "") + Code + "%>"; }
  640. }
  641. }
  642. public class DataBindingTag : Tag
  643. {
  644. private string data;
  645. public DataBindingTag (string data) : base ("", null, false)
  646. {
  647. tagType = TagType.DATABINDING;
  648. this.data = data.Trim ();
  649. }
  650. public string Data
  651. {
  652. get { return data; }
  653. }
  654. public string AsText
  655. {
  656. get { return "<%#" + Data + "%>"; }
  657. }
  658. }
  659. }