001: /*******************************************************************************
002: * Copyright (c) 2003, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.pde.internal.core.text;
011:
012: import java.io.StringReader;
013: import java.util.ArrayList;
014: import java.util.Stack;
015:
016: import org.eclipse.jface.text.BadLocationException;
017: import org.eclipse.jface.text.FindReplaceDocumentAdapter;
018: import org.eclipse.jface.text.IDocument;
019: import org.eclipse.jface.text.IRegion;
020: import org.eclipse.jface.text.Position;
021: import org.eclipse.jface.text.Region;
022: import org.eclipse.pde.internal.core.util.PDEXMLHelper;
023: import org.xml.sax.Attributes;
024: import org.xml.sax.InputSource;
025: import org.xml.sax.Locator;
026: import org.xml.sax.SAXException;
027: import org.xml.sax.SAXParseException;
028: import org.xml.sax.helpers.DefaultHandler;
029:
030: public abstract class DocumentHandler extends DefaultHandler {
031:
032: protected FindReplaceDocumentAdapter fFindReplaceAdapter;
033: protected Stack fDocumentNodeStack = new Stack();
034: protected int fHighestOffset = 0;
035: private Locator fLocator;
036: private IDocumentElementNode fLastError;
037: private boolean fReconciling;
038:
039: public DocumentHandler(boolean reconciling) {
040: fReconciling = reconciling;
041: }
042:
043: /* (non-Javadoc)
044: * @see org.xml.sax.helpers.DefaultHandler#startDocument()
045: */
046: public void startDocument() throws SAXException {
047: fDocumentNodeStack.clear();
048: fHighestOffset = 0;
049: fLastError = null;
050: fFindReplaceAdapter = new FindReplaceDocumentAdapter(
051: getDocument());
052: }
053:
054: /**
055: * @return
056: */
057: protected IDocumentElementNode getLastParsedDocumentNode() {
058: if (fDocumentNodeStack.isEmpty()) {
059: return null;
060: }
061: return (IDocumentElementNode) fDocumentNodeStack.peek();
062: }
063:
064: /* (non-Javadoc)
065: * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
066: */
067: public void startElement(String uri, String localName,
068: String qName, Attributes attributes) throws SAXException {
069: IDocumentElementNode parent = getLastParsedDocumentNode();
070: IDocumentElementNode node = getDocumentNode(qName, parent);
071: try {
072: int nodeOffset = getStartOffset(qName);
073: node.setOffset(nodeOffset);
074: IDocument doc = getDocument();
075: int line = doc.getLineOfOffset(nodeOffset);
076: node.setLineIndent(node.getOffset()
077: - doc.getLineOffset(line));
078: // create attributes
079: for (int i = 0; i < attributes.getLength(); i++) {
080: String attName = attributes.getQName(i);
081: String attValue = attributes.getValue(i);
082: IDocumentAttributeNode attribute = getDocumentAttribute(
083: attName, attValue, node);
084: if (attribute != null) {
085: IRegion region = getAttributeRegion(attName,
086: attValue, nodeOffset);
087: if (region == null) {
088: attValue = PDEXMLHelper
089: .getWritableString(attValue);
090: region = getAttributeRegion(attName, attValue,
091: nodeOffset);
092: }
093: if (region != null) {
094: attribute.setNameOffset(region.getOffset());
095: attribute.setNameLength(attName.length());
096: attribute.setValueOffset(region.getOffset()
097: + region.getLength() - 1
098: - attValue.length());
099: attribute.setValueLength(attValue.length());
100: }
101: node.setXMLAttribute(attribute);
102: }
103: }
104: removeOrphanAttributes(node);
105: } catch (BadLocationException e) {
106: }
107: if (parent != null && node != null
108: && node.getParentNode() == null) {
109: if (fReconciling) {
110: // find right place for the child
111: // this is necessary to save as much as possible from the model
112: // we do not want an xml element with one tag to overwrite an element
113: // with a different tag
114: int position = 0;
115: IDocumentElementNode[] children = parent
116: .getChildNodes();
117: for (; position < children.length; position++) {
118: if (children[position].getOffset() == -1)
119: break;
120: }
121: parent.addChildNode(node, position);
122: } else {
123: parent.addChildNode(node);
124: }
125: }
126: fDocumentNodeStack.push(node);
127: }
128:
129: protected abstract IDocumentElementNode getDocumentNode(
130: String name, IDocumentElementNode parent);
131:
132: protected abstract IDocumentAttributeNode getDocumentAttribute(
133: String name, String value, IDocumentElementNode parent);
134:
135: protected abstract IDocumentTextNode getDocumentTextNode(
136: String content, IDocumentElementNode parent);
137:
138: private int getStartOffset(String elementName)
139: throws BadLocationException {
140: int line = fLocator.getLineNumber();
141: int col = fLocator.getColumnNumber();
142: IDocument doc = getDocument();
143: if (col < 0)
144: col = doc.getLineLength(line);
145: String text = doc.get(fHighestOffset + 1, doc
146: .getLineOffset(line)
147: - fHighestOffset - 1);
148:
149: ArrayList commentPositions = new ArrayList();
150: for (int idx = 0; idx < text.length();) {
151: idx = text.indexOf("<!--", idx); //$NON-NLS-1$
152: if (idx == -1)
153: break;
154: int end = text.indexOf("-->", idx); //$NON-NLS-1$
155: if (end == -1)
156: break;
157:
158: commentPositions.add(new Position(idx, end - idx));
159: idx = end + 1;
160: }
161:
162: int idx = 0;
163: for (; idx < text.length(); idx += 1) {
164: idx = text.indexOf("<" + elementName, idx); //$NON-NLS-1$
165: if (idx == -1)
166: break;
167: boolean valid = true;
168: for (int i = 0; i < commentPositions.size(); i++) {
169: Position pos = (Position) commentPositions.get(i);
170: if (pos.includes(idx)) {
171: valid = false;
172: break;
173: }
174: }
175: if (valid)
176: break;
177: }
178: if (idx > -1)
179: fHighestOffset += idx + 1;
180: return fHighestOffset;
181: }
182:
183: private int getElementLength(IDocumentElementNode node, int line,
184: int column) throws BadLocationException {
185: int endIndex = node.getOffset();
186: IDocument doc = getDocument();
187: int start = Math.max(doc.getLineOffset(line), node.getOffset());
188: column = doc.getLineLength(line);
189: String lineText = doc.get(start, column - start
190: + doc.getLineOffset(line));
191:
192: int index = lineText.indexOf("</" + node.getXMLTagName() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
193: if (index == -1) {
194: index = lineText.indexOf(">"); //$NON-NLS-1$
195: if (index == -1) {
196: endIndex = column;
197: } else {
198: endIndex = index + 1;
199: }
200: } else {
201: endIndex = index + node.getXMLTagName().length() + 3;
202: }
203: return start + endIndex - node.getOffset();
204: }
205:
206: private IRegion getAttributeRegion(String name, String value,
207: int offset) throws BadLocationException {
208: IRegion nameRegion = fFindReplaceAdapter.find(offset, name
209: + "\\s*=\\s*\"", true, true, false, true); //$NON-NLS-1$
210: if (nameRegion != null) {
211: if (getDocument().get(
212: nameRegion.getOffset() + nameRegion.getLength(),
213: value.length()).equals(value))
214: return new Region(nameRegion.getOffset(), nameRegion
215: .getLength()
216: + value.length() + 1);
217: }
218: return null;
219: }
220:
221: /* (non-Javadoc)
222: * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
223: */
224: public void endElement(String uri, String localName, String qName)
225: throws SAXException {
226: if (fDocumentNodeStack.isEmpty())
227: return;
228:
229: IDocumentElementNode node = (IDocumentElementNode) fDocumentNodeStack
230: .pop();
231: try {
232: node.setLength(getElementLength(node, fLocator
233: .getLineNumber() - 1, fLocator.getColumnNumber()));
234: setTextNodeOffset(node);
235: } catch (BadLocationException e) {
236: }
237: removeOrphanElements(node);
238: }
239:
240: protected void setTextNodeOffset(IDocumentElementNode node)
241: throws BadLocationException {
242: IDocumentTextNode textNode = node.getTextNode();
243: if (textNode != null && textNode.getText() != null) {
244: if (textNode.getText().trim().length() == 0) {
245: node.removeTextNode();
246: return;
247: }
248: IDocument doc = getDocument();
249: String text = doc.get(node.getOffset(), node.getLength());
250: // 1st char of text node
251: int relativeStartOffset = text.indexOf('>') + 1;
252: // last char of text node
253: int relativeEndOffset = text.lastIndexOf('<') - 1;
254:
255: if ((relativeStartOffset < 0)
256: || (relativeStartOffset >= text.length())) {
257: return;
258: } else if ((relativeEndOffset < 0)
259: || (relativeEndOffset >= text.length())) {
260: return;
261: }
262:
263: // trim whitespace
264: while (Character.isWhitespace(text
265: .charAt(relativeStartOffset)))
266: relativeStartOffset += 1;
267: while (Character.isWhitespace(text
268: .charAt(relativeEndOffset)))
269: relativeEndOffset -= 1;
270:
271: textNode.setOffset(node.getOffset() + relativeStartOffset);
272: textNode.setLength(relativeEndOffset - relativeStartOffset
273: + 1);
274: textNode.setText(textNode.getText().trim());
275: }
276: }
277:
278: /* (non-Javadoc)
279: * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
280: */
281: public void fatalError(SAXParseException e) throws SAXException {
282: generateErrorElementHierarchy();
283: }
284:
285: /**
286: *
287: */
288: private void generateErrorElementHierarchy() {
289: while (!fDocumentNodeStack.isEmpty()) {
290: IDocumentElementNode node = (IDocumentElementNode) fDocumentNodeStack
291: .pop();
292: node.setIsErrorNode(true);
293: removeOrphanAttributes(node);
294: removeOrphanElements(node);
295: if (fLastError == null)
296: fLastError = node;
297: }
298: }
299:
300: /* (non-Javadoc)
301: * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
302: */
303: public void error(SAXParseException e) throws SAXException {
304: generateErrorElementHierarchy();
305: }
306:
307: /* (non-Javadoc)
308: * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
309: */
310: public void setDocumentLocator(Locator locator) {
311: fLocator = locator;
312: }
313:
314: protected abstract IDocument getDocument();
315:
316: public InputSource resolveEntity(String publicId, String systemId)
317: throws SAXException {
318: // Prevent the resolution of external entities in order to
319: // prevent the parser from accessing the Internet
320: // This will prevent huge workbench performance degradations and hangs
321: return new InputSource(new StringReader("")); //$NON-NLS-1$
322: }
323:
324: public IDocumentElementNode getLastErrorNode() {
325: return fLastError;
326: }
327:
328: /* (non-Javadoc)
329: * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
330: */
331: public void characters(char[] ch, int start, int length)
332: throws SAXException {
333: if (!fReconciling || fDocumentNodeStack.isEmpty())
334: return;
335:
336: IDocumentElementNode parent = (IDocumentElementNode) fDocumentNodeStack
337: .peek();
338: StringBuffer buffer = new StringBuffer();
339: buffer.append(ch, start, length);
340: getDocumentTextNode(buffer.toString(), parent);
341: }
342:
343: private void removeOrphanAttributes(IDocumentElementNode node) {
344: // when typing by hand, one element may overwrite a different existing one
345: // remove all attributes from previous element, if any.
346: if (fReconciling) {
347: IDocumentAttributeNode[] attrs = node.getNodeAttributes();
348: for (int i = 0; i < attrs.length; i++) {
349: if (attrs[i].getNameOffset() == -1)
350: node.removeDocumentAttribute(attrs[i]);
351: }
352: }
353: }
354:
355: private void removeOrphanElements(IDocumentElementNode node) {
356: // when typing by hand, one element may overwrite a different existing one
357: // remove all excess children elements, if any.
358: if (fReconciling) {
359: IDocumentElementNode[] children = node.getChildNodes();
360: for (int i = 0; i < children.length; i++) {
361: if (children[i].getOffset() == -1) {
362: node.removeChildNode(children[i]);
363: }
364: }
365: }
366: }
367:
368: protected boolean isReconciling() {
369: return fReconciling;
370: }
371:
372: }
|