Source code: com/port80/eclipse/xml/editors/Util.java
1 package com.port80.eclipse.xml.editors;
2
3 import java.io.IOException;
4 import java.io.StringReader;
5
6 import javax.xml.parsers.DocumentBuilder;
7 import javax.xml.parsers.DocumentBuilderFactory;
8
9 import org.apache.xerces.dom.DOMInputSourceImpl;
10 import org.apache.xerces.dom3.DOMError;
11 import org.apache.xerces.dom3.DOMErrorHandler;
12 import org.apache.xerces.xni.XMLLocator;
13 import org.apache.xerces.xni.parser.XMLParserConfiguration;
14 import org.eclipse.core.resources.IFile;
15 import org.eclipse.jface.text.BadLocationException;
16 import org.eclipse.jface.text.IDocument;
17 import org.eclipse.jface.text.Position;
18 import org.eclipse.jface.text.source.ISourceViewer;
19 import org.eclipse.jface.text.source.SourceViewer;
20 import org.eclipse.ui.IEditorInput;
21 import org.eclipse.ui.editors.text.TextEditor;
22 import org.eclipse.ui.texteditor.ITextEditor;
23 import org.w3c.dom.Document;
24 import org.w3c.dom.Node;
25 import org.xml.sax.SAXException;
26 import org.xml.sax.SAXParseException;
27
28 import com.port80.eclipse.editors.EditorsPlugin;
29 import com.port80.eclipse.editors.EditorsUtil;
30 import com.port80.eclipse.xml.parser.IXRange;
31 import com.port80.eclipse.xml.parser.XComment;
32 import com.port80.eclipse.xml.parser.XDomBuilder;
33 import com.port80.eclipse.xml.parser.XElementNS;
34 import com.port80.eclipse.xml.parser.XProcessingInstruction;
35
36 /**
37 * Static utilities routines.
38 *
39 * @author chrisl
40 */
41 public class Util {
42
43 ////////////////////////////////////////////////////////////////////////
44
45 private static final String NAME = "Util";
46 private static final boolean DEBUG = false;
47
48 ////////////////////////////////////////////////////////////////////////
49
50 public static Document parseXML(TextEditor editor, SourceViewer viewer, boolean quiet) {
51 return customParse(editor, viewer, quiet);
52 }
53
54 /** Parse a XML document using JAXP crimson XML parser. */
55 public static Document crimsonParse(TextEditor editor, SourceViewer viewer) {
56 IEditorInput input = editor.getEditorInput();
57 IFile file = (IFile) input.getAdapter(IFile.class);
58 if (file == null)
59 return null;
60 DocumentBuilder builder;
61 try {
62 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
63 if (DEBUG)
64 System.err.println(NAME + ".crimsonParse(): builder=" + builder);
65 } catch (Exception e) {
66 EditorsPlugin.error("Error creating XML DOM builder", e, input);
67 return null;
68 }
69 try {
70 return builder.parse(file.getLocation().toFile());
71 } catch (SAXParseException e) {
72 reportError("Util.crimsonParser()", e, editor, viewer);
73 } catch (SAXException e) {
74 reportError("Util.crimsonParser()", e, editor, viewer);
75 } catch (IOException e) {
76 EditorsPlugin.error("Error creating XML DOM builder", e, input);
77 }
78 return null;
79 }
80
81 /** Parse a XML document using a custom Xerces DOMBuilder. */
82 public static Document customParse(TextEditor editor, SourceViewer viewer, boolean quiet) {
83 IEditorInput input = editor.getEditorInput();
84 IFile file = (IFile) input.getAdapter(IFile.class);
85 if (file == null)
86 return null;
87 String filepath = file.getLocation().toString();
88 String dir = file.getLocation().removeLastSegments(1).toString();
89 XDomBuilder builder = new CustomDomBuilder();
90 XMLParserConfiguration cf = builder.getConfiguration();
91 // cf.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
92 cf.setFeature("http://xml.org/sax/features/external-general-entities", false);
93 cf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
94 cf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
95 cf.setFeature("http://xml.org/sax/features/validation", false);
96 cf.setFeature("http://apache.org/xml/features/continue-after-fatal-error", false);
97 cf.setProperty(
98 "http://apache.org/xml/properties/internal/entity-manager",
99 CustomEntityManager.createEntityManager(cf));
100 builder.setErrorHandler(new CustomErrorHandler("customParser(): ", quiet, editor, viewer, builder));
101 if (DEBUG) {
102 System.err.println(NAME + ".xercesParse(): builder=" + builder);
103 System.err.println(NAME + ".xercesParse(): filepath=" + filepath + ", dir=" + dir);
104 }
105 try {
106 return builder.parse(
107 new DOMInputSourceImpl(
108 null,
109 filepath,
110 dir,
111 new StringReader(viewer.getDocument().get()),
112 null));
113 } catch (Exception e) {
114 // Errors have been reported in error handler.
115 return null;
116 }
117 }
118
119 /** Parse a XML document with validation. */
120 public static Document validateXML(TextEditor editor, SourceViewer viewer, boolean quiet) {
121 IEditorInput input = editor.getEditorInput();
122 IFile file = (IFile) input.getAdapter(IFile.class);
123 if (file == null)
124 return null;
125 String filepath = file.getLocation().toString();
126 String dir = file.getLocation().removeLastSegments(1).toString();
127 XDomBuilder builder = new CustomDomBuilder();
128 XMLParserConfiguration cf = builder.getConfiguration();
129 // cf.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
130 cf.setFeature("http://xml.org/sax/features/external-general-entities", true);
131 cf.setFeature("http://xml.org/sax/features/external-parameter-entities", true);
132 cf.setFeature("http://xml.org/sax/features/validation", true);
133 cf.setFeature("http://apache.org/xml/features/continue-after-fatal-error", false);
134 builder.setErrorHandler(new CustomErrorHandler("validateXML(): ", quiet, editor, viewer, builder));
135 if (DEBUG) {
136 System.err.println(NAME + ".xercesParse(): builder=" + builder);
137 System.err.println(NAME + ".xercesParse(): filepath=" + filepath + ", dir=" + dir);
138 }
139 Document ret = null;
140 try {
141 ret =
142 builder.parse(
143 new DOMInputSourceImpl(
144 null,
145 filepath,
146 dir,
147 new StringReader(viewer.getDocument().get()),
148 null));
149 CustomErrorHandler handler = (CustomErrorHandler) builder.getErrorHandler();
150 if (handler.getErrors() == 0)
151 EditorsPlugin.info("Validate passed.", null, input);
152 } catch (Exception e) {
153 XMLLocator loc = builder.getLocator();
154 if (loc != null) {
155 reportError("Validate error", e, loc, editor, viewer);
156 } else {
157 EditorsPlugin.error("Validate error", e, input);
158 }
159 return null;
160 }
161 return ret;
162 }
163
164 public static void reportError(String msg, SAXParseException e, ITextEditor editor, ISourceViewer viewer) {
165 // XMLParser line numbers starts from 1.
166 // NOTE: Apparently, e.getColumnNumber() return char. offset (starting at 1)
167 // in the line not column number!
168 int line = e.getLineNumber();
169 int col = e.getColumnNumber();
170 int offset = EditorsUtil.findOffset(line, 1, viewer) + col - 1;
171 col = EditorsUtil.findLineColumn(offset, viewer)[1];
172 EditorsPlugin.error(
173 offset,
174 offset,
175 msg + ": @(" + line + ", " + col + ")",
176 e,
177 editor.getEditorInput(),
178 viewer);
179 }
180
181 public static void reportError(String msg, SAXException e, ITextEditor editor, ISourceViewer viewer) {
182 Exception ex = e.getException();
183 IEditorInput input = editor.getEditorInput();
184 if (ex == null)
185 EditorsPlugin.error(msg, e, input);
186 else if (ex instanceof SAXParseException)
187 Util.reportError(msg, (SAXParseException) ex, editor, viewer);
188 else
189 EditorsPlugin.error(msg, ex, input);
190 }
191
192 public static void reportError(
193 String msg,
194 Exception e,
195 XMLLocator locator,
196 ITextEditor editor,
197 ISourceViewer viewer) {
198 // NOTE: XDomBuilder locator column number is actually column offset.
199 int offset = EditorsUtil.findOffset(locator.getLineNumber(), 1, viewer) + locator.getColumnNumber() - 1;
200 int[] pos = EditorsUtil.findLineColumn(offset, viewer);
201 EditorsPlugin.error(
202 offset,
203 offset,
204 msg + ": @(" + pos[0] + ", " + pos[1] + ")",
205 (Exception) e,
206 editor.getEditorInput(),
207 viewer);
208 }
209
210 ////////////////////////////////////////////////////////////////////////
211
212 /**
213 * @return The innermost element/comment/processing instruction node that enclose the given range.
214 * @param line 1-based line number.
215 * @param column 1-based column number.
216 */
217 public static Position findElementRange(int line, int column, Node node, IDocument doc) {
218 Position ret = null;
219 for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
220 ret = findElementRange(line, column, n, doc);
221 if (ret != null)
222 return ret;
223 }
224 if (node instanceof XElementNS || node instanceof XComment || node instanceof XProcessingInstruction) {
225 IXRange range = (IXRange) node;
226 IXRange end = getEndRange(node, doc);
227 int endline, endcolumn;
228 if (end != null) {
229 endline = end.getStartLine();
230 endcolumn = end.getStartColumn();
231 } else {
232 endline = range.getEndLine();
233 endcolumn = range.getEndColumn();
234 }
235 if ((range.getStartLine() < line
236 || (range.getStartLine() == line && range.getStartColumn() < column))
237 && (endline > line || (endline == line && endcolumn > column)))
238 return getRange(doc, range, endline, endcolumn);
239 }
240 return null;
241 }
242
243 /**
244 * @return Find node that starts at the given line and column.
245 * @param line 1-based line number.
246 * @param column 1-based column number.
247 */
248 public static Node findNode(int line, int column, Node node, IDocument doc) {
249 if (node instanceof IXRange) {
250 IXRange range = (IXRange) node;
251 if (range.getStartLine() == line && range.getStartColumn() == column)
252 return node;
253 }
254 if (node instanceof IXRange) {
255 IXRange range = (IXRange) node;
256 if (range.getStartLine() == line && range.getStartColumn() == column)
257 return node;
258 }
259 Node ret;
260 for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
261 ret = findNode(line, column, n, doc);
262 if (ret != null)
263 return ret;
264 }
265 return null;
266 }
267
268 public static Position getRange(IDocument doc, IXRange range, int endline, int endcolumn) {
269 int startoffset, endoffset;
270 try {
271 startoffset = doc.getLineOffset(range.getStartLine() - 1) + range.getStartColumn() - 1;
272 endoffset = doc.getLineOffset(endline - 1) + endcolumn - 1;
273 } catch (BadLocationException e) {
274 return new Position(-1, -1);
275 }
276 return new Position(startoffset, endoffset - startoffset);
277 }
278
279 public static IXRange getEndRange(Node node, IDocument doc) {
280 Node n = node.getNextSibling();
281 if (n != null && n instanceof IXRange)
282 return (IXRange) n;
283 return null;
284 // int line = doc.getNumberOfLines();
285 // int column;
286 // try {
287 // column = doc.getLineLength(line-1);
288 // } catch (BadLocationException e) {
289 // if(true) System.err.println(NAME+".getEndRange(): Bad location");
290 // return null;
291 // }
292 // //NOTE: If the line ends with a line delimiter, there should be a text node and would not reach here.
293 // // So here, column number do not include line delimiter and should be incremeted for 1-based
294 // // column number.
295 // return new XRange(line, column+1, line, column+1);
296 }
297
298 ////////////////////////////////////////////////////////////////////////
299
300 static class CustomDomBuilder extends XDomBuilder {
301 protected ClassLoader getClassLoader() {
302 return EditorsPlugin.getPluginClassLoader();
303 }
304 }
305
306 ////////////////////////////////////////////////////////////////////////
307
308 static class CustomErrorHandler implements DOMErrorHandler {
309 String fMessagePrefix;
310 boolean fQuiet;
311 TextEditor fEditor;
312 ISourceViewer fViewer;
313 XDomBuilder fBuilder;
314 DOMErrorHandler fHandler;
315 int fErrors;
316 public CustomErrorHandler(
317 String prefix,
318 boolean quiet,
319 TextEditor editor,
320 ISourceViewer viewer,
321 XDomBuilder builder) {
322 fBuilder = builder;
323 fMessagePrefix = prefix;
324 fEditor = editor;
325 fViewer = viewer;
326 fQuiet = quiet;
327 fHandler = fBuilder.getErrorHandler();
328 }
329
330 public int getErrors() {
331 return fErrors;
332 }
333
334 /**
335 * @see org.apache.xerces.dom3.DOMErrorHandler#handleError(org.apache.xerces.dom3.DOMError)
336 */
337 public boolean handleError(DOMError error) {
338 ++fErrors;
339 if (fQuiet)
340 return true;
341 Object e = error.getException();
342 String message = fMessagePrefix;
343 if (!(e instanceof Exception)) {
344 e = null;
345 message += error.getMessage();
346 }
347 // NOTE: DOMError do not have valid locator!
348 XMLLocator locator = fBuilder.getLocator();
349 reportError(message, (Exception) e, locator, fEditor, fViewer);
350 if (fHandler != null)
351 fHandler.handleError(error);
352 return true;
353 }
354
355 }
356
357 ////////////////////////////////////////////////////////////////////////
358
359 }