001: /* ***** BEGIN LICENSE BLOCK *****
002: * Version: MPL 1.1/GPL 2.0
003: *
004: * The contents of this file are subject to the Mozilla Public License Version
005: * 1.1 (the "License"); you may not use this file except in compliance with
006: * the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS" basis,
010: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011: * for the specific language governing rights and limitations under the
012: * License.
013: *
014: * The Original Code is Rhino DOM-only E4X implementation.
015: *
016: * The Initial Developer of the Original Code is
017: * David P. Caldwell.
018: * Portions created by David P. Caldwell are Copyright (C)
019: * 2007 David P. Caldwell. All Rights Reserved.
020: *
021: *
022: * Contributor(s):
023: * David P. Caldwell <inonit@inonit.com>
024: *
025: * Alternatively, the contents of this file may be used under the terms of
026: * the GNU General Public License Version 2 or later (the "GPL"), in which
027: * case the provisions of the GPL are applicable instead of those above. If
028: * you wish to allow use of your version of this file only under the terms of
029: * the GPL and not to allow others to use your version of this file under the
030: * MPL, indicate your decision by deleting the provisions above and replacing
031: * them with the notice and other provisions required by the GPL. If you do
032: * not delete the provisions above, a recipient may use your version of this
033: * file under either the MPL or the GPL.
034: *
035: * ***** END LICENSE BLOCK ***** */
036:
037: package org.mozilla.javascript.xmlimpl;
038:
039: import org.w3c.dom.*;
040:
041: import javax.xml.parsers.DocumentBuilder;
042:
043: import org.mozilla.javascript.*;
044:
045: // Disambiguate from org.mozilla.javascript.Node
046: import org.w3c.dom.Node;
047: import org.xml.sax.ErrorHandler;
048: import org.xml.sax.SAXParseException;
049:
050: class XmlProcessor {
051: private boolean ignoreComments;
052: private boolean ignoreProcessingInstructions;
053: private boolean ignoreWhitespace;
054: private boolean prettyPrint;
055: private int prettyIndent;
056:
057: private javax.xml.parsers.DocumentBuilderFactory dom;
058: private javax.xml.transform.TransformerFactory xform;
059: private DocumentBuilder documentBuilder;
060: private RhinoSAXErrorHandler errorHandler = new RhinoSAXErrorHandler();
061:
062: private static class RhinoSAXErrorHandler implements ErrorHandler {
063:
064: private void throwError(SAXParseException e) {
065: throw ScriptRuntime.constructError("TypeError", e
066: .getMessage(), e.getLineNumber() - 1);
067: }
068:
069: public void error(SAXParseException e) {
070: throwError(e);
071: }
072:
073: public void fatalError(SAXParseException e) {
074: throwError(e);
075: }
076:
077: public void warning(SAXParseException e) {
078: Context.reportWarning(e.getMessage());
079: }
080: }
081:
082: XmlProcessor() {
083: setDefault();
084: this .dom = javax.xml.parsers.DocumentBuilderFactory
085: .newInstance();
086: this .dom.setNamespaceAware(true);
087: this .dom.setIgnoringComments(false);
088: this .xform = javax.xml.transform.TransformerFactory
089: .newInstance();
090: }
091:
092: final void setDefault() {
093: this .setIgnoreComments(true);
094: this .setIgnoreProcessingInstructions(true);
095: this .setIgnoreWhitespace(true);
096: this .setPrettyPrinting(true);
097: this .setPrettyIndent(2);
098: }
099:
100: final void setIgnoreComments(boolean b) {
101: this .ignoreComments = b;
102: }
103:
104: final void setIgnoreWhitespace(boolean b) {
105: this .ignoreWhitespace = b;
106: }
107:
108: final void setIgnoreProcessingInstructions(boolean b) {
109: this .ignoreProcessingInstructions = b;
110: }
111:
112: final void setPrettyPrinting(boolean b) {
113: this .prettyPrint = b;
114: }
115:
116: final void setPrettyIndent(int i) {
117: this .prettyIndent = i;
118: }
119:
120: final boolean isIgnoreComments() {
121: return ignoreComments;
122: }
123:
124: final boolean isIgnoreProcessingInstructions() {
125: return ignoreProcessingInstructions;
126: }
127:
128: final boolean isIgnoreWhitespace() {
129: return ignoreWhitespace;
130: }
131:
132: final boolean isPrettyPrinting() {
133: return prettyPrint;
134: }
135:
136: final int getPrettyIndent() {
137: return prettyIndent;
138: }
139:
140: private String toXmlNewlines(String rv) {
141: StringBuffer nl = new StringBuffer();
142: for (int i = 0; i < rv.length(); i++) {
143: if (rv.charAt(i) == '\r') {
144: if (rv.charAt(i + 1) == '\n') {
145: // DOS, do nothing and skip the \r
146: } else {
147: // Macintosh, substitute \n
148: nl.append('\n');
149: }
150: } else {
151: nl.append(rv.charAt(i));
152: }
153: }
154: return nl.toString();
155: }
156:
157: private javax.xml.parsers.DocumentBuilderFactory getDomFactory() {
158: return dom;
159: }
160:
161: private synchronized DocumentBuilder getDocumentBuilderFromPool()
162: throws javax.xml.parsers.ParserConfigurationException {
163: DocumentBuilder result;
164: if (documentBuilder == null) {
165: javax.xml.parsers.DocumentBuilderFactory factory = getDomFactory();
166: result = factory.newDocumentBuilder();
167: } else {
168: result = documentBuilder;
169: documentBuilder = null;
170: }
171: result.setErrorHandler(errorHandler);
172: return result;
173: }
174:
175: private synchronized void returnDocumentBuilderToPool(
176: DocumentBuilder db) {
177: if (documentBuilder == null) {
178: documentBuilder = db;
179: documentBuilder.reset();
180: }
181: }
182:
183: private void addProcessingInstructionsTo(java.util.Vector v,
184: Node node) {
185: if (node instanceof ProcessingInstruction) {
186: v.add(node);
187: }
188: if (node.getChildNodes() != null) {
189: for (int i = 0; i < node.getChildNodes().getLength(); i++) {
190: addProcessingInstructionsTo(v, node.getChildNodes()
191: .item(i));
192: }
193: }
194: }
195:
196: private void addCommentsTo(java.util.Vector v, Node node) {
197: if (node instanceof Comment) {
198: v.add(node);
199: }
200: if (node.getChildNodes() != null) {
201: for (int i = 0; i < node.getChildNodes().getLength(); i++) {
202: addProcessingInstructionsTo(v, node.getChildNodes()
203: .item(i));
204: }
205: }
206: }
207:
208: private void addTextNodesToRemoveAndTrim(java.util.Vector toRemove,
209: Node node) {
210: if (node instanceof Text) {
211: Text text = (Text) node;
212: boolean BUG_369394_IS_VALID = false;
213: if (!BUG_369394_IS_VALID) {
214: text.setData(text.getData().trim());
215: } else {
216: if (text.getData().trim().length() == 0) {
217: text.setData("");
218: }
219: }
220: if (text.getData().length() == 0) {
221: toRemove.add(node);
222: }
223: }
224: if (node.getChildNodes() != null) {
225: for (int i = 0; i < node.getChildNodes().getLength(); i++) {
226: addTextNodesToRemoveAndTrim(toRemove, node
227: .getChildNodes().item(i));
228: }
229: }
230: }
231:
232: final Node toXml(String defaultNamespaceUri, String xml)
233: throws org.xml.sax.SAXException {
234: // See ECMA357 10.3.1
235: DocumentBuilder builder = null;
236: try {
237: String syntheticXml = "<parent xmlns=\""
238: + defaultNamespaceUri + "\">" + xml + "</parent>";
239: builder = getDocumentBuilderFromPool();
240: Document document = builder
241: .parse(new org.xml.sax.InputSource(
242: new java.io.StringReader(syntheticXml)));
243: if (ignoreProcessingInstructions) {
244: java.util.Vector v = new java.util.Vector();
245: addProcessingInstructionsTo(v, document);
246: for (int i = 0; i < v.size(); i++) {
247: Node node = (Node) v.elementAt(i);
248: node.getParentNode().removeChild(node);
249: }
250: }
251: if (ignoreComments) {
252: java.util.Vector v = new java.util.Vector();
253: addCommentsTo(v, document);
254: for (int i = 0; i < v.size(); i++) {
255: Node node = (Node) v.elementAt(i);
256: node.getParentNode().removeChild(node);
257: }
258: }
259: if (ignoreWhitespace) {
260: // Apparently JAXP setIgnoringElementContentWhitespace() has a different meaning, it appears from the Javadoc
261: // Refers to element-only content models, which means we would need to have a validating parser and DTD or schema
262: // so that it would know which whitespace to ignore.
263:
264: // Instead we will try to delete it ourselves.
265: java.util.Vector v = new java.util.Vector();
266: addTextNodesToRemoveAndTrim(v, document);
267: for (int i = 0; i < v.size(); i++) {
268: Node node = (Node) v.elementAt(i);
269: node.getParentNode().removeChild(node);
270: }
271: }
272: NodeList rv = document.getDocumentElement().getChildNodes();
273: if (rv.getLength() > 1) {
274: throw ScriptRuntime.constructError("SyntaxError",
275: "XML objects may contain at most one node.");
276: } else if (rv.getLength() == 0) {
277: Node node = document.createTextNode("");
278: return node;
279: } else {
280: Node node = rv.item(0);
281: document.getDocumentElement().removeChild(node);
282: return node;
283: }
284: } catch (java.io.IOException e) {
285: throw new RuntimeException("Unreachable.");
286: } catch (javax.xml.parsers.ParserConfigurationException e) {
287: throw new RuntimeException(e);
288: } finally {
289: if (builder != null)
290: returnDocumentBuilderToPool(builder);
291: }
292: }
293:
294: Document newDocument() {
295: DocumentBuilder builder = null;
296: try {
297: // TODO Should this use XML settings?
298: builder = getDocumentBuilderFromPool();
299: return builder.newDocument();
300: } catch (javax.xml.parsers.ParserConfigurationException ex) {
301: // TODO How to handle these runtime errors?
302: throw new RuntimeException(ex);
303: } finally {
304: if (builder != null)
305: returnDocumentBuilderToPool(builder);
306: }
307: }
308:
309: // TODO Cannot remember what this is for, so whether it should use settings or not
310: private String toString(Node node) {
311: javax.xml.transform.dom.DOMSource source = new javax.xml.transform.dom.DOMSource(
312: node);
313: java.io.StringWriter writer = new java.io.StringWriter();
314: javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(
315: writer);
316: try {
317: javax.xml.transform.Transformer transformer = xform
318: .newTransformer();
319: transformer
320: .setOutputProperty(
321: javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION,
322: "yes");
323: transformer.setOutputProperty(
324: javax.xml.transform.OutputKeys.INDENT, "no");
325: transformer.setOutputProperty(
326: javax.xml.transform.OutputKeys.METHOD, "xml");
327: transformer.transform(source, result);
328: } catch (javax.xml.transform.TransformerConfigurationException ex) {
329: // TODO How to handle these runtime errors?
330: throw new RuntimeException(ex);
331: } catch (javax.xml.transform.TransformerException ex) {
332: // TODO How to handle these runtime errors?
333: throw new RuntimeException(ex);
334: }
335: return toXmlNewlines(writer.toString());
336: }
337:
338: String escapeAttributeValue(Object value) {
339: String text = ScriptRuntime.toString(value);
340:
341: if (text.length() == 0)
342: return "";
343:
344: Document dom = newDocument();
345: Element e = dom.createElement("a");
346: e.setAttribute("b", text);
347: String elementText = toString(e);
348: int begin = elementText.indexOf('"');
349: int end = elementText.lastIndexOf('"');
350: return elementText.substring(begin + 1, end);
351: }
352:
353: String escapeTextValue(Object value) {
354: if (value instanceof XMLObjectImpl) {
355: return ((XMLObjectImpl) value).toXMLString();
356: }
357:
358: String text = ScriptRuntime.toString(value);
359:
360: if (text.length() == 0)
361: return text;
362:
363: Document dom = newDocument();
364: Element e = dom.createElement("a");
365: e.setTextContent(text);
366: String elementText = toString(e);
367:
368: int begin = elementText.indexOf('>') + 1;
369: int end = elementText.lastIndexOf('<');
370: return (begin < end) ? elementText.substring(begin, end) : "";
371: }
372:
373: private String escapeElementValue(String s) {
374: // TODO Check this
375: return escapeTextValue(s);
376: }
377:
378: private String elementToXmlString(Element element) {
379: // TODO My goodness ECMA is complicated (see 10.2.1). We'll try this first.
380: Element copy = (Element) element.cloneNode(true);
381: if (prettyPrint) {
382: beautifyElement(copy, 0);
383: }
384: return toString(copy);
385: }
386:
387: final String ecmaToXmlString(Node node) {
388: // See ECMA 357 Section 10.2.1
389: StringBuffer s = new StringBuffer();
390: int indentLevel = 0;
391: if (prettyPrint) {
392: for (int i = 0; i < indentLevel; i++) {
393: s.append(' ');
394: }
395: }
396: if (node instanceof Text) {
397: String data = ((Text) node).getData();
398: // TODO Does Java trim() work same as XMLWhitespace?
399: String v = (prettyPrint) ? data.trim() : data;
400: s.append(escapeElementValue(v));
401: return s.toString();
402: }
403: if (node instanceof Attr) {
404: String value = ((Attr) node).getValue();
405: s.append(escapeAttributeValue(value));
406: return s.toString();
407: }
408: if (node instanceof Comment) {
409: s.append("<!--" + ((Comment) node).getNodeValue() + "-->");
410: return s.toString();
411: }
412: if (node instanceof ProcessingInstruction) {
413: ProcessingInstruction pi = (ProcessingInstruction) node;
414: s.append("<?" + pi.getTarget() + " " + pi.getData() + "?>");
415: return s.toString();
416: }
417: s.append(elementToXmlString((Element) node));
418: return s.toString();
419: }
420:
421: private void beautifyElement(Element e, int indent) {
422: StringBuffer s = new StringBuffer();
423: s.append('\n');
424: for (int i = 0; i < indent; i++) {
425: s.append(' ');
426: }
427: String afterContent = s.toString();
428: for (int i = 0; i < prettyIndent; i++) {
429: s.append(' ');
430: }
431: String beforeContent = s.toString();
432:
433: // We "mark" all the nodes first; if we tried to do this loop otherwise, it would behave unexpectedly (the inserted nodes
434: // would contribute to the length and it might never terminate).
435: java.util.Vector toIndent = new java.util.Vector();
436: boolean indentChildren = false;
437: for (int i = 0; i < e.getChildNodes().getLength(); i++) {
438: if (i == 1)
439: indentChildren = true;
440: if (e.getChildNodes().item(i) instanceof Text) {
441: toIndent.add(e.getChildNodes().item(i));
442: } else {
443: indentChildren = true;
444: toIndent.add(e.getChildNodes().item(i));
445: }
446: }
447: if (indentChildren) {
448: for (int i = 0; i < toIndent.size(); i++) {
449: e.insertBefore(e.getOwnerDocument().createTextNode(
450: beforeContent), (Node) toIndent.elementAt(i));
451: }
452: }
453: NodeList nodes = e.getChildNodes();
454: java.util.Vector v = new java.util.Vector();
455: for (int i = 0; i < nodes.getLength(); i++) {
456: if (nodes.item(i) instanceof Element) {
457: v.add(nodes.item(i));
458: }
459: }
460: for (int i = 0; i < v.size(); i++) {
461: beautifyElement((Element) v.elementAt(i), indent
462: + prettyIndent);
463: }
464: if (indentChildren) {
465: e.appendChild(e.getOwnerDocument().createTextNode(
466: afterContent));
467: }
468: }
469: }
|