All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.codeInsight.template.emmet.XmlEmmetParser Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition xml library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 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.emmet;

import com.google.common.base.Strings;
import com.intellij.codeInsight.template.CustomTemplateCallback;
import com.intellij.codeInsight.template.emmet.generators.ZenCodingGenerator;
import com.intellij.codeInsight.template.emmet.nodes.*;
import com.intellij.codeInsight.template.emmet.tokens.*;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.XmlElementFactory;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.intellij.openapi.util.text.StringUtil.startsWithIgnoreCase;

public class XmlEmmetParser extends EmmetParser {
  public static final String DEFAULT_ATTRIBUTE_NAME = "%default";
  public static final String BOOLEAN_ATTRIBUTE_VALUE = "%boolean";
  
  private static final String DEFAULT_TAG = "div";
  private static final int DEFAULT_LOREM_LENGTH = 30;
  private static final Pattern LOREM_PATTERN = Pattern.compile("(lorem|lipsum)(\\d*)");
  @NonNls private static final String DEFAULT_INLINE_TAG = "span";
  @NonNls private static final String LOREM_KEYWORD = "lorem";
  @NonNls private static final String LIPSUM_KEYWORD = "lipsum";

  private boolean hasTagContext = false;
  private final Stack tagLevel = new Stack();

  private static final Map parentChildTagMapping = new HashMap() {{
    put("p", "span");
    put("ul", "li");
    put("ol", "li");
    put("table", "tr");
    put("tr", "td");
    put("tbody", "tr");
    put("thead", "tr");
    put("tfoot", "tr");
    put("colgroup", "col");
    put("select", "option");
    put("optgroup", "option");
    put("audio", "source");
    put("video", "source");
    put("object", "param");
    put("map", "area");
  }};
  private boolean isHtml;

  public XmlEmmetParser(List tokens,
                        CustomTemplateCallback callback,
                        ZenCodingGenerator generator, boolean surroundWithTemplate) {
    super(tokens, callback, generator);
    PsiElement context = callback.getContext();
    XmlTag parentTag = PsiTreeUtil.getParentOfType(context, XmlTag.class);
    if (surroundWithTemplate && parentTag != null && context.getNode().getElementType() == XmlTokenType.XML_START_TAG_START) {
      parentTag = PsiTreeUtil.getParentOfType(parentTag, XmlTag.class);
    }
    isHtml = generator.isHtml(callback);
    if (parentTag != null) {
      hasTagContext = true;
      tagLevel.push(parentTag.getName());
    }
  }

  @Nullable
  private String parseAttributeName() {
    String name = "";
    ZenCodingToken token = getToken();
    while (token != null) {
      if ((token instanceof IdentifierToken)) {
        name += ((IdentifierToken)token).getText();
      }
      else if (token instanceof OperationToken && 
               (((OperationToken)token).getSign() == '+' || ((OperationToken)token).getSign() == '-')) {
        name += ((OperationToken)token).getSign();
      }
      else {
        break;
      }
      advance();
      token = getToken();
    }

    if (name.isEmpty()) {
      return null;
    }

    final XmlTag tag = XmlElementFactory.getInstance(myCallback.getProject()).createTagFromText("", StdLanguages.HTML);
    XmlAttribute[] attributes = tag.getAttributes();
    if (attributes.length == 1) {
      return attributes[0].getName();
    }
    else {
      return null;
    }
  }
  
  @NotNull
  private static String getAttributeValueByToken(@Nullable ZenCodingToken token) {
    if (token == null) {
      return "";
    }
    if (token instanceof StringLiteralToken) {
      final String text = ((StringLiteralToken)token).getText();
      return text.substring(1, text.length() - 1);
    }
    else if (token instanceof TextToken) {
      return ((TextToken)token).getText();
    }
    else if (token instanceof IdentifierToken) {
      return ((IdentifierToken)token).getText();
    }
    else if (token instanceof NumberToken) {
      return Integer.toString(((NumberToken)token).getNumber());
    }
    else if (token == ZenCodingTokens.DOT || token == ZenCodingTokens.SHARP) {
      return token.toString();
    }
    return "";
  }

