001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor.ext.html;
043:
044: import javax.swing.text.BadLocationException;
045: import org.netbeans.api.html.lexer.HTMLTokenId;
046: import org.netbeans.api.lexer.LanguagePath;
047: import org.netbeans.api.lexer.Token;
048: import org.netbeans.editor.BaseDocument;
049: import org.netbeans.editor.ext.html.dtd.DTD;
050: import org.netbeans.editor.ext.html.dtd.DTD.Element;
051: import org.netbeans.modules.editor.structure.formatting.TagBasedLexerFormatter;
052: import org.netbeans.modules.editor.structure.formatting.JoinedTokenSequence;
053:
054: /**
055: * A lexer-based formatter for html files.
056: * @author Tomasz.Slota@Sun.COM
057: */
058:
059: public class HTMLLexerFormatter extends TagBasedLexerFormatter {
060: /**
061: * Setting this flag in document property will make the HTML formatter act
062: * as the top-level language formatter. It is useful for Ruby and PHP editors.
063: * If this mechanism is used HTML formatter must be always used first.
064: */
065:
066: public static final String HTML_FORMATTER_ACTS_ON_TOP_LEVEL = "HTML_FORMATTER_ACTS_ON_TOP_LEVEL"; //NOI18N
067: private static final String[] UNFORMATTABLE_TAGS = new String[] {
068: "pre", "script", "code", "textarea" }; //NOI18N
069: private final LanguagePath languagePath;
070:
071: /** Creates a new instance of HTMLFormater */
072: public HTMLLexerFormatter(LanguagePath languagePath) {
073: this .languagePath = languagePath;
074: }
075:
076: @Override
077: protected int getTagEndingAtPosition(
078: JoinedTokenSequence JoinedTokenSequence, int position)
079: throws BadLocationException {
080: if (position >= 0) {
081: int originalOffset = JoinedTokenSequence.offset();
082: JoinedTokenSequence.move(position);
083: JoinedTokenSequence.moveNext();
084: Token token = JoinedTokenSequence.token();
085:
086: if (token.id() == HTMLTokenId.TAG_CLOSE_SYMBOL
087: && !token.text().toString().endsWith("/>")) { //NOI18N
088:
089: while (JoinedTokenSequence.movePrevious()) {
090: int tokenOffset = JoinedTokenSequence.offset();
091:
092: if (isOpeningTag(JoinedTokenSequence, tokenOffset)
093: || isClosingTag(JoinedTokenSequence,
094: tokenOffset)) {
095: int r = JoinedTokenSequence.offset();
096: JoinedTokenSequence.move(originalOffset);
097: JoinedTokenSequence.moveNext();
098: return r;
099: }
100: }
101: }
102:
103: JoinedTokenSequence.move(originalOffset);
104: JoinedTokenSequence.moveNext();
105: }
106: return -1;
107: }
108:
109: @Override
110: protected int getTagEndOffset(
111: JoinedTokenSequence JoinedTokenSequence, int tagStartOffset) {
112: int originalOffset = JoinedTokenSequence.offset();
113: JoinedTokenSequence.move(tagStartOffset);
114: JoinedTokenSequence.moveNext();
115: boolean thereAreMoreTokens = true;
116:
117: while (thereAreMoreTokens
118: && JoinedTokenSequence.token().id() != HTMLTokenId.TAG_CLOSE_SYMBOL) {
119: thereAreMoreTokens &= JoinedTokenSequence.moveNext();
120: }
121:
122: int r = JoinedTokenSequence.offset()
123: + JoinedTokenSequence.token().length();
124: JoinedTokenSequence.move(originalOffset);
125: JoinedTokenSequence.moveNext();
126: return thereAreMoreTokens ? r : -1;
127: }
128:
129: @Override
130: protected boolean isJustBeforeClosingTag(
131: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset)
132: throws BadLocationException {
133: // a workaround for the difference with XML syntax support
134: return super .isJustBeforeClosingTag(JoinedTokenSequence,
135: tagTokenOffset + "</".length()); //NOI18N
136: }
137:
138: @Override
139: protected boolean isClosingTag(
140: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset) {
141: Token token = getTokenAtOffset(JoinedTokenSequence,
142: tagTokenOffset);
143: return token != null && token.id() == HTMLTokenId.TAG_CLOSE;
144: }
145:
146: @Override
147: protected boolean isOpeningTag(
148: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset) {
149: Token token = getTokenAtOffset(JoinedTokenSequence,
150: tagTokenOffset);
151: return token != null && token.id() == HTMLTokenId.TAG_OPEN;
152: }
153:
154: @Override
155: protected String extractTagName(
156: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset) {
157: return getTokenAtOffset(JoinedTokenSequence, tagTokenOffset)
158: .text().toString().trim();
159: }
160:
161: @Override
162: protected boolean areTagNamesEqual(String tagName1, String tagName2) {
163: return tagName1.equalsIgnoreCase(tagName2);
164: }
165:
166: @Override
167: protected int getOpeningSymbolOffset(
168: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset) {
169: int originalOffset = JoinedTokenSequence.offset();
170: JoinedTokenSequence.move(tagTokenOffset);
171: boolean thereAreMoreTokens = true;
172:
173: do {
174: thereAreMoreTokens = JoinedTokenSequence.movePrevious();
175: } while (thereAreMoreTokens
176: && JoinedTokenSequence.token().id() != HTMLTokenId.TAG_OPEN_SYMBOL);
177:
178: if (thereAreMoreTokens) {
179: int r = JoinedTokenSequence.offset();
180: JoinedTokenSequence.move(originalOffset);
181: JoinedTokenSequence.moveNext();
182: return r;
183: }
184:
185: JoinedTokenSequence.move(originalOffset);
186: JoinedTokenSequence.moveNext();
187: return -1;
188: }
189:
190: @Override
191: protected boolean isClosingTagRequired(BaseDocument doc,
192: String tagName) {
193: HTMLSyntaxSupport htmlsup = HTMLSyntaxSupport.get(doc);
194: DTD dtd = htmlsup.getDTD();
195:
196: if (dtd == null) {
197: // Unknown DTD, do not automatically close any tag
198: return false;
199: }
200:
201: Element elem = dtd.getElement(tagName.toUpperCase());
202:
203: if (elem == null) {
204: // automatically close unknown tags
205: return true;
206: }
207:
208: return !elem.isEmpty(); // close tag unless it is known to be empty
209: }
210:
211: @Override
212: protected boolean isUnformattableToken(
213: JoinedTokenSequence JoinedTokenSequence, int tagTokenOffset) {
214: Token token = getTokenAtOffset(JoinedTokenSequence,
215: tagTokenOffset);
216:
217: if (token.id() == HTMLTokenId.BLOCK_COMMENT) {
218: return true;
219: }
220:
221: return false;
222: }
223:
224: @Override
225: protected boolean isUnformattableTag(String tag) {
226: for (int i = 0; i < UNFORMATTABLE_TAGS.length; i++) {
227: if (tag.equalsIgnoreCase(UNFORMATTABLE_TAGS[i])) {
228: return true;
229: }
230: }
231:
232: return false;
233: }
234:
235: @Override
236: protected boolean isTopLevelLanguage(BaseDocument doc) {
237: return super .isTopLevelLanguage(doc)
238: || doc.getProperty(HTML_FORMATTER_ACTS_ON_TOP_LEVEL) != null;
239: }
240:
241: protected LanguagePath supportedLanguagePath() {
242: return languagePath;
243: }
244: }
|