View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import org.apache.velocity.tools.config.DefaultKey;
27  import org.apache.velocity.tools.generic.SafeConfig;
28  
29  /**
30   * <b>NOTE: This tools is considered "alpha" quality due to lack of testing
31   * and a generally unpolished API.  Feel free to use but expect changes.
32   * Also, this is not automatically provided via the default tools.xml file.
33   * </b>
34   *
35   * <p>
36   * A tool to make it easy to generate XML or HTML on the fly.  It uses a CSS-type
37   * syntax with a vaguely jQuery-ish API to help you generate the markup you need.
38   * <p>
39   * <pre>
40   * Example uses in a template:
41   *   #set( $foospan = $markup.span.id($foo.id).body($foo) )
42   *   $markup.tag('table tr.bar td').body("This is $foospan")
43   *
44   * Output:
45   *   <table>
46   *     <tr class="bar">
47   *       <td>This is <span id="foo1">my first foo.</span></td>
48   *     </tr>
49   *   </table>
50   *
51   *
52   * Example tools.xml config:
53   * &lt;tools&gt;
54   *   &lt;toolbox scope="application"&gt;
55   *     &lt;tool class="org.apache.velocity.tools.generic.alpha.MarkupTool"/&gt;
56   *   &lt;/toolbox&gt;
57   * &lt;/tools&gt;
58   * </pre></p>
59   *
60   * @author Nathan Bubna
61   * @since VelocityTools 2.0
62   * @version $Id$
63   */
64  @DefaultKey("mark")
65  public class MarkupTool extends SafeConfig
66  {
67      public static final String DEFAULT_TAB = "  ";
68      public static final String DEFAULT_DELIMITER = " ";
69  
70      private String tab = DEFAULT_TAB;
71      private String delim = DEFAULT_DELIMITER;
72  
73      public void setTab(String tab)
74      {
75          if (isConfigLocked())
76          {
77              //TODO: log setTab failure
78          }
79          else
80          {
81              this.tab = tab;
82          }
83      }
84  
85      public String getTab()
86      {
87          return this.tab;
88      }
89  
90      public Tag get(String tag)
91      {
92          return tag(tag);
93      }
94  
95      public Tag tag(String definition)
96      {
97          String[] tags = split(definition);
98          Tag last = null;
99          for (int i=0; i < tags.length; i++)
100         {
101             Tag tag = parse(tags[i]);
102             if (last != null)
103             {
104                 last.append(tag);
105             }
106             last = tag;
107         }
108         return last;
109     }
110 
111     protected String[] split(String me)
112     {
113         //TODO: fix escaped delimiters
114         return me.split(delim);
115     }
116 
117     private static enum Mode { NAME, ID, CLASS, ATTR }
118     protected Tag parse(String definition)
119     {
120         StringBuilder store = new StringBuilder();
121         Tag tag = new Tag(this);
122         Mode mode = Mode.NAME;
123         for (int i=0; i < definition.length(); i++)
124         {
125             char c = definition.charAt(i);
126             if (c == '#')
127             {
128                 store = clear(mode, tag, store, true);
129                 mode = Mode.ID;
130             }
131             else if (c == '.')
132             {
133                 store = clear(mode, tag, store, true);
134                 mode = Mode.CLASS;
135             }
136             else if (c == '[')
137             {
138                 store = clear(mode, tag, store, true);
139                 mode = Mode.ATTR;
140             }
141             else if (c == ']')
142             {
143                 store = clear(mode, tag, store, true);
144                 mode = Mode.NAME;
145             }
146             else
147             {
148                 store.append(c);
149             }
150         }
151         clear(mode, tag, store, false);
152         return tag;
153     }
154     private StringBuilder clear(Mode mode, Tag tag, StringBuilder val, boolean emptyStore)
155     {
156         if (val.length() > 0)
157         {
158             String s = val.toString();
159             switch (mode)
160             {
161                 case NAME:
162                     tag.name(s);
163                     break;
164                 case ID:
165                     tag.id(s);
166                     break;
167                 case CLASS:
168                     tag.addClass(s);
169                     break;
170                 case ATTR:
171                     if (s.indexOf('=') > 0)
172                     {
173                         String[] kv = s.split("=");
174                         tag.attr(kv[0], kv[1]);
175                     }
176                     else
177                     {
178                         tag.attr(s, null);
179                     }
180                     break;
181             }
182             if (emptyStore)
183             {
184                 return new StringBuilder();
185             }
186             return val;
187         }
188         else
189         {
190             // already is clean
191             return val;
192         }
193     }
194 
195     public static class Tag
196     {
197         private MarkupTool tool;
198         private Tag parent;
199         private Object name;
200         private Object id;
201         private List<Object> classes;
202         private Map<Object,Object> attributes;
203         private List<Object> children;
204 
205         public Tag(MarkupTool tool)
206         {
207             this.tool = tool;
208         }
209 
210         public Tag name(Object name)
211         {
212             this.name = name;
213             return this;
214         }
215 
216         public Tag id(Object id)
217         {
218             this.id = id;
219             return this;
220         }
221 
222         public Tag addClass(Object c)
223         {
224             if (c == null)
225             {
226                 return null;
227             }
228 
229             if (classes == null)
230             {
231                 classes = new ArrayList<Object>();
232             }
233             classes.add(c);
234             return this;
235         }
236 
237         public Tag attr(Object k, Object v)
238         {
239             if (k == null)
240             {
241                 return null;
242             }
243             if (attributes == null)
244             {
245                 attributes = new HashMap<Object,Object>();
246             }
247             attributes.put(k, v);
248             return this;
249         }
250 
251         public Tag body(Object o)
252         {
253             if (children == null)
254             {
255                 children = new ArrayList<Object>();
256             }
257             else
258             {
259                 children.clear();
260             }
261             children.add(o);
262             return this;
263         }
264 
265         public Tag append(Object o)
266         {
267             if (children == null)
268             {
269                 children = new ArrayList<Object>();
270             }
271             children.add(o);
272             if (o instanceof Tag)
273             {
274                 ((Tag)o).parent(this);
275             }
276             return this;
277         }
278 
279         public Tag prepend(Object o)
280         {
281             if (children == null)
282             {
283                 children = new ArrayList<Object>();
284                 children.add(o);
285             }
286             else
287             {
288                 children.add(0, o);
289             }
290             if (o instanceof Tag)
291             {
292                 ((Tag)o).parent(this);
293             }
294             return this;
295         }
296 
297         public Tag wrap(String tag)
298         {
299             // make new tag
300             Tag prnt = tool.tag(tag);
301             // give root of new tag our parent
302             prnt.root().parent(parent());
303             // make new tag our parent
304             parent(prnt);
305             return this;
306         }
307 
308         public Tag orphan()
309         {
310             return parent(null);
311         }
312 
313         public Tag parent(Tag parent)
314         {
315             this.parent = parent;
316             return this;
317         }
318 
319         public Tag parent()
320         {
321             return this.parent;
322         }
323 
324         public Tag root()
325         {
326             if (isOrphan())
327             {
328                 return this;
329             }
330             return this.parent.root();
331         }
332 
333         public List<Object> children()
334         {
335             return children;
336         }
337 
338         public boolean isOrphan()
339         {
340             return (parent == null);
341         }
342 
343         public boolean isEmpty()
344         {
345             return (children == null || children().isEmpty());
346         }
347 
348         public boolean matches(Tag tag)
349         {
350             if (missed(name, tag.name) ||
351                 missed(id, tag.id) ||
352                 missed(classes, tag.classes))
353             {
354                 return false;
355             }
356             //TODO: match attributes
357             return true;
358         }
359 
360         protected boolean missed(Object target, Object arrow)
361         {
362             // no arrow, no miss
363             if (arrow == null)
364             {
365                 return false;
366             }
367             // otherwise, the arrow must hit the target
368             return !arrow.equals(target);
369         }
370 
371         protected boolean missed(List<Object> targets, List<Object> arrows)
372         {
373             // no arrows, no miss
374             if (arrows == null)
375             {
376                 return false;
377             }
378             // no targets, always miss
379             if (targets == null)
380             {
381                 return true;
382             }
383             for (Object o : arrows)
384             {
385                 if (!targets.contains(o))
386                 {
387                     return true;
388                 }
389             }
390             return false;
391         }
392 
393 
394         /************* rendering methods **************/
395 
396         protected void render(String indent, StringBuilder s)
397         {
398             if (render_start(indent, s))
399             {
400                 render_body(indent, s);
401                 render_end(indent, s);
402             }
403         }
404 
405         protected boolean render_start(String indent, StringBuilder s)
406         {
407             if (indent != null)
408             {
409                 s.append(indent);
410             }
411             s.append('<');
412             render_name(s);
413             render_id(s);
414             render_classes(s);
415             render_attributes(s);
416             if (isEmpty())
417             {
418                 s.append("/>");
419                 return false;
420             }
421             else
422             {
423                 s.append('>');
424                 return true;
425             }
426         }
427 
428         protected void render_name(StringBuilder s)
429         {
430             s.append(name == null ? "div" : name);
431         }
432 
433         protected void render_id(StringBuilder s)
434         {
435             if (id != null)
436             {
437                 s.append(" id=\"").append(id).append('"');
438             }
439         }
440 
441         protected void render_classes(StringBuilder s)
442         {
443             if (classes != null)
444             {
445                 s.append(" class=\"");
446                 for (int i=0; i < classes.size(); i++)
447                 {
448                     s.append(classes.get(i));
449                     if (i + 1 != classes.size())
450                     {
451                         s.append(' ');
452                     }
453                 }
454                 s.append('"');
455             }
456         }
457 
458         protected void render_attributes(StringBuilder s)
459         {
460             if (attributes != null)
461             {
462                 for (Map.Entry<Object,Object> entry : attributes.entrySet())
463                 {
464                     s.append(' ').append(entry.getKey()).append("=\"");
465                     if (entry.getValue() != null)
466                     {
467                         s.append(entry.getValue());
468                     }
469                     s.append('"');
470                 }
471             }
472         }
473 
474         protected void render_body(String indent, StringBuilder s)
475         {
476             String kidIndent = indent + tool.getTab();
477             for (Object o : children)
478             {
479                 if (o instanceof Tag)
480                 {
481                     ((Tag)o).render(kidIndent, s);
482                 }
483                 else
484                 {
485                     s.append(kidIndent);
486                     s.append(o);
487                 }
488             }
489         }
490 
491         protected void render_end(String indent, StringBuilder s)
492         {
493             if (indent != null)
494             {
495                 s.append(indent);
496             }
497             s.append("</").append(name).append('>');
498         }
499 
500         public String toString()
501         {
502             StringBuilder s = new StringBuilder();
503             root().render("\n", s);
504             return s.toString();
505         }
506     }
507 }