Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

JsUtils.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /*
  2. * Copyright 2011, The gwtquery team.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.gwt.query.client.js;
  17. import static com.google.gwt.query.client.GQuery.browser;
  18. import com.google.gwt.core.client.GWT;
  19. import com.google.gwt.core.client.JavaScriptObject;
  20. import com.google.gwt.core.client.JsArray;
  21. import com.google.gwt.core.client.JsArrayMixed;
  22. import com.google.gwt.core.client.JsonUtils;
  23. import com.google.gwt.dom.client.Document;
  24. import com.google.gwt.dom.client.Element;
  25. import com.google.gwt.dom.client.Node;
  26. import com.google.gwt.dom.client.NodeList;
  27. import com.google.gwt.query.client.Function;
  28. import com.google.gwt.query.client.GQuery;
  29. import com.google.gwt.query.client.Properties;
  30. import com.google.gwt.user.client.Command;
  31. import com.google.gwt.user.client.DOM;
  32. /**
  33. * A bunch of utility methods for GQuery.
  34. */
  35. public class JsUtils {
  36. /**
  37. * A Function which wraps a javascript function.
  38. */
  39. public static class JsFunction extends Function implements Command {
  40. private JavaScriptObject jso = null;
  41. public JsFunction(JavaScriptObject f) {
  42. if (JsUtils.isFunction(f)) {
  43. jso = f;
  44. }
  45. }
  46. public boolean equals(Object obj) {
  47. return jso.equals(obj);
  48. }
  49. public int hashCode() {
  50. return jso.hashCode();
  51. }
  52. private native Object exec(JavaScriptObject f, Object data) /*-{
  53. return @com.google.gwt.query.client.js.JsCache::gwtBox(*)([ f(data) ]);
  54. }-*/;
  55. public void f() {
  56. if (jso != null) {
  57. setArguments(exec(jso, arguments(0)));
  58. }
  59. }
  60. public void execute() {
  61. f();
  62. }
  63. }
  64. /**
  65. * Wraps a GQuery function into a native javascript one so as we can
  66. * export Java methods without using JSNI.
  67. */
  68. public static native JavaScriptObject wrapFunction(Function f) /*-{
  69. return function(r) {
  70. var o = @java.util.ArrayList::new()();
  71. for (i in arguments) {
  72. r = @com.google.gwt.query.client.js.JsCache::gwtBox(*)([arguments[i]]);
  73. o.@java.util.ArrayList::add(Ljava/lang/Object;)(r);
  74. }
  75. o = o.@java.util.ArrayList::toArray()();
  76. f.@com.google.gwt.query.client.Function::setArguments([Ljava/lang/Object;)(o);
  77. return f.@com.google.gwt.query.client.Function::fe([Ljava/lang/Object;)(o);
  78. }
  79. }-*/;
  80. /**
  81. * Default JsUtils implementation.
  82. */
  83. public static class JsUtilsImpl {
  84. public Properties parseJSON(String json) {
  85. return JsonUtils.safeEval(json);
  86. }
  87. public native String JSON2String(JavaScriptObject o) /*-{
  88. return $wnd.JSON.stringify(o);
  89. }-*/;
  90. public native Element parseXML(String xml) /*-{
  91. return new DOMParser().parseFromString(xml, "text/xml").documentElement;
  92. }-*/;
  93. public String text(Element e) {
  94. return e.getInnerText();
  95. }
  96. public JsArray<Element> unique(JsArray<Element> a) {
  97. JsArray<Element> ret = JavaScriptObject.createArray().cast();
  98. JsCache cache = JsCache.create();
  99. for (int i = 0; i < a.length(); i++) {
  100. Element e = a.get(i);
  101. int id = e.hashCode();
  102. if (!cache.exists(id)) {
  103. cache.putNumber(id, 1);
  104. ret.push(e);
  105. }
  106. }
  107. return ret;
  108. }
  109. public native String XML2String(JavaScriptObject o) /*-{
  110. return (new XMLSerializer()).serializeToString(o);
  111. }-*/;
  112. }
  113. /**
  114. * IE JsUtils implemetation.
  115. */
  116. public static class JsUtilsImplIE6 extends JsUtilsImpl {
  117. @Override
  118. public Properties parseJSON(String json) {
  119. return JsonUtils.unsafeEval(json);
  120. }
  121. @Override
  122. public String JSON2String(JavaScriptObject js) {
  123. // This is a very basic implementation for IE6/IE7 of JSON.stringify
  124. // If many people demand a better one we could consider to use json2.js
  125. // @see https://github.com/douglascrockford/JSON-js/blob/master/json2.js
  126. Properties prop = js.cast();
  127. String ret = "";
  128. for (String k : prop.keys()) {
  129. String ky = k.matches("\\d+") ? k : "\"" + k + "\"";
  130. JsCache o = prop.getArray(k).cast();
  131. if (o != null) {
  132. ret += ky + ":[";
  133. for (int i = 0, l = o.length(); i < l; i++) {
  134. Properties p = o.<JsCache> cast().getJavaScriptObject(i);
  135. if (p != null) {
  136. ret += p.toJsonString() + ",";
  137. } else {
  138. ret += "\"" + o.getString(i) + "\",";
  139. }
  140. }
  141. ret += "],";
  142. } else {
  143. Properties p = prop.getJavaScriptObject(k);
  144. if (p != null) {
  145. ret += ky + ":" + p.toJsonString() + ",";
  146. } else {
  147. ret += ky + ":\"" + prop.getStr(k) + "\",";
  148. }
  149. }
  150. }
  151. return "{" + ret.replaceAll(",\\s*([\\]}]|$)", "$1")
  152. .replaceAll("([:,\\[])\"(-?[\\d\\.]+|null|false|true)\"", "$1$2")
  153. + "}";
  154. }
  155. @Override
  156. public native Element parseXML(String xml) /*-{
  157. var d = new ActiveXObject("Microsoft.XmlDom");
  158. d.loadXML(xml);
  159. return d.documentElement;
  160. }-*/;
  161. @Override
  162. public String text(Element e) {
  163. return isXML(e) ? xmlText(e) : super.text(e);
  164. }
  165. @Override
  166. public JsArray<Element> unique(JsArray<Element> a) {
  167. // in IE6 XML elements does not support adding hashId to the object
  168. if (browser.ie6 && isXML(a.get(0))) {
  169. return a;
  170. }
  171. return super.unique(a);
  172. }
  173. @Override
  174. public native String XML2String(JavaScriptObject o) /*-{
  175. return o.xml;
  176. }-*/;
  177. private native String xmlText(Element e) /*-{
  178. return e.text;
  179. }-*/;
  180. }
  181. private static JsUtilsImpl utilsImpl = GWT.create(JsUtilsImpl.class);
  182. /**
  183. * Returns a property present in a javascript object.
  184. */
  185. public static <T> T prop(JavaScriptObject o, Object id, Class<? extends T> type) {
  186. return o == null ? null : o.<JsCache> cast().get(id, type);
  187. }
  188. /**
  189. * Returns a property present in a javascript object.
  190. */
  191. public static <T> T prop(JavaScriptObject o, Object id) {
  192. return o == null ? null : o.<JsCache> cast().<T> get(id);
  193. }
  194. /**
  195. * Set a property to a javascript object.
  196. */
  197. public static void prop(JavaScriptObject o, Object id, Object val) {
  198. if (o != null) {
  199. o.<JsCache> cast().put(id, val);
  200. }
  201. }
  202. /**
  203. * Execute a native javascript function.
  204. */
  205. public static <T> T exec(JavaScriptObject jsFunction, Object... args) {
  206. assert isFunction(jsFunction);
  207. return jsni(jsFunction, "call", jsFunction, args);
  208. }
  209. /**
  210. * Assign a function to a property of the window object.
  211. */
  212. public static void export(String name, Function f) {
  213. export(GQuery.window, name, f);
  214. }
  215. /**
  216. * Export a function as a property of a javascript object.
  217. */
  218. public static void export(JavaScriptObject o, String name, Function f) {
  219. prop(o, name, (Object)(f != null ? wrapFunction(f) : null));
  220. }
  221. /**
  222. * Camelize style property names.
  223. * for instance: font-name -> fontName
  224. */
  225. public static native String camelize(String s) /*-{
  226. return s.replace(/\-(\w)/g, function(all, letter) {
  227. return letter.toUpperCase();
  228. });
  229. }-*/;
  230. /**
  231. * Merge the oldNodes list into the newNodes one. If oldNodes is null, a new
  232. * list will be created and returned. If oldNodes is not null, a new list will
  233. * be created depending on the create flag.
  234. */
  235. public static NodeList<Element> copyNodeList(NodeList<Element> oldNodes,
  236. NodeList<Element> newNodes, boolean create) {
  237. NodeList<Element> ret = oldNodes == null || create ? JsNodeArray.create()
  238. : oldNodes;
  239. JsCache idlist = JsCache.create();
  240. for (int i = 0; oldNodes != null && i < oldNodes.getLength(); i++) {
  241. Element e = oldNodes.getItem(i);
  242. idlist.put(e.hashCode(), 1);
  243. if (create) {
  244. ret.<JsNodeArray> cast().addNode(e, i);
  245. }
  246. }
  247. for (int i = 0, l = newNodes.getLength(), j = ret.getLength(); i < l; i++) {
  248. Element e = newNodes.getItem(i);
  249. if (!idlist.exists(e.hashCode())) {
  250. ret.<JsNodeArray> cast().addNode(newNodes.getItem(i), j++);
  251. }
  252. }
  253. return ret;
  254. }
  255. /**
  256. * Use the method in the gquery class.
  257. * $(elem).cur(prop, force);
  258. */
  259. @Deprecated
  260. public static double cur(Element elem, String prop, boolean force) {
  261. return GQuery.$(elem).cur(prop, force);
  262. }
  263. /**
  264. * Compare two numbers using javascript equality.
  265. */
  266. public static native boolean eq(double s1, double s2) /*-{
  267. return s1 == s2;
  268. }-*/;
  269. /**
  270. * Compare two objects using javascript equality.
  271. */
  272. public static native boolean eq(Object s1, Object s2) /*-{
  273. return s1 == s2;
  274. }-*/;
  275. /**
  276. * Returns the owner document element of an element.
  277. */
  278. public static Document getOwnerDocument(Node n) {
  279. return n == null || !isElement(n) ? null : n.getNodeType() == Node.DOCUMENT_NODE
  280. ? n.<Document> cast() : n.getOwnerDocument();
  281. }
  282. /**
  283. * Check if an object has a property with <code>name</code> defined.
  284. * It supports dots in the name meaning checking nested properties.
  285. *
  286. * Example:
  287. * <pre>
  288. * // Check whether a browser supports touch events
  289. * hasProperty(window, "ontouchstart");
  290. * </pre>
  291. */
  292. public static native boolean hasProperty(JavaScriptObject o, String name)/*-{
  293. var p = name.split('.');
  294. for (var i in p) {
  295. if (!(o && p[i] in o)) return false;
  296. o = o[p[i]];
  297. }
  298. return true;
  299. }-*/;
  300. /**
  301. * Check whether an element has an attribute, this is here since GWT Element.getAttribute
  302. * implementation returns an empty string instead of null when the attribute is not
  303. * present.
  304. */
  305. public static native boolean hasAttribute(Element o, String name) /*-{
  306. return !!(o && o.getAttribute(name) !== null);
  307. }-*/;
  308. /**
  309. * Hyphenize style property names.
  310. * for instance: fontName -> font-name
  311. */
  312. public static native String hyphenize(String name) /*-{
  313. return name.replace(/([A-Z])/g, "-$1").toLowerCase();
  314. }-*/;
  315. /**
  316. * Check is a javascript object can be used as an array.
  317. */
  318. public static native boolean isArray(JavaScriptObject o) /*-{
  319. return Object.prototype.toString.call(o) == '[object Array]'
  320. || typeof o.length == 'number';
  321. }-*/;
  322. /**
  323. * Check is a javascript object is a FormData.
  324. */
  325. public static native boolean isFormData(JavaScriptObject o) /*-{
  326. return Object.prototype.toString.call(o) == '[object FormData]';
  327. }-*/;
  328. /**
  329. * Return whether the event was prevented.
  330. */
  331. public static native boolean isDefaultPrevented(JavaScriptObject e) /*-{
  332. return e.defaultPrevented || e.returnValue === false || e.getPreventDefault
  333. && e.getPreventDefault() ? true : false;
  334. }-*/;
  335. /**
  336. * Return whether a node is detached to the DOM.
  337. *
  338. * Be careful : This method works only on node that should be inserted within the body node.
  339. */
  340. public static boolean isDetached(Node n) {
  341. assert n != null;
  342. if ("html".equalsIgnoreCase(n.getNodeName())) {
  343. return false;
  344. }
  345. return !getOwnerDocument(n).getBody().isOrHasChild(n);
  346. }
  347. /**
  348. * Check is a javascript object can be cast to an Element.
  349. */
  350. public static native boolean isElement(Object o) /*-{
  351. return !!o && 'nodeType' in o && 'nodeName' in o;
  352. }-*/;
  353. /**
  354. * Check is a javascript object can be cast to an Event.
  355. */
  356. public static boolean isEvent(JavaScriptObject o) {
  357. return hasProperty(o, "currentTarget");
  358. }
  359. /**
  360. * Check is a javascript object is a function.
  361. */
  362. public static native boolean isFunction(JavaScriptObject o) /*-{
  363. return Object.prototype.toString.call(o) == '[object Function]';
  364. }-*/;
  365. /**
  366. * Check is a javascript can be cast to a node list.
  367. */
  368. public static native boolean isNodeList(JavaScriptObject o) /*-{
  369. var r = Object.prototype.toString.call(o);
  370. return r == '[object HTMLCollection]' || r == '[object NodeList]'
  371. || (typeof o == 'object' && o.length && o[0] && o[0].tagName) ? true : false;
  372. }-*/;
  373. /**
  374. * Check is a javascript object is a Window.
  375. */
  376. public static boolean isWindow(JavaScriptObject o) {
  377. return hasProperty(o, "alert");
  378. }
  379. /**
  380. * Check if an element is a DOM or a XML node.
  381. */
  382. public static boolean isXML(Node o) {
  383. return o == null ? false
  384. : !"HTML".equals(getOwnerDocument(o).getDocumentElement().getNodeName());
  385. }
  386. /**
  387. * Load an external javascript library. The inserted script replaces the
  388. * element with the given id in the document.
  389. *
  390. * @deprecated use {@link com.google.gwt.query.client.plugins.ajax.Ajax#loadScript(String)}
  391. */
  392. @Deprecated
  393. public static void loadScript(String url, String id) {
  394. GQuery gs = GQuery.$(DOM.createElement("script"));
  395. GQuery gp = GQuery.$("#" + id).parent();
  396. if (gp.size() != 1) {
  397. gp = GQuery.$(GQuery.document.getBody());
  398. }
  399. GQuery.$("#" + id).remove();
  400. gp.append(gs.attr("src", url).attr("type", "text/javascript").attr("id", id));
  401. }
  402. /**
  403. * Return the element which is truth in the double scope.
  404. */
  405. public static native double or(double s1, double s2) /*-{
  406. return s1 || s2;
  407. }-*/;
  408. /**
  409. * Return the element which is truth in the javascript scope.
  410. */
  411. public static native <T> T or(T s1, T s2) /*-{
  412. return s1 || s2;
  413. }-*/;
  414. /**
  415. * Parses a json string returning a Object with useful method to get the
  416. * content.
  417. */
  418. public static Properties parseJSON(String json) {
  419. try {
  420. return utilsImpl.parseJSON(json);
  421. } catch (Exception e) {
  422. if (!GWT.isProdMode()) {
  423. System.err.println("Error while parsing json: " + e.getMessage() + ".\n" + json);
  424. }
  425. return Properties.create();
  426. }
  427. }
  428. /**
  429. * Parses a xml string and return the xml document element which can then be
  430. * passed to GQuery to create a typical GQuery object that can be traversed
  431. * and manipulated.
  432. */
  433. public static Element parseXML(String xml) {
  434. return utilsImpl.parseXML(xml);
  435. }
  436. public static String text(Element e) {
  437. return utilsImpl.text(e);
  438. }
  439. /**
  440. * Utility method to cast objects in production.
  441. * Useful for casting native implementations to interfaces like JsInterop
  442. */
  443. public static native <T> T cast(Object o) /*-{
  444. return o;
  445. }-*/;
  446. /**
  447. * Utility method to cast objects to array of string in production.
  448. */
  449. public static native String[] castArrayString(Object a) /*-{
  450. return a
  451. }-*/;
  452. /**
  453. * Call any arbitrary function present in a Javascript object.
  454. * It checks the existence of the function and object hierarchy before executing it.
  455. * It's very useful in order to avoid writing jsni blocks for very simple snippets.
  456. *
  457. * Note that GWT 3.0 jsinterop will come with a method similar, so we might deprecate
  458. * this in the future.
  459. *
  460. * Example
  461. * <pre>
  462. * // Create a svg node in our document.
  463. * Element svg = jsni(document, "createElementNS", "http://www.w3.org/2000/svg", "svg");
  464. * // Append it to the dom
  465. * $(svg).appendTo(document);
  466. * // show the svg element in the debug console
  467. * jsni("console.log", svg);
  468. * </pre>
  469. *
  470. * @param jso the object containing the method to execute
  471. * @param meth the literal name of the function to call, dot separators are allowed.
  472. * @param args an array with the arguments to pass to the function.
  473. * @return the java ready boxed object returned by the jsni method or null, if the
  474. * call return a number we will get a Double, if it returns a boolean we get a java
  475. * Boolean, strings comes as java String, otherwise we get the javascript object.
  476. */
  477. public static <T> T jsni(JavaScriptObject jso, String meth, Object... args) {
  478. return runJavascriptFunction(jso, meth, args);
  479. }
  480. /**
  481. * Run any arbitrary function in javascript scope using the window as the base object.
  482. * It checks the existence of the function and object hierarchy before executing it.
  483. * It's very useful in order to avoid writing jsni blocks for very simple snippets.
  484. *
  485. * Note that GWT 3.0 jsinterop will come with a method similar, so we might deprecate
  486. * this in the future.
  487. *
  488. * Example
  489. * <pre>
  490. * // Create a svg node in our document.
  491. * Element svg = jsni("document.createElementNS", "http://www.w3.org/2000/svg", "svg");
  492. * // Append it to the dom
  493. * $(svg).appendTo(document);
  494. * // show the svg element in the debug console
  495. * jsni("console.log", svg);
  496. * </pre>
  497. *
  498. * @param meth the literal name of the function to call, dot separators are allowed.
  499. * @param args an array with the arguments to pass to the function.
  500. * @return the java ready boxed object returned by the jsni method or null, if the
  501. * call return a number we will get a Double, if it returns a boolean we get a java
  502. * Boolean, strings comes as java String, otherwise we get the javascript object.
  503. */
  504. public static <T> T jsni(String meth, Object... args) {
  505. return runJavascriptFunction(null, meth, args);
  506. }
  507. /**
  508. * Call via jsni any arbitrary function present in a Javascript object.
  509. *
  510. * It's thought for avoiding to create jsni methods to call external functions and
  511. * facilitate the writing of js wrappers.
  512. *
  513. * Example
  514. * <pre>
  515. * // Create a svg node in our document.
  516. * Element svg = runJavascriptFunction(document, "createElementNS", "http://www.w3.org/2000/svg", "svg");
  517. * // Append it to the dom
  518. * $(svg).appendTo(document);
  519. * </pre>
  520. *
  521. * @param o the javascript object where the function is, it it is null we use window.
  522. * @param meth the literal name of the function to call, dot separators are allowed.
  523. * @param args an array with the arguments to pass to the function.
  524. * @return the java ready boxed object returned by the jsni method or null, if the
  525. * call return a number we will get a Double, if it returns a boolean we get a java
  526. * Boolean, strings comes as java String, otherwise we get the javascript object.
  527. *
  528. * @deprecated use jsni instead.
  529. */
  530. public static <T> T runJavascriptFunction(JavaScriptObject o, String meth, Object... args) {
  531. return runJavascriptFunctionImpl(o, meth, JsObjectArray.create().add(args).<JsArrayMixed>cast());
  532. }
  533. private static native <T> T runJavascriptFunctionImpl(JavaScriptObject o, String meth, JsArrayMixed args) /*-{
  534. var f = o || $wnd, p = meth.split('.');
  535. for (var i in p) {
  536. o = f;
  537. f = f[p[i]];
  538. if (!f) return null;
  539. }
  540. return @com.google.gwt.query.client.js.JsUtils::isFunction(*)(f)
  541. && @com.google.gwt.query.client.js.JsCache::gwtBox(*)([f.apply(o, args)]);
  542. }-*/;
  543. /**
  544. * Check if a number is true in the javascript scope.
  545. */
  546. public static native boolean truth(double a) /*-{
  547. return a ? true : false;
  548. }-*/;
  549. /**
  550. * Check if an object is true in the javascript scope.
  551. */
  552. public static native boolean truth(Object a) /*-{
  553. return a ? true : false;
  554. }-*/;
  555. /**
  556. * Remove duplicates from an elements array.
  557. */
  558. public static JsArray<Element> unique(JsArray<Element> a) {
  559. return utilsImpl.unique(a);
  560. }
  561. public static String XML2String(JavaScriptObject js) {
  562. return utilsImpl.XML2String(js);
  563. }
  564. public static String JSON2String(JavaScriptObject js) {
  565. return utilsImpl.JSON2String(js);
  566. }
  567. /**
  568. * Returns a QueryString representation of a JavascriptObject.
  569. *
  570. * TODO: jquery implementation accepts a second parameter (traditional)
  571. */
  572. public static String param(JavaScriptObject js) {
  573. Properties prop = js.cast();
  574. String ret = "";
  575. for (String k : prop.keys()) {
  576. ret += ret.isEmpty() ? "" : "&";
  577. JsCache o = prop.getArray(k).cast();
  578. if (o != null) {
  579. for (int i = 0, l = o.length(); i < l; i++) {
  580. ret += i > 0 ? "&" : "";
  581. Properties p = o.<JsCache> cast().getJavaScriptObject(i);
  582. if (p != null) {
  583. ret += k + "[]=" + p.toJsonString();
  584. } else {
  585. ret += k + "[]=" + o.getString(i);
  586. }
  587. }
  588. } else {
  589. Properties p = prop.getJavaScriptObject(k);
  590. if (p != null) {
  591. ret += k + "=" + p.tostring();
  592. } else {
  593. String v = prop.getStr(k);
  594. if (v != null && !v.isEmpty() && !"null".equalsIgnoreCase(v)) {
  595. ret += k + "=" + v;
  596. }
  597. }
  598. }
  599. }
  600. return ret;
  601. }
  602. }