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.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.fileTypes.StdFileTypes;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.psi.PsiElement;
25 import com.intellij.psi.PsiFile;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.xml.XmlTag;
28 import com.intellij.psi.xml.XmlToken;
29 import com.intellij.psi.xml.XmlTokenType;
30 import com.intellij.util.containers.HashMap;
31 import com.intellij.util.containers.HashSet;
32 import com.intellij.util.containers.IntArrayList;
33 import com.intellij.xml.util.HtmlUtil;
34 import org.jetbrains.annotations.Nullable;
39 * @author Eugene.Kudelevsky
41 class XmlZenCodingInterpreter {
42 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.zencoding.XmlZenCodingInterpreter");
43 private static final String ATTRS = "ATTRS";
45 private final List<Token> myTokens;
47 private final CustomTemplateCallback myCallback;
48 private final String mySurroundedText;
50 private State myState;
52 private XmlZenCodingInterpreter(List<Token> tokens,
53 CustomTemplateCallback callback,
55 String surroundedText) {
57 myCallback = callback;
58 mySurroundedText = surroundedText;
59 myState = initialState;
62 private void finish() {
63 myCallback.gotoEndOffset();
66 private void gotoChild(Object templateBoundsKey) {
67 int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey);
68 int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey);
69 int offset = myCallback.getOffset();
71 PsiFile file = myCallback.parseCurrentText(StdFileTypes.XML);
73 PsiElement element = file.findElementAt(offset);
74 if (offset < endOfTemplate && element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) {
79 XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class);
81 for (PsiElement child : tag.getChildren()) {
82 if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) {
83 newOffset = child.getTextOffset();
89 if (offset < endOfTemplate) {
90 myCallback.fixEndOffset();
92 myCallback.moveToOffset(newOffset);
96 // returns if expanding finished
98 public static void interpret(List<Token> tokens,
100 CustomTemplateCallback callback,
102 String surroundedText) {
103 XmlZenCodingInterpreter interpreter =
104 new XmlZenCodingInterpreter(tokens, callback, initialState, surroundedText);
105 interpreter.invoke(startIndex);
108 private void invoke(int startIndex) {
109 String filter = null;
111 if (myTokens.size() > 0) {
112 Token lastToken = myTokens.get(myTokens.size() - 1);
113 if (lastToken instanceof FilterToken) {
114 filter = ((FilterToken)lastToken).getSuffix();
118 final int n = myTokens.size();
119 TemplateToken templateToken = null;
121 for (int i = startIndex; i < n; i++) {
122 Token token = myTokens.get(i);
125 if (templateToken != null) {
126 if (token instanceof MarkerToken || token instanceof OperationToken) {
127 final char sign = token instanceof OperationToken ? ((OperationToken)token).getSign() : ZenCodingTemplate.MARKER;
128 if (sign == '+' || (mySurroundedText == null && sign == ZenCodingTemplate.MARKER)) {
129 final Object key = new Object();
130 myCallback.fixStartOfTemplate(key);
131 invokeTemplate(templateToken, myCallback, 0, filter);
132 myState = State.WORD;
133 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
134 myCallback.fixEndOffset();
137 myCallback.gotoEndOfTemplate(key);
139 templateToken = null;
141 else if (sign == '>' || (mySurroundedText != null && sign == ZenCodingTemplate.MARKER)) {
142 startTemplateAndGotoChild(templateToken, filter);
143 templateToken = null;
145 else if (sign == '*') {
146 myState = State.NUMBER;
155 if (token instanceof TemplateToken) {
156 templateToken = ((TemplateToken)token);
157 myState = State.OPERATION;
164 if (token instanceof NumberToken) {
165 number = ((NumberToken)token).getNumber();
166 myState = State.AFTER_NUMBER;
173 if (token instanceof MarkerToken || token instanceof OperationToken) {
174 char sign = token instanceof OperationToken ? ((OperationToken)token).getSign() : ZenCodingTemplate.MARKER;
175 if (sign == '+' || (mySurroundedText == null && sign == ZenCodingTemplate.MARKER)) {
176 invokeTemplateSeveralTimes(templateToken, 0, number, filter);
177 templateToken = null;
179 else if (number > 1) {
180 invokeTemplateAndProcessTail(templateToken, 0, number, i + 1, filter);
185 startTemplateAndGotoChild(templateToken, filter);
186 templateToken = null;
188 myState = State.WORD;
196 if (mySurroundedText != null) {
197 insertText(myCallback, mySurroundedText);
202 private void startTemplateAndGotoChild(TemplateToken templateToken, String filter) {
203 final Object key = new Object();
204 myCallback.fixStartOfTemplate(key);
205 invokeTemplate(templateToken, myCallback, 0, filter);
206 myState = State.WORD;
210 private void invokeTemplateSeveralTimes(final TemplateToken templateToken,
211 final int startIndex,
214 final Object key = new Object();
215 myCallback.fixStartOfTemplate(key);
216 for (int i = startIndex; i < count; i++) {
217 invokeTemplate(templateToken, myCallback, i, filter);
218 myState = State.WORD;
219 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
220 myCallback.fixEndOffset();
222 myCallback.gotoEndOfTemplate(key);
226 private static void insertText(CustomTemplateCallback callback, String text) {
227 int offset = callback.getOffset();
228 callback.insertString(offset, text);
231 private void invokeTemplateAndProcessTail(final TemplateToken templateToken,
232 final int startIndex,
236 final Object key = new Object();
237 myCallback.fixStartOfTemplate(key);
238 for (int i = startIndex; i < count; i++) {
239 Object iterKey = new Object();
240 myCallback.fixStartOfTemplate(iterKey);
241 invokeTemplate(templateToken, myCallback, i, filter);
243 interpret(myTokens, tailStart, myCallback, State.WORD, mySurroundedText);
244 if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
245 myCallback.fixEndOffset();
247 myCallback.gotoEndOfTemplate(key);
252 static boolean containsAttrsVar(TemplateImpl template) {
253 for (int i = 0; i < template.getVariableCount(); i++) {
254 String varName = template.getVariableNameAt(i);
255 if (ATTRS.equals(varName)) {
262 private static void removeVariablesWhichHasNoSegment(TemplateImpl template) {
263 Set<String> segments = new HashSet<String>();
264 for (int i = 0; i < template.getSegmentsCount(); i++) {
265 segments.add(template.getSegmentName(i));
267 IntArrayList varsToRemove = new IntArrayList();
268 for (int i = 0; i < template.getVariableCount(); i++) {
269 String varName = template.getVariableNameAt(i);
270 if (!segments.contains(varName)) {
274 for (int i = 0; i < varsToRemove.size(); i++) {
275 template.removeVariable(varsToRemove.get(i));
280 private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value,
281 int numberInIteration,
282 CustomTemplateCallback callback) {
283 String attributes = buildAttributesString(attribute2value, numberInIteration, callback);
284 assert attributes != null;
285 attributes = attributes.length() > 0 ? ' ' + attributes : null;
286 Map<String, String> predefinedValues = null;
287 if (attributes != null) {
288 predefinedValues = new HashMap<String, String>();
289 predefinedValues.put(ATTRS, attributes);
291 return predefinedValues;
295 private static String buildAttributesString(List<Pair<String, String>> attribute2value,
296 int numberInIteration,
297 CustomTemplateCallback callback) {
298 PsiElement context = callback.getContext();
299 for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
300 if (filter.isMyContext(context)) {
301 return filter.buildAttributesString(attribute2value, numberInIteration);
304 return new XmlZenCodingFilterImpl().buildAttributesString(attribute2value, numberInIteration);
308 private static void invokeTemplate(TemplateToken token,
309 final CustomTemplateCallback callback,
310 final int numberInIteration,
312 if (token instanceof XmlTemplateToken && token.getTemplate() != null) {
313 XmlTemplateToken xmlTemplateToken = (XmlTemplateToken)token;
314 final List<Pair<String, String>> attr2value = new ArrayList<Pair<String, String>>(xmlTemplateToken.getAttribute2Value());
315 TemplateImpl modifiedTemplate = token.getTemplate().copy();
316 final XmlTag tag = xmlTemplateToken.getTag();
318 ApplicationManager.getApplication().runWriteAction(new Runnable() {
320 setAttributeValues(tag, attr2value, numberInIteration);
323 String s = filterXml(tag, callback, filter);
325 if (HtmlUtil.isHtmlBlockTagL(tag.getName())) {
326 boolean newLineBefore = callback.newLineBefore();
327 boolean newLineAfter = callback.newLineAfter();
328 if (!newLineBefore || !newLineAfter) {
329 StringBuilder builder = new StringBuilder();
330 if (!newLineBefore) {
331 builder.append('\n');
335 builder.append('\n');
337 s = builder.toString();
340 modifiedTemplate.setString(s);
341 removeVariablesWhichHasNoSegment(modifiedTemplate);
342 Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration, callback);
343 callback.expandTemplate(modifiedTemplate, predefinedValues);
348 callback.expandTemplate(token.getKey(), null);
352 private static void setAttributeValues(XmlTag tag, List<Pair<String, String>> attr2value, int numberInIteration) {
353 for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
354 Pair<String, String> pair = iterator.next();
355 if (tag.getAttribute(pair.first) != null) {
356 tag.setAttribute(pair.first, ZenCodingUtil.getValue(pair, numberInIteration));
363 private static String filterXml(XmlTag tag, CustomTemplateCallback callback, String filterSuffix) {
364 PsiElement context = callback.getContext();
365 for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
366 if ((filterSuffix == null && filter.isDefaultFilter()) || (filterSuffix != null && filterSuffix.equals(filter.getSuffix()))) {
367 if (filter instanceof XmlZenCodingFilter && filter.isDefaultFilter() && filter.isMyContext(context)) {
368 return ((XmlZenCodingFilter)filter).toString(tag, context);
372 return new XmlZenCodingFilterImpl().toString(tag, context);
375 private static void fail() {
376 LOG.error("Input string was checked incorrectly during isApplicable() invokation");