  @Nullable
  @Override
  protected ZenCodingNode parseTemplate() {
    ZenCodingToken token = getToken();
    String templateKey = getDefaultTemplateKey();
    boolean mustHaveSelector = true;

    if (token instanceof IdentifierToken) {
      templateKey = ((IdentifierToken)token).getText();
      advance();
      if (startsWithIgnoreCase(templateKey, LOREM_KEYWORD) || startsWithIgnoreCase(templateKey, LIPSUM_KEYWORD)) {
        return parseLorem(templateKey);
      }
      mustHaveSelector = false;
    }

    if (templateKey == null) {
      return null;
    }

    TemplateImpl template = myCallback.findApplicableTemplate(templateKey);
    if (template == null && !ZenCodingUtil.isXML11ValidQName(templateKey) && !StringUtil.containsChar(templateKey, '$')) {
      return null;
    }

    final Map attributes = parseSelectors();
    if (mustHaveSelector && attributes.isEmpty()) {
      return null;
    }

    final TemplateToken templateToken = new TemplateToken(templateKey, attributes);
    if (!setTemplate(templateToken, template)) {
      return null;
    }
    return new TemplateNode(templateToken, myGenerator);
  }

  @Override
  protected ZenCodingNode parseClimbUpOperation(@Nullable ZenCodingNode leftPart) {
    popTagLevel();
    return super.parseClimbUpOperation(leftPart);
  }

  @Override
  protected ZenCodingNode parseMoreOperation(@Nullable ZenCodingNode leftPart) {
    String parentTag = getParentTag(leftPart);
    boolean hasParent = false;
    if (!Strings.isNullOrEmpty(parentTag)) {
      hasParent = true;
      tagLevel.push(parentTag);
    }
    ZenCodingNode result = super.parseMoreOperation(leftPart);
    if (result == null) {
      return null;
    }
    if (hasParent) {
      popTagLevel();
    }
    return result;
  }

  @Nullable
  private String getDefaultTemplateKey() {
    return isHtml ? suggestTagName() : null;
  }

  @Nullable
  private static String getParentTag(ZenCodingNode node) {
    if (node instanceof TemplateNode) {
      return ((TemplateNode)node).getTemplateToken().getKey();
    }
    else if (node instanceof MulOperationNode) {
      ZenCodingNode leftOperand = ((MulOperationNode)node).getLeftOperand();
      if (leftOperand instanceof TemplateNode) {
        return ((TemplateNode)leftOperand).getTemplateToken().getKey();
      }
    }
    return null;
  }

  @Nullable
  private ZenCodingNode parseLorem(String templateKey) {
    Matcher matcher = LOREM_PATTERN.matcher(templateKey);
    if (matcher.matches()) {
      int loremWordsCount = DEFAULT_LOREM_LENGTH;
      if (matcher.groupCount() > 1) {
        String group = matcher.group(2);
        loremWordsCount = group == null || group.isEmpty() ? DEFAULT_LOREM_LENGTH : Integer.parseInt(group);
      }

      final Map attributes = parseSelectors();
      ZenCodingToken token = getToken();
      boolean isRepeating = token instanceof OperationToken && ((OperationToken)token).getSign() == '*';
      if (!attributes.isEmpty() || isRepeating) {
        String wrapTag = suggestTagName();
        TemplateImpl template = myCallback.findApplicableTemplate(templateKey);
        if (template == null && !ZenCodingUtil.isXML11ValidQName(templateKey)) {
          return null;
        }
        final TemplateToken templateToken = new TemplateToken(wrapTag, attributes);
        if (!setTemplate(templateToken, template)) {
          return null;
        }
        return new MoreOperationNode(new TemplateNode(templateToken), new LoremNode(loremWordsCount));
      }
      else {
        return new LoremNode(loremWordsCount);
      }
    }
    else {
      return null;
    }
  }

  private String suggestTagName() {
    if (!tagLevel.empty()) {
      String parentTag = tagLevel.peek();
      if (parentChildTagMapping.containsKey(parentTag)) {
        return parentChildTagMapping.get(parentTag);
      }
      if (HtmlUtil.isPossiblyInlineTag(parentTag)) {
        return DEFAULT_INLINE_TAG;
      }
    }
    return DEFAULT_TAG;
  }

