2 * Copyright 2000-2009 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
.psi
.formatter
.xml
;
18 import com
.intellij
.formatting
.FormattingDocumentModel
;
19 import com
.intellij
.formatting
.WrapType
;
20 import com
.intellij
.lang
.ASTNode
;
21 import com
.intellij
.openapi
.util
.TextRange
;
22 import com
.intellij
.psi
.PsiElement
;
23 import com
.intellij
.psi
.TokenType
;
24 import com
.intellij
.psi
.codeStyle
.CodeStyleSettings
;
25 import com
.intellij
.psi
.impl
.source
.SourceTreeToPsiMap
;
26 import com
.intellij
.psi
.impl
.source
.tree
.LeafElement
;
27 import com
.intellij
.psi
.xml
.XmlElementType
;
28 import com
.intellij
.psi
.xml
.XmlTag
;
30 import java
.util
.HashMap
;
33 public class HtmlPolicy
extends XmlFormattingPolicy
{
35 protected final CodeStyleSettings mySettings
;
37 public HtmlPolicy(final CodeStyleSettings settings
, final FormattingDocumentModel documentModel
) {
39 mySettings
= settings
;
43 public boolean indentChildrenOf(final XmlTag parentTag
) {
44 if (parentTag
== null) {
47 final PsiElement firstChild
= findFirstNonEmptyChild(parentTag
);
49 if (firstChild
== null) {
53 if (firstChild
.getNode().getElementType() != XmlElementType
.XML_START_TAG_START
) {
57 if (mySettings
.HTML_DO_NOT_ALIGN_CHILDREN_OF_MIN_LINES
> 0 && getLines(parentTag
) > mySettings
.HTML_DO_NOT_ALIGN_CHILDREN_OF_MIN_LINES
)
62 return !checkName(parentTag
, mySettings
.HTML_DO_NOT_INDENT_CHILDREN_OF
);
66 private PsiElement
findFirstNonEmptyChild(final XmlTag parentTag
) {
67 PsiElement result
= parentTag
.getFirstChild();
68 while (result
!= null && result
.getTextLength() == 0) {
69 result
= result
.getNextSibling();
74 private int getLines(final XmlTag parentTag
) {
75 final TextRange textRange
= parentTag
.getTextRange();
76 return myDocumentModel
.getLineNumber(textRange
.getEndOffset()) - myDocumentModel
.getLineNumber(textRange
.getStartOffset());
79 public boolean insertLineBreakBeforeTag(final XmlTag xmlTag
) {
80 PsiElement prev
= xmlTag
.getPrevSibling();
81 if (prev
== null) return false;
82 ASTNode prevNode
= SourceTreeToPsiMap
.psiElementToTree(prev
);
83 while (prevNode
!= null && containsWhiteSpacesOnly(prevNode
)) {
84 prevNode
= prevNode
.getTreePrev();
86 if (prevNode
== null) return false;
87 if (!(SourceTreeToPsiMap
.treeElementToPsi(prevNode
)instanceof XmlTag
)) return false;
88 return checkName(xmlTag
, mySettings
.HTML_ELEMENTS_TO_INSERT_NEW_LINE_BEFORE
);
91 private boolean containsWhiteSpacesOnly(final ASTNode node
) {
92 if (node
== null) return false;
93 if (node
.getElementType() == TokenType
.WHITE_SPACE
) return true;
94 if (node
instanceof LeafElement
) return false;
95 ASTNode child
= node
.getFirstChildNode();
96 while (child
!= null) {
97 if (!containsWhiteSpacesOnly(child
)) return false;
98 child
= child
.getTreeNext();
103 public boolean removeLineBreakBeforeTag(final XmlTag xmlTag
) {
104 return checkName(xmlTag
, mySettings
.HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE
);
107 protected boolean checkName(XmlTag tag
, String option
) {
108 if (option
== null) return false;
109 for (String name
: getTagNames(option
)) {
110 if (name
.trim().equalsIgnoreCase(tag
.getName())) return true;
115 private final Map
<String
, String
[]> myCachedSplits
= new HashMap
<String
, String
[]>();
117 private String
[] getTagNames(final String option
) {
118 String
[] splits
= myCachedSplits
.get(option
);
119 if (splits
== null) {
120 splits
= option
.split(",");
121 myCachedSplits
.put(option
, splits
);
126 public boolean keepWhiteSpacesInsideTag(final XmlTag tag
) {
127 return checkName(tag
, mySettings
.HTML_KEEP_WHITESPACES_INSIDE
) || "jsp:attribute".equals(tag
.getName());
130 public WrapType
getWrappingTypeForTagEnd(final XmlTag xmlTag
) {
131 return shouldBeWrapped(xmlTag
) ? WrapType
.ALWAYS
: WrapType
.NORMAL
;
134 public WrapType
getWrappingTypeForTagBegin(final XmlTag tag
) {
135 if (shouldBeWrapped(tag
)) {
136 return WrapType
.ALWAYS
;
139 if (!isInlineTag(tag
)) {
141 if (checkName(tag
, mySettings
.HTML_DONT_ADD_BREAKS_IF_INLINE_CONTENT
)) {
142 if (hasInlineContentOnly(tag
)) return WrapType
.NORMAL
;
145 return WrapType
.ALWAYS
;
148 return WrapType
.NORMAL
;
151 private boolean hasInlineContentOnly(final XmlTag tag
) {
152 final XmlTag
[] tags
= tag
.getSubTags();
153 for (int i
= 0; i
< tags
.length
; i
++) {
154 XmlTag xmlTag
= tags
[i
];
155 if (!isInlineTag(xmlTag
)) return false;
156 if (!hasInlineContentOnly(xmlTag
)) return false;
162 protected boolean isInlineTag(final XmlTag tag
) {
163 return checkName(tag
, mySettings
.HTML_INLINE_ELEMENTS
);
166 protected boolean shouldBeWrapped(final XmlTag tag
) {
170 public boolean isTextElement(XmlTag tag
) {
171 return isInlineTag(tag
);
174 public int getTextWrap(final XmlTag tag
) {
175 return mySettings
.HTML_TEXT_WRAP
;
178 public int getAttributesWrap() {
179 return mySettings
.HTML_ATTRIBUTE_WRAP
;
182 public boolean getShouldAlignAttributes() {
183 return mySettings
.HTML_ALIGN_ATTRIBUTES
;
186 public boolean getShouldAlignText() {
187 return mySettings
.HTML_ALIGN_TEXT
;
190 public boolean getShouldKeepWhiteSpaces() {
191 return mySettings
.HTML_KEEP_WHITESPACES
;
194 public boolean getShouldAddSpaceAroundEqualityInAttribute() {
195 return mySettings
.HTML_SPACE_AROUND_EQUALITY_IN_ATTRINUTE
;
198 public boolean getShouldAddSpaceAroundTagName() {
199 return mySettings
.HTML_SPACE_AFTER_TAG_NAME
;
202 public int getKeepBlankLines() {
203 return mySettings
.HTML_KEEP_BLANK_LINES
;
206 public boolean getShouldKeepLineBreaks() {
207 return mySettings
.HTML_KEEP_LINE_BREAKS
;
210 public boolean getShouldKeepLineBreaksInText() {
211 return mySettings
.HTML_KEEP_LINE_BREAKS_IN_TEXT
;
214 public CodeStyleSettings
getSettings() {
218 public boolean addSpaceIntoEmptyTag() {
219 return mySettings
.HTML_SPACE_INSIDE_EMPTY_TAG
;
222 public boolean shouldSaveSpacesBetweenTagAndText() {