refactoring
authorEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Mon, 15 Mar 2010 15:51:30 +0000 (18:51 +0300)
committerEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Mon, 15 Mar 2010 15:51:30 +0000 (18:51 +0300)
platform/platform-resources/src/META-INF/XmlPlugin.xml
xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java [deleted file]
xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java [new file with mode: 0644]

index 98b39a2de6cf571a67850bb00a752d27b771e1a2..0b04b7fe2ad722f8a199d166789e5ed471c991c1 100644 (file)
                                         implementationClass="com.intellij.codeInsight.hint.XmlImplementationTextSelectioner"/>
     <basicWordSelectionFilter implementation="com.intellij.codeInsight.editorActions.XmlBasicWordSelectionFilter"/>
     <defaultLiveTemplatesProvider implementation="com.intellij.codeInsight.template.XmlDefaultLiveTemplatesProvider"/>
-    <customLiveTemplate implementation="com.intellij.codeInsight.template.XmlZenCodingTemplate"/>
+    <customLiveTemplate implementation="com.intellij.codeInsight.template.zencoding.XmlZenCodingTemplate"/>
     <productivityFeaturesProvider implementation="com.intellij.featureStatistics.XmlProductivityFeatureProvider"/>
 
     <idIndexer filetype="XML" implementationClass="com.intellij.psi.impl.cache.impl.idCache.XmlIdIndexer"/>