  private void popTagLevel() {
    if (tagLevel.size() > (hasTagContext ? 1 : 0)) {
      tagLevel.pop();
    }
  }

  @NotNull
  private Map parseSelectors() {
    final Map result = ContainerUtil.newLinkedHashMap();
    List> attrList = parseSelector();
    while (attrList != null) {
      for (Couple attr : attrList) {
        if (getClassAttributeName().equals(attr.first)) {
          result.put(getClassAttributeName(), (StringUtil.notNullize(result.get(getClassAttributeName())) + " " + attr.second).trim());
        }
        else if (HtmlUtil.ID_ATTRIBUTE_NAME.equals(attr.first)) {
          result.put(HtmlUtil.ID_ATTRIBUTE_NAME, (StringUtil.notNullize(result.get(HtmlUtil.ID_ATTRIBUTE_NAME)) + " " + attr.second).trim());
        }
        else {
          result.put(attr.first, attr.second);
        }
      }
      attrList = parseSelector();
    }
    return result;
  }

  @Nullable
  private List> parseSelector() {
    ZenCodingToken token = getToken();
    if (token == ZenCodingTokens.OPENING_SQ_BRACKET) {
      advance();
      final List> attrList = parseAttributeList();
      if (attrList == null || getToken() != ZenCodingTokens.CLOSING_SQ_BRACKET) {
        return null;
      }
      advance();
      return attrList;
    }

    if (token == ZenCodingTokens.DOT || token == ZenCodingTokens.SHARP) {
      final String name = token == ZenCodingTokens.DOT ? getClassAttributeName() : HtmlUtil.ID_ATTRIBUTE_NAME;
      advance();
      token = getToken();
      final String value = getAttributeValueByToken(token);
      if (!value.isEmpty()) {
        advance();
      }
      return Collections.singletonList(Couple.of(name, value));
    }

    return null;
  }

  @NotNull
  protected String getClassAttributeName() {
    return HtmlUtil.CLASS_ATTRIBUTE_NAME;
  }

  @Nullable
  private List> parseAttributeList() {
    final List> result = new ArrayList>();
    while (true) {
      final Couple attribute = parseAttribute();
      if (attribute == null) {
        return result;
      }
      result.add(attribute);

      final ZenCodingToken token = getToken();
      if (token != ZenCodingTokens.COMMA && token != ZenCodingTokens.SPACE) {
        return result;
      }
      advance();
    }
  }

  @Nullable
  private Couple parseAttribute() {
    final int position = getCurrentPosition();
    String attributeName = parseAttributeName();
    if (attributeName != null && !attributeName.isEmpty()) {
      if (getToken() == ZenCodingTokens.DOT) {
        if (isEndOfAttribute(nextToken(1))) {
          // boolean attribute
          advance(); // dot
          return Couple.of(attributeName, BOOLEAN_ATTRIBUTE_VALUE);
        }
      }
      else {
        // attribute with value
        if (getToken() == ZenCodingTokens.EQ) {
          advance();
          return Couple.of(attributeName, parseAttributeValue());
        }
        else {
          return Couple.of(attributeName, "");
        }
      }
    }
    restorePosition(position);

    final String impliedValue = parseAttributeValue();
    if (!impliedValue.isEmpty()) {
      // implied attribute
      return Couple.of(DEFAULT_ATTRIBUTE_NAME, impliedValue);
    }
    return null;
  }

  @NotNull
  private String parseAttributeValue() {
    ZenCodingToken token;
    final StringBuilder attrValueBuilder = new StringBuilder();
    String value;
    do {
      token = getToken();
      value = getAttributeValueByToken(token);
      attrValueBuilder.append(value);
      if (!isEndOfAttribute(token)) {
        advance();
      }
    }
    while (!isEndOfAttribute(token));
    return attrValueBuilder.toString();
  }

  private static boolean isEndOfAttribute(@Nullable ZenCodingToken nextToken) {
    return nextToken == null || nextToken == ZenCodingTokens.SPACE || nextToken == ZenCodingTokens.CLOSING_SQ_BRACKET 
           || nextToken == ZenCodingTokens.COMMA;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy