1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.clearsilver.jsilver.syntax;
18
19import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
20import com.google.clearsilver.jsilver.syntax.analysis.AnalysisAdapter;
21import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
22import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
23import com.google.clearsilver.jsilver.syntax.node.AInlineCommand;
24import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
25import com.google.clearsilver.jsilver.syntax.node.PCommand;
26import com.google.clearsilver.jsilver.syntax.node.TData;
27
28/**
29 * Rewrites the AST to replace all 'inline' commands with their associated inner
30 * command sub-tree, where all whitespace data commands have been removed.
31 *
32 * <p>The following template:
33 * <pre>
34 * <?cs inline?>
35 * <?cs if:x.flag?>
36 *   <?cs var:">> " + x.foo + " <<"?>
37 * <?cs /if?>
38 * <?cs /inline?>
39 * </pre>
40 *
41 * <p>will render as if it had been written:
42 * <pre>
43 * <?cs if:x.flag?><?cs var:">> " + x.foo + " <<"?><?cs /if?>
44 * </pre>
45 *
46 * <p>The inline command is intended only to allow neater template authoring.
47 * As such there is a restriction that any data commands (ie, bare literal text)
48 * inside an inline command can consist only of whitespace characters. This
49 * limits the risk of accidentally modifying the template's output in an
50 * unexpected way when using the inline command. Literal text may still be
51 * rendered in an inlined section if it is part of a var command.
52 *
53 * <p>Data commands containing only whitespace are effectively removed by
54 * replacing them with noop commands. These can be removed (if needed) by a
55 * later optimization step but shouldn't cause any issues.
56 */
57public class InlineRewriter extends DepthFirstAdapter {
58
59  /**
60   * Inner visitor class to recursively replace data commands with noops.
61   */
62  private static AnalysisAdapter WHITESPACE_STRIPPER = new DepthFirstAdapter() {
63    @Override
64    public void caseADataCommand(ADataCommand node) {
65      TData data = node.getData();
66      if (isAllWhitespace(data.getText())) {
67        node.replaceBy(new ANoopCommand());
68        return;
69      }
70      // TODO: Add more information here (line numbers etc...)
71      throw new JSilverBadSyntaxException(
72          "literal text in an inline block may only contain whitespace", data.getText(), null, data
73              .getLine(), data.getPos(), null);
74    }
75
76    @Override
77    public void caseAInlineCommand(AInlineCommand node) {
78      // Once in an inline block, just remove any more we encounter.
79      PCommand command = node.getCommand();
80      node.replaceBy(command);
81      command.apply(this);
82    }
83  };
84
85  private static boolean isAllWhitespace(String s) {
86    for (int i = 0; i < s.length(); i++) {
87      if (!Character.isWhitespace(s.charAt(i))) {
88        return false;
89      }
90    }
91    return true;
92  }
93
94  /**
95   * Removes data commands within an inline command.
96   *
97   * @throws JSilverBadSyntaxException if any data commands within the inline block contain
98   *         non-whitespace text.
99   */
100  @Override
101  public void caseAInlineCommand(AInlineCommand node) {
102    node.getCommand().apply(WHITESPACE_STRIPPER);
103    node.replaceBy(node.getCommand());
104  }
105}
106