diff --git a/xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java b/xml/impl/src/com/intellij/codeInsight/template/XmlZenCodingTemplate.java
deleted file mode 100644 (file)
index 5e168aa..0000000
+++ /dev/null
@@ -1,801 +0,0 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.intellij.codeInsight.template;
-
-import com.intellij.application.options.editor.WebEditorOptions;
-import com.intellij.codeInsight.template.impl.TemplateImpl;
-import com.intellij.lang.xml.XMLLanguage;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiFileFactory;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.psi.xml.*;
-import com.intellij.util.containers.HashMap;
-import com.intellij.util.containers.HashSet;
-import com.intellij.util.containers.IntArrayList;
-import org.apache.xerces.util.XML11Char;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.*;
-
-/**
- * @author Eugene.Kudelevsky
- */
-public class XmlZenCodingTemplate implements CustomLiveTemplate {
-  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.XmlZenCodingTemplate");
-
-  private static final String ATTRS = "ATTRS";
-
-  private static final String OPERATIONS = ">+*";
-  private static final String SELECTORS = ".#[";
-  private static final char MARKER = '$';
-  private static final String ID = "id";
-  private static final String CLASS = "class";
-  private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$";
-
-  private static enum MyState {
-    OPERATION, WORD, AFTER_NUMBER, NUMBER
-  }
-
-  private static class MyToken {
-  }
-
-  private static class MyMarkerToken extends MyToken {
-  }
-
-  private static class MyTemplateToken extends MyToken {
-    final String myKey;
-    final List<Pair<String, String>> myAttribute2Value;
-    TemplateImpl myTemplate;
-
-    MyTemplateToken(String key, List<Pair<String, String>> attribute2value) {
-      myKey = key;
-      myAttribute2Value = attribute2value;
-    }
-  }
-
-  private static class MyNumberToken extends MyToken {
-    final int myNumber;
-
-    MyNumberToken(int number) {
-      myNumber = number;
-    }
-  }
-
-  private static class MyOperationToken extends MyToken {
-    final char mySign;
-
-    MyOperationToken(char sign) {
-      mySign = sign;
-    }
-  }
-
-  private static int parseNonNegativeInt(@NotNull String s) {
-    try {
-      return Integer.parseInt(s);
-    }
-    catch (Throwable ignored) {
-    }
-    return -1;
-  }
-
-  private static String getPrefix(@NotNull String templateKey) {
-    for (int i = 0, n = templateKey.length(); i < n; i++) {
-      char c = templateKey.charAt(i);
-      if (SELECTORS.indexOf(c) >= 0) {
-        return templateKey.substring(0, i);
-      }
-    }
-    return templateKey;
-  }
-
-  @Nullable
-  private static Pair<String, String> parseAttrNameAndValue(@NotNull String text) {
-    int eqIndex = text.indexOf('=');
-    if (eqIndex > 0) {
-      return new Pair<String, String>(text.substring(0, eqIndex), text.substring(eqIndex + 1));
-    }
-    return null;
-  }
-
-  @Nullable
-  private static MyTemplateToken parseSelectors(@NotNull String text) {
-    String templateKey = null;
-    List<Pair<String, String>> attributes = new ArrayList<Pair<String, String>>();
-    Set<String> definedAttrs = new HashSet<String>();
-    final List<String> classes = new ArrayList<String>();
-    StringBuilder builder = new StringBuilder();
-    char lastDelim = 0;
-    text += MARKER;
-    int classAttrPosition = -1;
-    for (int i = 0, n = text.length(); i < n; i++) {
-      char c = text.charAt(i);
-      if (c == '#' || c == '.' || c == '[' || c == ']' || i == n - 1) {
-        if (c != ']') {
-          switch (lastDelim) {
-            case 0:
-              templateKey = builder.toString();
-              break;
-            case '#':
-              if (!definedAttrs.add(ID)) {
-                return null;
-              }
-              attributes.add(new Pair<String, String>(ID, builder.toString()));
-              break;
-            case '.':
-              if (builder.length() <= 0) {
-                return null;
-              }
-              if (classAttrPosition < 0) {
-                classAttrPosition = attributes.size();
-              }
-              classes.add(builder.toString());
-              break;
-            case ']':
-              if (builder.length() > 0) {
-                return null;
-              }
-              break;
-            default:
-              return null;
-          }
-        }
-        else if (lastDelim != '[') {
-          return null;
-        }
-        else {
-          Pair<String, String> pair = parseAttrNameAndValue(builder.toString());
-          if (pair == null || !definedAttrs.add(pair.first)) {
-            return null;
-          }
-          attributes.add(pair);
-        }
-        lastDelim = c;
-        builder = new StringBuilder();
-      }
-      else {
-        builder.append(c);
-      }
-    }
-    if (classes.size() > 0) {
-      if (definedAttrs.contains(CLASS)) {
-        return null;
-      }
-      StringBuilder classesAttrValue = new StringBuilder();
-      for (int i = 0; i < classes.size(); i++) {
-        classesAttrValue.append(classes.get(i));
-        if (i < classes.size() - 1) {
-          classesAttrValue.append(' ');
-        }
-      }
-      assert classAttrPosition >= 0;
-      attributes.add(classAttrPosition, new Pair<String, String>(CLASS, classesAttrValue.toString()));
-    }
-    return new MyTemplateToken(templateKey, attributes);
-  }
-
-  private static boolean isXML11ValidQName(String str) {
-    final int colon = str.indexOf(':');
-    if (colon == 0 || colon == str.length() - 1) {
-      return false;
-    }
-    if (colon > 0) {
-      final String prefix = str.substring(0, colon);
-      final String localPart = str.substring(colon + 1);
-      return XML11Char.isXML11ValidNCName(prefix) && XML11Char.isXML11ValidNCName(localPart);
-    }
-    return XML11Char.isXML11ValidNCName(str);
-  }
-
-  private static boolean containsAttrsVar(TemplateImpl template) {
-    for (int i = 0; i < template.getVariableCount(); i++) {
-      String varName = template.getVariableNameAt(i);
-      if (ATTRS.equals(varName)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private static void removeVariablesWhichHasNoSegment(TemplateImpl template) {
-    Set<String> segments = new HashSet<String>();
-    for (int i = 0; i < template.getSegmentsCount(); i++) {
-      segments.add(template.getSegmentName(i));
-    }
-    IntArrayList varsToRemove = new IntArrayList();
-    for (int i = 0; i < template.getVariableCount(); i++) {
-      String varName = template.getVariableNameAt(i);
-      if (!segments.contains(varName)) {
-        varsToRemove.add(i);
-      }
-    }
-    for (int i = 0; i < varsToRemove.size(); i++) {
-      template.removeVariable(varsToRemove.get(i));
-    }
-  }
-
-  @Nullable
-  private static XmlTag parseXmlTagInTemplate(String templateString, Project project) {
-    XmlFile xmlFile = (XmlFile)PsiFileFactory.getInstance(project).createFileFromText("dummy.xml", templateString);
-    XmlDocument document = xmlFile.getDocument();
-    return document == null ? null : document.getRootTag();
-  }
-
-  private static boolean generateTemplateAndAddToToken(MyTemplateToken token, CustomTemplateCallback callback) {
-    TemplateImpl template = callback.findApplicableTemplate(token.myKey);
-    assert template != null;
-    XmlTag tag = parseXmlTagInTemplate(template.getString(), callback.getProject());
-    if (tag == null) {
-      return false;
-    }
-    token.myTemplate = template;
-    return true;
-  }
-
-  @Nullable
-  private static List<MyToken> parse(@NotNull String text, @NotNull CustomTemplateCallback callback) {
-    text += MARKER;
-    StringBuilder templateKeyBuilder = new StringBuilder();
-    List<MyToken> result = new ArrayList<MyToken>();
-    for (int i = 0, n = text.length(); i < n; i++) {
-      char c = text.charAt(i);
-      if (i == n - 1 || (i < n - 2 && OPERATIONS.indexOf(c) >= 0)) {
-        String key = templateKeyBuilder.toString();
-        templateKeyBuilder = new StringBuilder();
-        int num = parseNonNegativeInt(key);
-        if (num > 0) {
-          result.add(new MyNumberToken(num));
-        }
-        else {
-          if (key.length() == 0) {
-            return null;
-          }
-          String prefix = getPrefix(key);
-          boolean applicable = callback.isLiveTemplateApplicable(prefix);
-          if (!applicable && !isXML11ValidQName(prefix)) {
-            return null;
-          }
-          MyTemplateToken token = parseSelectors(key);
-          if (token == null) {
-            return null;
-          }
-          if (applicable && token.myAttribute2Value.size() > 0) {
-            assert prefix.equals(token.myKey);
-            if (!generateTemplateAndAddToToken(token, callback)) {
-              return null;
-            }
-          }
-          result.add(token);
-        }
-        result.add(i < n - 1 ? new MyOperationToken(c) : new MyMarkerToken());
-      }
-      else if (!Character.isWhitespace(c)) {
-        templateKeyBuilder.append(c);
-      }
-      else {
-        return null;
-      }
-    }
-    return result;
-  }
-
-  private static boolean check(@NotNull Collection<MyToken> tokens) {
-    MyState state = MyState.WORD;
-    for (MyToken token : tokens) {
-      if (token instanceof MyMarkerToken) {
-        break;
-      }
-      switch (state) {
-        case OPERATION:
-          if (token instanceof MyOperationToken) {
-            state = ((MyOperationToken)token).mySign == '*' ? MyState.NUMBER : MyState.WORD;
-          }
-          else {
-            return false;
-          }
-          break;
-        case WORD:
-          if (token instanceof MyTemplateToken) {
-            state = MyState.OPERATION;
-          }
-          else {
-            return false;
-          }
-          break;
-        case NUMBER:
-          if (token instanceof MyNumberToken) {
-            state = MyState.AFTER_NUMBER;
-          }
-          else {
-            return false;
-          }
-          break;
-        case AFTER_NUMBER:
-          if (token instanceof MyOperationToken && ((MyOperationToken)token).mySign != '*') {
-            state = MyState.WORD;
-          }
-          else {
-            return false;
-          }
-          break;
-      }
-    }
-    return state == MyState.OPERATION || state == MyState.AFTER_NUMBER;
-  }
-
-  private static String computeKey(Editor editor, int startOffset) {
-    int offset = editor.getCaretModel().getOffset();
-    String s = editor.getDocument().getCharsSequence().subSequence(startOffset, offset).toString();
-    int index = 0;
-    while (index < s.length() && Character.isWhitespace(s.charAt(index))) {
-      index++;
-    }
-    String key = s.substring(index);
-    int lastWhitespaceIndex = -1;
-    for (int i = 0; i < key.length(); i++) {
-      if (Character.isWhitespace(key.charAt(i))) {
-        lastWhitespaceIndex = i;
-      }
-    }
-    if (lastWhitespaceIndex >= 0 && lastWhitespaceIndex < key.length() - 1) {
-      return key.substring(lastWhitespaceIndex + 1);
-    }
-    return key;
-  }
-
-  public String computeTemplateKey(@NotNull CustomTemplateCallback callback) {
-    Editor editor = callback.getEditor();
-    int offset = callback.getOffset();
-    PsiElement element = callback.getFile().findElementAt(offset > 0 ? offset - 1 : offset);
-    int line = editor.getCaretModel().getLogicalPosition().line;
-    int lineStart = editor.getDocument().getLineStartOffset(line);
-    int parentStart;
-    do {
-      parentStart = element != null ? element.getTextRange().getStartOffset() : 0;
-      int startOffset = parentStart > lineStart ? parentStart : lineStart;
-      String key = computeKey(editor, startOffset);
-      List<MyToken> tokens = parse(key, callback);
-      if (tokens != null && check(tokens)) {
-        if (tokens.size() == 2) {
-          MyToken token = tokens.get(0);
-          if (token instanceof MyTemplateToken) {
-            if (key.equals(((MyTemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) {
-              // do not activate only live template
-              return null;
-            }
-          }
-        }
-        return key;
-      }
-      if (element != null) {
-        element = element.getParent();
-      }
-    }
-    while (element != null && parentStart > lineStart);
-    return null;
-  }
-
-  public boolean isApplicable(PsiFile file, int offset, boolean selection) {
-    WebEditorOptions webEditorOptions = WebEditorOptions.getInstance();
-    if (!webEditorOptions.isZenCodingEnabled()) {
-      return false;
-    }
-    if (file.getLanguage() instanceof XMLLanguage) {
-      PsiElement element = file.findElementAt(offset > 0 ? offset - 1 : offset);
-      if (element == null || element.getLanguage() instanceof XMLLanguage) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public void expand(String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
-    List<MyToken> tokens = parse(key, callback);
-    assert tokens != null;
-    MyInterpreter interpreter = new MyInterpreter(tokens, callback, MyState.WORD, listener);
-    interpreter.invoke(0);
-  }
-
-  public void wrap(String selection, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
-  }
-
-  private static void fail() {
-    LOG.error("Input string was checked incorrectly during isApplicable() invokation");
-  }
-
-  @Nullable
-  private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value, int numberInIteration) {
-    StringBuilder result = new StringBuilder();
-    for (Iterator<Pair<String, String>> it = attribute2value.iterator(); it.hasNext();) {
-      Pair<String, String> pair = it.next();
-      String name = pair.first;
-      String value = getValue(pair, numberInIteration);
-      result.append(name).append("=\"").append(value).append('"');
-      if (it.hasNext()) {
-        result.append(' ');
-      }
-    }
-    String attributes = result.toString();
-    attributes = attributes.length() > 0 ? ' ' + attributes : null;
-    Map<String, String> predefinedValues = null;
-    if (attributes != null) {
-      predefinedValues = new HashMap<String, String>();
-      predefinedValues.put(ATTRS, attributes);
-    }
-    return predefinedValues;
-  }
-
-  private static String getValue(Pair<String, String> pair, int numberInIteration) {
-    return pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1));
-  }
-
-  @Nullable
-  private static String addAttrsVar(TemplateImpl modifiedTemplate, XmlTag tag) {
-    String text = tag.getContainingFile().getText();
-    PsiElement[] children = tag.getChildren();
-    if (children.length >= 1 &&
-        children[0] instanceof XmlToken &&
-        ((XmlToken)children[0]).getTokenType() == XmlTokenType.XML_START_TAG_START) {
-      PsiElement beforeAttrs = children[0];
-      if (children.length >= 2 && children[1] instanceof XmlToken && ((XmlToken)children[1]).getTokenType() == XmlTokenType.XML_NAME) {
-        beforeAttrs = children[1];
-      }
-      TextRange range = beforeAttrs.getTextRange();
-      if (range == null) {
-        return null;
-      }
-      int offset = range.getEndOffset();
-      text = text.substring(0, offset) + " $ATTRS$" + text.substring(offset);
-      modifiedTemplate.addVariable(ATTRS, "", "", false);
-      return text;
-    }
-    return null;
-  }
-
-  private static boolean invokeTemplate(MyTemplateToken token,
-                                        final CustomTemplateCallback callback,
-                                        final TemplateInvokationListener listener,
-                                        int numberInIteration) {
-    List<Pair<String, String>> attr2value = new ArrayList<Pair<String, String>>(token.myAttribute2Value);
-    if (callback.isLiveTemplateApplicable(token.myKey)) {
-      if (token.myTemplate != null) {
-        TemplateImpl modifiedTemplate = token.myTemplate.copy();
-        XmlTag tag = parseXmlTagInTemplate(token.myTemplate.getString(), callback.getProject());
-        assert tag != null;
-        for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
-          Pair<String, String> pair = iterator.next();
-          if (tag.getAttribute(pair.first) != null) {
-            tag.setAttribute(pair.first, getValue(pair, numberInIteration));
-            iterator.remove();
-          }
-        }
-        String text = null;
-        if (!containsAttrsVar(modifiedTemplate) && attr2value.size() > 0) {
-          String textWithAttrs = addAttrsVar(modifiedTemplate, tag);
-          if (textWithAttrs != null) {
-            text = textWithAttrs;
-          }
-          else {
-            for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
-              Pair<String, String> pair = iterator.next();
-              tag.setAttribute(pair.first, getValue(pair, numberInIteration));
-              iterator.remove();
-            }
-          }
-        }
-        if (text == null) {
-          text = tag.getContainingFile().getText();
-        }
-        modifiedTemplate.setString(text);
-        removeVariablesWhichHasNoSegment(modifiedTemplate);
-        Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
-        return callback.startTemplate(modifiedTemplate, predefinedValues, listener);
-      }
-      else {
-        Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
-        return callback.startTemplate(token.myKey, predefinedValues, listener);
-      }
-    }
-    else {
-      TemplateImpl template = new TemplateImpl("", "");
-      template.addTextSegment('<' + token.myKey);
-      if (attr2value.size() > 0) {
-        template.addVariable(ATTRS, "", "", false);
-        template.addVariableSegment(ATTRS);
-      }
-      template.addTextSegment(">");
-      template.addVariableSegment(TemplateImpl.END);
-      template.addTextSegment("</" + token.myKey + ">");
-      template.setToReformat(true);
-      Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
-      return callback.startTemplate(template, predefinedValues, listener);
-    }
-  }
-
-  /*private static boolean hasClosingTag(CharSequence text, CharSequence tagName, int offset, int rightBound) {
-    if (offset + 1 < text.length() && text.charAt(offset) == '<' && text.charAt(offset + 1) == '/') {
-      CharSequence closingTagName = parseTagName(text, offset + 2, rightBound);
-      if (tagName.equals(closingTagName)) {
-        return true;
-      }
-    }
-    return false;
-  }*/
-
-  private class MyInterpreter {
-    private final List<MyToken> myTokens;
-    private final CustomTemplateCallback myCallback;
-    private final TemplateInvokationListener myListener;
-    private MyState myState;
-
-    private MyInterpreter(List<MyToken> tokens,
-                          CustomTemplateCallback callback,
-                          MyState initialState,
-                          TemplateInvokationListener listener) {
-      myTokens = tokens;
-      myCallback = callback;
-      myListener = listener;
-      myState = initialState;
-    }
-
-    private void finish(boolean inSeparateEvent) {
-      myCallback.gotoEndOffset();
-      if (myListener != null) {
-        myListener.finished(inSeparateEvent);
-      }
-    }
-
-    private void gotoChild(Object templateBoundsKey) {
-      int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey);
-      int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey);
-      Editor editor = myCallback.getEditor();
-      int offset = myCallback.getOffset();
-
-      PsiFile file = myCallback.getFile();
-
-      PsiElement element = file.findElementAt(offset);
-      if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) {
-        return;
-      }
-
-      int newOffset = -1;
-      XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class);
-      if (tag != null) {
-        for (PsiElement child : tag.getChildren()) {
-          if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) {
-            newOffset = child.getTextOffset();
-          }
-        }
-      }
-
-      if (newOffset >= 0) {
-        myCallback.fixEndOffset();
-        editor.getCaretModel().moveToOffset(newOffset);
-      }
-
-      /*CharSequence tagName = getPrecedingTagName(text, offset, startOfTemplate);
-      if (tagName != null) {
-        *//*if (!hasClosingTag(text, tagName, offset, endOfTemplate)) {
-          document.insertString(offset, "</" + tagName + '>');
-        }*//*
-      }
-      else if (offset != endOfTemplate) {
-        tagName = getPrecedingTagName(text, endOfTemplate, startOfTemplate);
-        if (tagName != null) {
-          *//*fixEndOffset();
-          document.insertString(endOfTemplate, "</" + tagName + '>');*//*
-          editor.getCaretModel().moveToOffset(endOfTemplate);
-        }
-      }*/
-    }
-
-    public boolean invoke(int startIndex) {
-      final int n = myTokens.size();
-      MyTemplateToken templateToken = null;
-      int number = -1;
-      for (int i = startIndex; i < n; i++) {
-        final int finalI = i;
-        MyToken token = myTokens.get(i);
-        switch (myState) {
-          case OPERATION:
-            if (templateToken != null) {
-              if (token instanceof MyMarkerToken || token instanceof MyOperationToken) {
-                final char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER;
-                if (sign == MARKER || sign == '+') {
-                  final Object key = new Object();
-                  myCallback.fixStartOfTemplate(key);
-                  TemplateInvokationListener listener = new TemplateInvokationListener() {
-                    public void finished(boolean inSeparateEvent) {
-                      myState = MyState.WORD;
-                      if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
-                        myCallback.fixEndOffset();
-                      }
-                      if (sign == '+') {
-                        myCallback.gotoEndOfTemplate(key);
-                      }
-                      if (inSeparateEvent) {
-                        invoke(finalI + 1);
-                      }
-                    }
-                  };
-                  if (!invokeTemplate(templateToken, myCallback, listener, 0)) {
-                    return false;
-                  }
-                  templateToken = null;
-                }
-                else if (sign == '>') {
-                  if (!startTemplateAndGotoChild(templateToken, finalI)) {
-                    return false;
-                  }
-                  templateToken = null;
-                }
-                else if (sign == '*') {
-                  myState = MyState.NUMBER;
-                }
-              }
-              else {
-                fail();
-              }
-            }
-            break;
-          case WORD:
-            if (token instanceof MyTemplateToken) {
-              templateToken = ((MyTemplateToken)token);
-              myState = MyState.OPERATION;
-            }
-            else {
-              fail();
-            }
-            break;
-          case NUMBER:
-            if (token instanceof MyNumberToken) {
-              number = ((MyNumberToken)token).myNumber;
-              myState = MyState.AFTER_NUMBER;
-            }
-            else {
-              fail();
-            }
-            break;
-          case AFTER_NUMBER:
-            if (token instanceof MyMarkerToken || token instanceof MyOperationToken) {
-              char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER;
-              if (sign == MARKER || sign == '+') {
-                if (!invokeTemplateSeveralTimes(templateToken, 0, number, finalI)) {
-                  return false;
-                }
-                templateToken = null;
-              }
-              else if (number > 1) {
-                return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1);
-              }
-              else {
-                assert number == 1;
-                if (!startTemplateAndGotoChild(templateToken, finalI)) {
-                  return false;
-                }
-                templateToken = null;
-              }
-              myState = MyState.WORD;
-            }
-            else {
-              fail();
-            }
-            break;
-        }
-      }
-      finish(startIndex == n);
-      return true;
-    }
-
-    private boolean startTemplateAndGotoChild(MyTemplateToken templateToken, final int index) {
-      final Object key = new Object();
-      myCallback.fixStartOfTemplate(key);
-      TemplateInvokationListener listener = new TemplateInvokationListener() {
-        public void finished(boolean inSeparateEvent) {
-          myState = MyState.WORD;
-          gotoChild(key);
-          if (inSeparateEvent) {
-            invoke(index + 1);
-          }
-        }
-      };
-      if (!invokeTemplate(templateToken, myCallback, listener, 0)) {
-        return false;
-      }
-      return true;
-    }
-
-    private boolean invokeTemplateSeveralTimes(final MyTemplateToken templateToken,
-                                               final int startIndex,
-                                               final int count,
-                                               final int globalIndex) {
-      final Object key = new Object();
-      myCallback.fixStartOfTemplate(key);
-      for (int i = startIndex; i < count; i++) {
-        final int finalI = i;
-        TemplateInvokationListener listener = new TemplateInvokationListener() {
-          public void finished(boolean inSeparateEvent) {
-            myState = MyState.WORD;
-            if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
-              myCallback.fixEndOffset();
-            }
-            myCallback.gotoEndOfTemplate(key);
-            if (inSeparateEvent) {
-              if (finalI + 1 < count) {
-                invokeTemplateSeveralTimes(templateToken, finalI + 1, count, globalIndex);
-              }
-              else {
-                invoke(globalIndex + 1);
-              }
-            }
-          }
-        };
-        if (!invokeTemplate(templateToken, myCallback, listener, i)) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    private boolean invokeTemplateAndProcessTail(final MyTemplateToken templateToken,
-                                                 final int startIndex,
-                                                 final int count,
-                                                 final int tailStart) {
-      final Object key = new Object();
-      myCallback.fixStartOfTemplate(key);
-      for (int i = startIndex; i < count; i++) {
-        final int finalI = i;
-        final boolean[] flag = new boolean[]{false};
-        TemplateInvokationListener listener = new TemplateInvokationListener() {
-          public void finished(boolean inSeparateEvent) {
-            gotoChild(key);
-            MyInterpreter interpreter = new MyInterpreter(myTokens, myCallback, MyState.WORD, new TemplateInvokationListener() {
-              public void finished(boolean inSeparateEvent) {
-                if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
-                  myCallback.fixEndOffset();
-                }
-                myCallback.gotoEndOfTemplate(key);
-                if (inSeparateEvent) {
-                  invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
-                }
-              }
-            });
-            if (interpreter.invoke(tailStart)) {
-              if (inSeparateEvent) {
-                invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
-              }
-            }
-            else {
-              flag[0] = true;
-            }
-          }
-        };
-        if (!invokeTemplate(templateToken, myCallback, listener, i) || flag[0]) {
-          return false;
-        }
-      }
-      finish(count == 0);
-      return true;
-    }
-  }
-
-}
\ No newline at end of file
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/MarkerToken.java
new file mode 100644 (file)
index 0000000..ba9c9af
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class MarkerToken extends Token {
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/NumberToken.java
new file mode 100644 (file)
index 0000000..4adf4f7
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class NumberToken extends Token {
+  final int myNumber;
+
+  NumberToken(int number) {
+    myNumber = number;
+  }
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/OperationToken.java
new file mode 100644 (file)
index 0000000..63bb2d3
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class OperationToken extends Token {
+  final char mySign;
+
+  OperationToken(char sign) {
+    mySign = sign;
+  }
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/State.java
new file mode 100644 (file)
index 0000000..23ff80b
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+enum State {
+  OPERATION, WORD, AFTER_NUMBER, NUMBER
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/TemplateToken.java
new file mode 100644 (file)
index 0000000..bb9b2f9
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+import com.intellij.codeInsight.template.impl.TemplateImpl;
+import com.intellij.openapi.util.Pair;
+
+import java.util.List;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class TemplateToken extends Token {
+  final String myKey;
+  final List<Pair<String, String>> myAttribute2Value;
+  TemplateImpl myTemplate;
+
+  TemplateToken(String key, List<Pair<String, String>> attribute2value) {
+    myKey = key;
+    myAttribute2Value = attribute2value;
+  }
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/Token.java
new file mode 100644 (file)
index 0000000..082b83b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class Token {
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java
new file mode 100644 (file)
index 0000000..eab488d
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+import com.intellij.codeInsight.template.CustomTemplateCallback;
+import com.intellij.codeInsight.template.TemplateInvokationListener;
+import com.intellij.codeInsight.template.impl.TemplateImpl;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.xml.*;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.containers.IntArrayList;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Eugene.Kudelevsky
+ */ 
+class XmlZenCodingInterpreter {
+  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.zencoding.XmlZenCodingInterpreter");
+  private static final String ATTRS = "ATTRS";
+
+  private final List<Token> myTokens;
+  private final CustomTemplateCallback myCallback;
+  private final TemplateInvokationListener myListener;
+  private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$";
+  private State myState;
+
+  XmlZenCodingInterpreter(List<Token> tokens,
+                          CustomTemplateCallback callback,
+                          State initialState,
+                          TemplateInvokationListener listener) {
+    myTokens = tokens;
+    myCallback = callback;
+    myListener = listener;
+    myState = initialState;
+  }
+
+  private void finish(boolean inSeparateEvent) {
+    myCallback.gotoEndOffset();
+    if (myListener != null) {
+      myListener.finished(inSeparateEvent);
+    }
+  }
+
+  private void gotoChild(Object templateBoundsKey) {
+    int startOfTemplate = myCallback.getStartOfTemplate(templateBoundsKey);
+    int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey);
+    Editor editor = myCallback.getEditor();
+    int offset = myCallback.getOffset();
+
+    PsiFile file = myCallback.getFile();
+
+    PsiElement element = file.findElementAt(offset);
+    if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) {
+      return;
+    }
+
+    int newOffset = -1;
+    XmlTag tag = PsiTreeUtil.findElementOfClassAtRange(file, startOfTemplate, endOfTemplate, XmlTag.class);
+    if (tag != null) {
+      for (PsiElement child : tag.getChildren()) {
+        if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_END_TAG_START) {
+          newOffset = child.getTextOffset();
+        }
+      }
+    }
+
+    if (newOffset >= 0) {
+      myCallback.fixEndOffset();
+      editor.getCaretModel().moveToOffset(newOffset);
+    }
+
+    /*CharSequence tagName = getPrecedingTagName(text, offset, startOfTemplate);
+    if (tagName != null) {
+      *//*if (!hasClosingTag(text, tagName, offset, endOfTemplate)) {
+        document.insertString(offset, "</" + tagName + '>');
+      }*//*
+    }
+    else if (offset != endOfTemplate) {
+      tagName = getPrecedingTagName(text, endOfTemplate, startOfTemplate);
+      if (tagName != null) {
+        *//*fixEndOffset();
+        document.insertString(endOfTemplate, "</" + tagName + '>');*//*
+        editor.getCaretModel().moveToOffset(endOfTemplate);
+      }
+    }*/
+  }
+
+  public boolean invoke(int startIndex) {
+    final int n = myTokens.size();
+    TemplateToken templateToken = null;
+    int number = -1;
+    for (int i = startIndex; i < n; i++) {
+      final int finalI = i;
+      Token token = myTokens.get(i);
+      switch (myState) {
+        case OPERATION:
+          if (templateToken != null) {
+            if (token instanceof MarkerToken || token instanceof OperationToken) {
+              final char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER;
+              if (sign == XmlZenCodingTemplate.MARKER || sign == '+') {
+                final Object key = new Object();
+                myCallback.fixStartOfTemplate(key);
+                TemplateInvokationListener listener = new TemplateInvokationListener() {
+                  public void finished(boolean inSeparateEvent) {
+                    myState = State.WORD;
+                    if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
+                      myCallback.fixEndOffset();
+                    }
+                    if (sign == '+') {
+                      myCallback.gotoEndOfTemplate(key);
+                    }
+                    if (inSeparateEvent) {
+                      invoke(finalI + 1);
+                    }
+                  }
+                };
+                if (!invokeTemplate(templateToken, myCallback, listener, 0)) {
+                  return false;
+                }
+                templateToken = null;
+              }
+              else if (sign == '>') {
+                if (!startTemplateAndGotoChild(templateToken, finalI)) {
+                  return false;
+                }
+                templateToken = null;
+              }
+              else if (sign == '*') {
+                myState = State.NUMBER;
+              }
+            }
+            else {
+              fail();
+            }
+          }
+          break;
+        case WORD:
+          if (token instanceof TemplateToken) {
+            templateToken = ((TemplateToken)token);
+            myState = State.OPERATION;
+          }
+          else {
+            fail();
+          }
+          break;
+        case NUMBER:
+          if (token instanceof NumberToken) {
+            number = ((NumberToken)token).myNumber;
+            myState = State.AFTER_NUMBER;
+          }
+          else {
+            fail();
+          }
+          break;
+        case AFTER_NUMBER:
+          if (token instanceof MarkerToken || token instanceof OperationToken) {
+            char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER;
+            if (sign == XmlZenCodingTemplate.MARKER || sign == '+') {
+              if (!invokeTemplateSeveralTimes(templateToken, 0, number, finalI)) {
+                return false;
+              }
+              templateToken = null;
+            }
+            else if (number > 1) {
+              return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1);
+            }
+            else {
+              assert number == 1;
+              if (!startTemplateAndGotoChild(templateToken, finalI)) {
+                return false;
+              }
+              templateToken = null;
+            }
+            myState = State.WORD;
+          }
+          else {
+            fail();
+          }
+          break;
+      }
+    }
+    finish(startIndex == n);
+    return true;
+  }
+
+  private boolean startTemplateAndGotoChild(TemplateToken templateToken, final int index) {
+    final Object key = new Object();
+    myCallback.fixStartOfTemplate(key);
+    TemplateInvokationListener listener = new TemplateInvokationListener() {
+      public void finished(boolean inSeparateEvent) {
+        myState = State.WORD;
+        gotoChild(key);
+        if (inSeparateEvent) {
+          invoke(index + 1);
+        }
+      }
+    };
+    if (!invokeTemplate(templateToken, myCallback, listener, 0)) {
+      return false;
+    }
+    return true;
+  }
+
+  private boolean invokeTemplateSeveralTimes(final TemplateToken templateToken,
+                                             final int startIndex,
+                                             final int count,
+                                             final int globalIndex) {
+    final Object key = new Object();
+    myCallback.fixStartOfTemplate(key);
+    for (int i = startIndex; i < count; i++) {
+      final int finalI = i;
+      TemplateInvokationListener listener = new TemplateInvokationListener() {
+        public void finished(boolean inSeparateEvent) {
+          myState = State.WORD;
+          if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
+            myCallback.fixEndOffset();
+          }
+          myCallback.gotoEndOfTemplate(key);
+          if (inSeparateEvent) {
+            if (finalI + 1 < count) {
+              invokeTemplateSeveralTimes(templateToken, finalI + 1, count, globalIndex);
+            }
+            else {
+              invoke(globalIndex + 1);
+            }
+          }
+        }
+      };
+      if (!invokeTemplate(templateToken, myCallback, listener, i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean invokeTemplateAndProcessTail(final TemplateToken templateToken,
+                                               final int startIndex,
+                                               final int count,
+                                               final int tailStart) {
+    final Object key = new Object();
+    myCallback.fixStartOfTemplate(key);
+    for (int i = startIndex; i < count; i++) {
+      final int finalI = i;
+      final boolean[] flag = new boolean[]{false};
+      TemplateInvokationListener listener = new TemplateInvokationListener() {
+        public void finished(boolean inSeparateEvent) {
+          gotoChild(key);
+          XmlZenCodingInterpreter interpreter =
+            new XmlZenCodingInterpreter(myTokens, myCallback, State.WORD, new TemplateInvokationListener() {
+              public void finished(boolean inSeparateEvent) {
+                if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
+                  myCallback.fixEndOffset();
+                }
+                myCallback.gotoEndOfTemplate(key);
+                if (inSeparateEvent) {
+                  invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
+                }
+              }
+            });
+          if (interpreter.invoke(tailStart)) {
+            if (inSeparateEvent) {
+              invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
+            }
+          }
+          else {
+            flag[0] = true;
+          }
+        }
+      };
+      if (!invokeTemplate(templateToken, myCallback, listener, i) || flag[0]) {
+        return false;
+      }
+    }
+    finish(count == 0);
+    return true;
+  }
+
+  private static boolean containsAttrsVar(TemplateImpl template) {
+    for (int i = 0; i < template.getVariableCount(); i++) {
+      String varName = template.getVariableNameAt(i);
+      if (ATTRS.equals(varName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static void removeVariablesWhichHasNoSegment(TemplateImpl template) {
+    Set<String> segments = new HashSet<String>();
+    for (int i = 0; i < template.getSegmentsCount(); i++) {
+      segments.add(template.getSegmentName(i));
+    }
+    IntArrayList varsToRemove = new IntArrayList();
+    for (int i = 0; i < template.getVariableCount(); i++) {
+      String varName = template.getVariableNameAt(i);
+      if (!segments.contains(varName)) {
+        varsToRemove.add(i);
+      }
+    }
+    for (int i = 0; i < varsToRemove.size(); i++) {
+      template.removeVariable(varsToRemove.get(i));
+    }
+  }
+
+  @Nullable
+  private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value, int numberInIteration) {
+    StringBuilder result = new StringBuilder();
+    for (Iterator<Pair<String, String>> it = attribute2value.iterator(); it.hasNext();) {
+      Pair<String, String> pair = it.next();
+      String name = pair.first;
+      String value = getValue(pair, numberInIteration);
+      result.append(name).append("=\"").append(value).append('"');
+      if (it.hasNext()) {
+        result.append(' ');
+      }
+    }
+    String attributes = result.toString();
+    attributes = attributes.length() > 0 ? ' ' + attributes : null;
+    Map<String, String> predefinedValues = null;
+    if (attributes != null) {
+      predefinedValues = new HashMap<String, String>();
+      predefinedValues.put(ATTRS, attributes);
+    }
+    return predefinedValues;
+  }
+
+  private static String getValue(Pair<String, String> pair, int numberInIteration) {
+    return pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1));
+  }
+
+  @Nullable
+  private static String addAttrsVar(TemplateImpl modifiedTemplate, XmlTag tag) {
+    String text = tag.getContainingFile().getText();
+    PsiElement[] children = tag.getChildren();
+    if (children.length >= 1 &&
+        children[0] instanceof XmlToken &&
+        ((XmlToken)children[0]).getTokenType() == XmlTokenType.XML_START_TAG_START) {
+      PsiElement beforeAttrs = children[0];
+      if (children.length >= 2 && children[1] instanceof XmlToken && ((XmlToken)children[1]).getTokenType() == XmlTokenType.XML_NAME) {
+        beforeAttrs = children[1];
+      }
+      TextRange range = beforeAttrs.getTextRange();
+      if (range == null) {
+        return null;
+      }
+      int offset = range.getEndOffset();
+      text = text.substring(0, offset) + " $ATTRS$" + text.substring(offset);
+      modifiedTemplate.addVariable(ATTRS, "", "", false);
+      return text;
+    }
+    return null;
+  }
+
+  private static boolean invokeTemplate(TemplateToken token,
+                                        final CustomTemplateCallback callback,
+                                        final TemplateInvokationListener listener,
+                                        int numberInIteration) {
+    List<Pair<String, String>> attr2value = new ArrayList<Pair<String, String>>(token.myAttribute2Value);
+    if (callback.isLiveTemplateApplicable(token.myKey)) {
+      if (token.myTemplate != null) {
+        TemplateImpl modifiedTemplate = token.myTemplate.copy();
+        XmlTag tag = XmlZenCodingTemplate.parseXmlTagInTemplate(token.myTemplate.getString(), callback.getProject());
+        assert tag != null;
+        for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
+          Pair<String, String> pair = iterator.next();
+          if (tag.getAttribute(pair.first) != null) {
+            tag.setAttribute(pair.first, getValue(pair, numberInIteration));
+            iterator.remove();
+          }
+        }
+        String text = null;
+        if (!containsAttrsVar(modifiedTemplate) && attr2value.size() > 0) {
+          String textWithAttrs = addAttrsVar(modifiedTemplate, tag);
+          if (textWithAttrs != null) {
+            text = textWithAttrs;
+          }
+          else {
+            for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
+              Pair<String, String> pair = iterator.next();
+              tag.setAttribute(pair.first, getValue(pair, numberInIteration));
+              iterator.remove();
+            }
+          }
+        }
+        if (text == null) {
+          text = tag.getContainingFile().getText();
+        }
+        modifiedTemplate.setString(text);
+        removeVariablesWhichHasNoSegment(modifiedTemplate);
+        Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
+        return callback.startTemplate(modifiedTemplate, predefinedValues, listener);
+      }
+      else {
+        Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
+        return callback.startTemplate(token.myKey, predefinedValues, listener);
+      }
+    }
+    else {
+      TemplateImpl template = new TemplateImpl("", "");
+      template.addTextSegment('<' + token.myKey);
+      if (attr2value.size() > 0) {
+        template.addVariable(ATTRS, "", "", false);
+        template.addVariableSegment(ATTRS);
+      }
+      template.addTextSegment(">");
+      template.addVariableSegment(TemplateImpl.END);
+      template.addTextSegment("</" + token.myKey + ">");
+      template.setToReformat(true);
+      Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
+      return callback.startTemplate(template, predefinedValues, listener);
+    }
+  }
+
+  private static void fail() {
+    LOG.error("Input string was checked incorrectly during isApplicable() invokation");
+  }
+}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java b/xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java
new file mode 100644 (file)
index 0000000..29fdbb9
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.template.zencoding;
+
+import com.intellij.application.options.editor.WebEditorOptions;
+import com.intellij.codeInsight.template.CustomLiveTemplate;
+import com.intellij.codeInsight.template.CustomTemplateCallback;
+import com.intellij.codeInsight.template.TemplateInvokationListener;
+import com.intellij.codeInsight.template.impl.TemplateImpl;
+import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.xml.*;
+import com.intellij.util.containers.HashSet;
+import org.apache.xerces.util.XML11Char;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class XmlZenCodingTemplate implements CustomLiveTemplate {
+  static final char MARKER = '$';
+  private static final String OPERATIONS = ">+*";
+  private static final String SELECTORS = ".#[";
+  private static final String ID = "id";
+  private static final String CLASS = "class";
+
+  private static int parseNonNegativeInt(@NotNull String s) {
+    try {
+      return Integer.parseInt(s);
+    }
+    catch (Throwable ignored) {
+    }
+    return -1;
+  }
+
+  private static String getPrefix(@NotNull String templateKey) {
+    for (int i = 0, n = templateKey.length(); i < n; i++) {
+      char c = templateKey.charAt(i);
+      if (SELECTORS.indexOf(c) >= 0) {
+        return templateKey.substring(0, i);
+      }
+    }
+    return templateKey;
+  }
+
+  @Nullable
+  private static Pair<String, String> parseAttrNameAndValue(@NotNull String text) {
+    int eqIndex = text.indexOf('=');
+    if (eqIndex > 0) {
+      return new Pair<String, String>(text.substring(0, eqIndex), text.substring(eqIndex + 1));
+    }
+    return null;
+  }
+
+  @Nullable
+  private static TemplateToken parseSelectors(@NotNull String text) {
+    String templateKey = null;
+    List<Pair<String, String>> attributes = new ArrayList<Pair<String, String>>();
+    Set<String> definedAttrs = new HashSet<String>();
+    final List<String> classes = new ArrayList<String>();
+    StringBuilder builder = new StringBuilder();
+    char lastDelim = 0;
+    text += MARKER;
+    int classAttrPosition = -1;
+    for (int i = 0, n = text.length(); i < n; i++) {
+      char c = text.charAt(i);
+      if (c == '#' || c == '.' || c == '[' || c == ']' || i == n - 1) {
+        if (c != ']') {
+          switch (lastDelim) {
+            case 0:
+              templateKey = builder.toString();
+              break;
+            case '#':
+              if (!definedAttrs.add(ID)) {
+                return null;
+              }
+              attributes.add(new Pair<String, String>(ID, builder.toString()));
+              break;
+            case '.':
+              if (builder.length() <= 0) {
+                return null;
+              }
+              if (classAttrPosition < 0) {
+                classAttrPosition = attributes.size();
+              }
+              classes.add(builder.toString());
+              break;
+            case ']':
+              if (builder.length() > 0) {
+                return null;
+              }
+              break;
+            default:
+              return null;
+          }
+        }
+        else if (lastDelim != '[') {
+          return null;
+        }
+        else {
+          Pair<String, String> pair = parseAttrNameAndValue(builder.toString());
+          if (pair == null || !definedAttrs.add(pair.first)) {
+            return null;
+          }
+          attributes.add(pair);
+        }
+        lastDelim = c;
+        builder = new StringBuilder();
+      }
+      else {
+        builder.append(c);
+      }
+    }
+    if (classes.size() > 0) {
+      if (definedAttrs.contains(CLASS)) {
+        return null;
+      }
+      StringBuilder classesAttrValue = new StringBuilder();
+      for (int i = 0; i < classes.size(); i++) {
+        classesAttrValue.append(classes.get(i));
+        if (i < classes.size() - 1) {
+          classesAttrValue.append(' ');
+        }
+      }
+      assert classAttrPosition >= 0;
+      attributes.add(classAttrPosition, new Pair<String, String>(CLASS, classesAttrValue.toString()));
+    }
+    return new TemplateToken(templateKey, attributes);
+  }
+
+  private static boolean isXML11ValidQName(String str) {
+    final int colon = str.indexOf(':');
+    if (colon == 0 || colon == str.length() - 1) {
+      return false;
+    }
+    if (colon > 0) {
+      final String prefix = str.substring(0, colon);
+      final String localPart = str.substring(colon + 1);
+      return XML11Char.isXML11ValidNCName(prefix) && XML11Char.isXML11ValidNCName(localPart);
+    }
+    return XML11Char.isXML11ValidNCName(str);
+  }
+
+  private static boolean generateTemplateAndAddToToken(TemplateToken token, CustomTemplateCallback callback) {
+    TemplateImpl template = callback.findApplicableTemplate(token.myKey);
+    assert template != null;
+    XmlTag tag = parseXmlTagInTemplate(template.getString(), callback.getProject());
+    if (tag == null) {
+      return false;
+    }
+    token.myTemplate = template;
+    return true;
+  }
+
+  @Nullable
+  private static List<Token> parse(@NotNull String text, @NotNull CustomTemplateCallback callback) {
+    text += MARKER;
+    StringBuilder templateKeyBuilder = new StringBuilder();
+    List<Token> result = new ArrayList<Token>();
+    for (int i = 0, n = text.length(); i < n; i++) {
+      char c = text.charAt(i);
+      if (i == n - 1 || (i < n - 2 && OPERATIONS.indexOf(c) >= 0)) {
+        String key = templateKeyBuilder.toString();
+        templateKeyBuilder = new StringBuilder();
+        int num = parseNonNegativeInt(key);
+        if (num > 0) {
+          result.add(new NumberToken(num));
+        }
+        else {
+          if (key.length() == 0) {
+            return null;
+          }
+          String prefix = getPrefix(key);
+          boolean applicable = callback.isLiveTemplateApplicable(prefix);
+          if (!applicable && !isXML11ValidQName(prefix)) {
+            return null;
+          }
+          TemplateToken token = parseSelectors(key);
+          if (token == null) {
+            return null;
+          }
+          if (applicable && token.myAttribute2Value.size() > 0) {
+            assert prefix.equals(token.myKey);
+            if (!generateTemplateAndAddToToken(token, callback)) {
+              return null;
+            }
+          }
+          result.add(token);
+        }
+        result.add(i < n - 1 ? new OperationToken(c) : new MarkerToken());
+      }
+      else if (!Character.isWhitespace(c)) {
+        templateKeyBuilder.append(c);
+      }
+      else {
+        return null;
+      }
+    }
+    return result;
+  }
+
+  private static boolean check(@NotNull Collection<Token> tokens) {
+    State state = State.WORD;
+    for (Token token : tokens) {
+      if (token instanceof MarkerToken) {
+        break;
+      }
+      switch (state) {
+        case OPERATION:
+          if (token instanceof OperationToken) {
+            state = ((OperationToken)token).mySign == '*' ? State.NUMBER : State.WORD;
+          }
+          else {
+            return false;
+          }
+          break;
+        case WORD:
+          if (token instanceof TemplateToken) {
+            state = State.OPERATION;
+          }
+          else {
+            return false;
+          }
+          break;
+        case NUMBER:
+          if (token instanceof NumberToken) {
+            state = State.AFTER_NUMBER;
+          }
+          else {
+            return false;
+          }
+          break;
+        case AFTER_NUMBER:
+          if (token instanceof OperationToken && ((OperationToken)token).mySign != '*') {
+            state = State.WORD;
+          }
+          else {
+            return false;
+          }
+          break;
+      }
+    }
+    return state == State.OPERATION || state == State.AFTER_NUMBER;
+  }
+
+  private static String computeKey(Editor editor, int startOffset) {
+    int offset = editor.getCaretModel().getOffset();
+    String s = editor.getDocument().getCharsSequence().subSequence(startOffset, offset).toString();
+    int index = 0;
+    while (index < s.length() && Character.isWhitespace(s.charAt(index))) {
+      index++;
+    }
+    String key = s.substring(index);
+    int lastWhitespaceIndex = -1;
+    for (int i = 0; i < key.length(); i++) {
+      if (Character.isWhitespace(key.charAt(i))) {
+        lastWhitespaceIndex = i;
+      }
+    }
+    if (lastWhitespaceIndex >= 0 && lastWhitespaceIndex < key.length() - 1) {
+      return key.substring(lastWhitespaceIndex + 1);
+    }
+    return key;
+  }
+
+  public String computeTemplateKey(@NotNull CustomTemplateCallback callback) {
+    Editor editor = callback.getEditor();
+    int offset = callback.getOffset();
+    PsiElement element = callback.getFile().findElementAt(offset > 0 ? offset - 1 : offset);
+    int line = editor.getCaretModel().getLogicalPosition().line;
+    int lineStart = editor.getDocument().getLineStartOffset(line);
+    int parentStart;
+    do {
+      parentStart = element != null ? element.getTextRange().getStartOffset() : 0;
+      int startOffset = parentStart > lineStart ? parentStart : lineStart;
+      String key = computeKey(editor, startOffset);
+      List<Token> tokens = parse(key, callback);
+      if (tokens != null && check(tokens)) {
+        if (tokens.size() == 2) {
+          Token token = tokens.get(0);
+          if (token instanceof TemplateToken) {
+            if (key.equals(((TemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) {
+              // do not activate only live template
+              return null;
+            }
+          }
+        }
+        return key;
+      }
+      if (element != null) {
+        element = element.getParent();
+      }
+    }
+    while (element != null && parentStart > lineStart);
+    return null;
+  }
+
+  public boolean isApplicable(PsiFile file, int offset, boolean selection) {
+    WebEditorOptions webEditorOptions = WebEditorOptions.getInstance();
+    if (!webEditorOptions.isZenCodingEnabled()) {
+      return false;
+    }
+    if (file.getLanguage() instanceof XMLLanguage) {
+      PsiElement element = file.findElementAt(offset > 0 ? offset - 1 : offset);
+      if (element == null || element.getLanguage() instanceof XMLLanguage) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void expand(String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
+    List<Token> tokens = parse(key, callback);
+    assert tokens != null;
+    XmlZenCodingInterpreter interpreter = new XmlZenCodingInterpreter(tokens, callback, State.WORD, listener);
+    interpreter.invoke(0);
+  }
+
+  public void wrap(String selection, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
+  }
+
+  @Nullable
+  static XmlTag parseXmlTagInTemplate(String templateString, Project project) {
+    XmlFile xmlFile = (XmlFile)PsiFileFactory.getInstance(project).createFileFromText("dummy.xml", templateString);
+    XmlDocument document = xmlFile.getDocument();
+    return document == null ? null : document.getRootTag();
+  }
+}
\ No newline at end of file