2 * Copyright 2000-2010 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.template.zencoding;
18 import com.intellij.codeInsight.template.CustomTemplateCallback;
19 import com.intellij.codeInsight.template.impl.TemplateImpl;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.fileTypes.StdFileTypes;
22 import com.intellij.openapi.util.Pair;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.PsiFile;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.xml.XmlTag;
27 import com.intellij.psi.xml.XmlToken;
28 import com.intellij.psi.xml.XmlTokenType;
29 import com.intellij.util.containers.HashMap;
30 import com.intellij.util.containers.HashSet;
31 import com.intellij.util.containers.IntArrayList;
32 import com.intellij.xml.util.HtmlUtil;
33 import org.jetbrains.annotations.Nullable;
38 * @author Eugene.Kudelevsky
40 class XmlZenCodingInterpreter {
41 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.zencoding.XmlZenCodingInterpreter");
42 private static final String ATTRS = "ATTRS";
44 private final List<Token> myTokens;
46 private final CustomTemplateCallback myCallback;
47 private final String mySurroundedText;
49 private State myState;
51 private XmlZenCodingInterpreter(List<Token> tokens,
52 CustomTemplateCallback callback,
54 String surroundedText) {
56 myCallback = callback;
57 mySurroundedText = surroundedText;
58 myState = initialState;
61 private void finish() {
62 myCallback.gotoEndOffset();
65 private void gotoChild(Object templateBoundsKey) {
66 int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey);
67 int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey);
68 int offset = myCallback.getOffset();
70 PsiFile file = myCallback.parseCurrentText(StdFileTypes.XML);
72 PsiElement element = file.findElementAt(offset);
73 if (offset < endOfTemplate && element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) {
78 XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class);
80 for (PsiElement child : tag.getChildren()) {
81 if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) {
82 newOffset = child.getTextOffset();
88 if (offset < endOfTemplate) {
89 myCallback.fixEndOffset();
91 myCallback.moveToOffset(newOffset);
95 // returns if expanding finished
97 public static void interpret(List<Token> tokens,
99 CustomTemplateCallback callback,
101 String surroundedText) {
102 XmlZenCodingInterpreter interpreter =
103 new XmlZenCodingInterpreter(tokens, callback, initialState, surroundedText);
104 interpreter.invoke(startIndex);
107 private void invoke(int startIndex) {
108 String filter = null;
110 if (myTokens.size() > 0) {
111 Token lastToken = myTokens.get(myTokens.size() - 1);
112 if (lastToken instanceof FilterToken) {
113 filter = ((FilterToken)lastToken).getSuffix();
117 final int n = myTokens.size();
118 TemplateToken templateToken = null;
120 for (int i = startIndex; i < n; i++) {
121 Token token = myTokens.get(i);
124 if (templateToken != null) {
125 if (token instanceof MarkerToken || token instanceof OperationToken) {
126 final char sign = token instanceof OperationToken ? ((OperationToken)token).getSign() : ZenCodingTemplate.MARKER;
127 if (sign == '+' || (mySurroundedText == null && sign == ZenCodingTemplate.MARKER)) {
128 final Object key = new Object();
129 myCallback.fixStartOfTemplate(key);
130 invokeTemplate(templateToken, myCallback, 0, filter);
131 myState = State.WORD;
132 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
133 myCallback.fixEndOffset();
136 myCallback.gotoEndOfTemplate(key);
138 templateToken = null;
140 else if (sign == '>' || (mySurroundedText != null && sign == ZenCodingTemplate.MARKER)) {
141 startTemplateAndGotoChild(templateToken, filter);
142 templateToken = null;
144 else if (sign == '*') {
145 myState = State.NUMBER;
154 if (token instanceof TemplateToken) {
155 templateToken = ((TemplateToken)token);
156 myState = State.OPERATION;
163 if (token instanceof NumberToken) {
164 number = ((NumberToken)token).getNumber();
165 myState = State.AFTER_NUMBER;
172 if (token instanceof MarkerToken || token instanceof OperationToken) {
173 char sign = token instanceof OperationToken ? ((OperationToken)token).getSign() : ZenCodingTemplate.MARKER;
174 if (sign == '+' || (mySurroundedText == null && sign == ZenCodingTemplate.MARKER)) {
175 invokeTemplateSeveralTimes(templateToken, 0, number, filter);
176 templateToken = null;
178 else if (number > 1) {
179 invokeTemplateAndProcessTail(templateToken, 0, number, i + 1, filter);
184 startTemplateAndGotoChild(templateToken, filter);
185 templateToken = null;
187 myState = State.WORD;
195 if (mySurroundedText != null) {
196 insertText(myCallback, mySurroundedText);
201 private void startTemplateAndGotoChild(TemplateToken templateToken, String filter) {
202 final Object key = new Object();
203 myCallback.fixStartOfTemplate(key);
204 invokeTemplate(templateToken, myCallback, 0, filter);
205 myState = State.WORD;
209 private void invokeTemplateSeveralTimes(final TemplateToken templateToken,
210 final int startIndex,
213 final Object key = new Object();
214 myCallback.fixStartOfTemplate(key);
215 for (int i = startIndex; i < count; i++) {
216 invokeTemplate(templateToken, myCallback, i, filter);
217 myState = State.WORD;
218 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
219 myCallback.fixEndOffset();
221 myCallback.gotoEndOfTemplate(key);
225 private static void insertText(CustomTemplateCallback callback, String text) {
226 int offset = callback.getOffset();
227 callback.insertString(offset, text);
230 private void invokeTemplateAndProcessTail(final TemplateToken templateToken,
231 final int startIndex,
235 final Object key = new Object();
236 myCallback.fixStartOfTemplate(key);
237 for (int i = startIndex; i < count; i++) {
238 Object iterKey = new Object();
239 myCallback.fixStartOfTemplate(iterKey);
240 invokeTemplate(templateToken, myCallback, i, filter);
242 interpret(myTokens, tailStart, myCallback, State.WORD, mySurroundedText);
243 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
244 myCallback.fixEndOffset();
246 myCallback.gotoEndOfTemplate(key);
251 static boolean containsAttrsVar(TemplateImpl template) {
252 for (int i = 0; i < template.getVariableCount(); i++) {
253 String varName = template.getVariableNameAt(i);
254 if (ATTRS.equals(varName)) {
261 private static void removeVariablesWhichHasNoSegment(TemplateImpl template) {
262 Set<String> segments = new HashSet<String>();
263 for (int i = 0; i < template.getSegmentsCount(); i++) {
264 segments.add(template.getSegmentName(i));
266 IntArrayList varsToRemove = new IntArrayList();
267 for (int i = 0; i < template.getVariableCount(); i++) {
268 String varName = template.getVariableNameAt(i);
269 if (!segments.contains(varName)) {
273 for (int i = 0; i < varsToRemove.size(); i++) {
274 template.removeVariable(varsToRemove.get(i));
279 private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value,
280 int numberInIteration,
281 CustomTemplateCallback callback) {
282 String attributes = buildAttributesString(attribute2value, numberInIteration, callback);
283 assert attributes != null;
284 attributes = attributes.length() > 0 ? ' ' + attributes : null;
285 Map<String, String> predefinedValues = null;
286 if (attributes != null) {
287 predefinedValues = new HashMap<String, String>();
288 predefinedValues.put(ATTRS, attributes);
290 return predefinedValues;
294 private static String buildAttributesString(List<Pair<String, String>> attribute2value,
295 int numberInIteration,
296 CustomTemplateCallback callback) {
297 PsiElement context = callback.getContext();
298 for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
299 if (filter.isMyContext(context)) {
300 return filter.buildAttributesString(attribute2value, numberInIteration);
303 return new XmlZenCodingFilterImpl().buildAttributesString(attribute2value, numberInIteration);
307 private static void invokeTemplate(TemplateToken token,
308 final CustomTemplateCallback callback,
309 int numberInIteration,
311 if (token instanceof XmlTemplateToken && token.getTemplate() != null) {
312 XmlTemplateToken xmlTemplateToken = (XmlTemplateToken)token;
313 List<Pair<String, String>> attr2value = new ArrayList<Pair<String, String>>(xmlTemplateToken.getAttribute2Value());
314 TemplateImpl modifiedTemplate = token.getTemplate().copy();
315 XmlTag tag = xmlTemplateToken.getTag();
317 for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
318 Pair<String, String> pair = iterator.next();
319 if (tag.getAttribute(pair.first) != null) {
320 tag.setAttribute(pair.first, ZenCodingUtil.getValue(pair, numberInIteration));
324 String s = filterXml(tag, callback, filter);
326 if (HtmlUtil.isHtmlBlockTagL(tag.getName())) {
327 boolean newLineBefore = callback.newLineBefore();
328 boolean newLineAfter = callback.newLineAfter();
329 if (!newLineBefore || !newLineAfter) {
330 StringBuilder builder = new StringBuilder();
331 if (!newLineBefore) {
332 builder.append('\n');
336 builder.append('\n');
338 s = builder.toString();
341 modifiedTemplate.setString(s);
342 removeVariablesWhichHasNoSegment(modifiedTemplate);
343 Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration, callback);
344 callback.expandTemplate(modifiedTemplate, predefinedValues);
349 callback.expandTemplate(token.getKey(), null);
354 private static String filterXml(XmlTag tag, CustomTemplateCallback callback, String filterSuffix) {
355 PsiElement context = callback.getContext();
356 for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
357 if ((filterSuffix == null && filter.isDefaultFilter()) || (filterSuffix != null && filterSuffix.equals(filter.getSuffix()))) {
358 if (filter instanceof XmlZenCodingFilter && filter.isDefaultFilter() && filter.isMyContext(context)) {
359 return ((XmlZenCodingFilter)filter).toString(tag, context);
363 return new XmlZenCodingFilterImpl().toString(tag, context);
366 private static void fail() {
367 LOG.error("Input string was checked incorrectly during isApplicable() invokation");