0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.insync.markup;
0042:
0043: import org.netbeans.api.queries.FileEncodingQuery;
0044: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
0045: import com.sun.rave.designtime.markup.MarkupDesignBean;
0046: import org.netbeans.modules.visualweb.insync.InSyncServiceProvider;
0047: import org.netbeans.modules.visualweb.insync.Util;
0048: import java.io.ByteArrayInputStream;
0049: import java.io.CharArrayReader;
0050: import java.io.EOFException;
0051: import java.io.IOException;
0052: import java.io.PrintWriter;
0053: import java.io.StringReader;
0054: import java.io.StringWriter;
0055: import java.io.Writer;
0056: import java.lang.ref.WeakReference;
0057: import java.net.MalformedURLException;
0058: import java.net.URI;
0059: import java.net.URISyntaxException;
0060: import java.net.URL;
0061: import java.nio.charset.Charset;
0062: import java.util.Collection;
0063: import java.util.HashMap;
0064: import java.util.Locale;
0065: import java.util.Map;
0066: import java.util.WeakHashMap;
0067: import javax.xml.parsers.DocumentBuilder;
0068: import javax.xml.parsers.ParserConfigurationException;
0069: import org.apache.xerces.dom.events.MutationEventImpl;
0070: import org.apache.xerces.util.EncodingMap;
0071: import org.apache.xml.serialize.OutputFormat;
0072: import org.openide.ErrorManager;
0073: import org.openide.filesystems.FileObject;
0074: import org.openide.filesystems.FileUtil;
0075: import org.openide.loaders.DataObject;
0076: import org.openide.util.Lookup;
0077: import org.openide.util.NbBundle;
0078: import org.netbeans.modules.visualweb.extension.openide.util.Trace;
0079: import org.openide.windows.IOProvider;
0080: import org.openide.windows.InputOutput;
0081: import org.openide.windows.OutputEvent;
0082: import org.openide.windows.OutputWriter;
0083: import org.w3c.dom.Attr;
0084: import org.w3c.dom.Document;
0085: import org.w3c.dom.DocumentFragment;
0086: import org.w3c.dom.Element;
0087: import org.w3c.dom.NamedNodeMap;
0088: import org.w3c.dom.Node;
0089: import org.w3c.dom.NodeList;
0090: import org.w3c.dom.Text;
0091: import org.w3c.dom.events.DocumentEvent;
0092: import org.w3c.dom.events.EventTarget;
0093: import org.w3c.dom.events.MutationEvent;
0094: import org.xml.sax.EntityResolver;
0095: import org.xml.sax.ErrorHandler;
0096: import org.xml.sax.SAXException;
0097: import org.xml.sax.SAXParseException;
0098: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
0099: import org.netbeans.modules.visualweb.insync.ParserAnnotation;
0100: import org.netbeans.modules.visualweb.insync.SourceUnit;
0101: import org.netbeans.modules.visualweb.insync.UndoManager;
0102: import org.netbeans.modules.visualweb.insync.markup.JspxSerializer;
0103: import org.openide.windows.OutputListener;
0104: import org.w3c.dom.UserDataHandler;
0105:
0106: /**
0107: *
0108: */
0109: public class MarkupUnit extends SourceUnit implements
0110: org.w3c.dom.events.EventListener {
0111:
0112: public static final int ALLOW_XML = 0x01;
0113: public static final int ALLOW_HTML = 0x02;
0114:
0115: int flags;
0116: /** Original parsed DOM document. */
0117: private Document sourceDocument;
0118: /** For JSP/JSF page -> rendered DOM document. */
0119: private Document renderedDocument;
0120: HashMap namespaces = new HashMap(); // namespace URI => prefix mapping
0121: Map<String, String> namespaceUriMap = new HashMap<String, String>(); // prefix ==> namespace URI mapping
0122: URI baseURI;
0123: URL base;
0124: private boolean supportCss;
0125: protected EventTarget registeredAsEventListenerOn;
0126:
0127: //--------------------------------------------------------------------------------- Construction
0128:
0129: /**
0130: * Construct an MarkupUnit with a source doc
0131: */
0132: public MarkupUnit(FileObject fobj, int flags, boolean supportCss,
0133: UndoManager undoManager) {
0134: super (fobj, undoManager);
0135: this .flags = flags;
0136: this .supportCss = supportCss;
0137: //Trace.enableTraceCategory("insync.markup");
0138: }
0139:
0140: public void destroy() {
0141: namespaces.clear();
0142: namespaceUriMap.clear();
0143: base = null;
0144: baseURI = null;
0145: // XhtmlCssEngine engine = CssEngineServiceProvider.getDefault().getCssEngine(sourceDocument);
0146: // if (engine != null) {
0147: // engine.dispose();
0148: // engine = null;
0149: // }
0150: // CssProvider.getEngineService().removeCssEngineForDocument(sourceDocument);
0151: CssProvider.getEngineService().removeCssEngineForDocument(
0152: renderedDocument);
0153: synchronized (LOCK_RENDERED_DOCUMENT) {
0154: renderedDocument = null;
0155: }
0156:
0157: sourceDocument = null;
0158: unregisterDomListeners();
0159: super .destroy();
0160: }
0161:
0162: //------------------------------------------------------------------------ Document Node Helpers
0163:
0164: /**
0165: * Ensures that there is an element of a given type at the root of the document.
0166: * @return the found or created element
0167: */
0168: public static Element ensureRoot(Document document, String tag) {
0169: Element root = document.getDocumentElement();
0170: if (root == null) {
0171: root = document.createElement(tag);
0172: document.appendChild(root);
0173: Trace.trace("insync.markup", "MU ensure created root "
0174: + root);
0175: } else
0176: Trace
0177: .trace("insync.markup", "MU ensure found root "
0178: + root);
0179: return root;
0180: }
0181:
0182: /**
0183: *
0184: */
0185: public static Element ensureElement(Element parent, String tag,
0186: Element after) {
0187: Element elem = getFirstDescendantElement(parent, tag);
0188: if (elem == null) {
0189: Document document = parent.getOwnerDocument();
0190: elem = document.createElement(tag);
0191:
0192: Element before = after != null ? getNextSiblingElement(after)
0193: : getFirstChildElement(parent);
0194: if (before != null)
0195: parent.insertBefore(elem, before);
0196: else
0197: parent.appendChild(elem);
0198: Trace.trace("insync.markup", "MU scan created " + elem
0199: + " under " + parent + " before " + before);
0200: } else
0201: Trace.trace("insync.markup", "MU scan found " + elem
0202: + " under " + elem.getParentNode());
0203: return elem;
0204: }
0205:
0206: /**
0207: *
0208: */
0209: public Element addElement(Element parent, Node before,
0210: String taglibUri, String tagPrefix, String tag) {
0211:
0212: String prefix = taglibUri != null && taglibUri.length() > 0 ? getNamespacePrefix(
0213: taglibUri, tagPrefix)
0214: + ":"
0215: : "";
0216: Element element = sourceDocument.createElementNS(taglibUri,
0217: prefix + tag);
0218:
0219: if (parent != null) {
0220: if (before != null)
0221: parent.insertBefore(element, before);
0222: else
0223: parent.appendChild(element);
0224: } else {
0225: sourceDocument.appendChild(element);
0226: }
0227:
0228: return element;
0229: }
0230:
0231: /**
0232: * Ensure that a given attribute of a given element exists. Create it with the default value if
0233: * it does not, leave it alone if it is. Handles special case of xmlns definition attributes.
0234: */
0235: public void ensureAttributeExists(Element element, String attr,
0236: String defValue) {
0237: if (element.getAttributeNode(attr) == null)
0238: ensureAttributeValue(element, attr, defValue);
0239: }
0240:
0241: /**
0242: * Ensure that a given attribute of a given element is a specific value. Update it if it is not,
0243: * leave it alone if it is. Handles special case of xmlns definition attributes.
0244: */
0245: public void ensureAttributeValue(Element element, String attr,
0246: String value) {
0247: if (!element.getAttribute(attr).equals(value)) {
0248: element.setAttribute(attr, value);
0249: if (element == sourceDocument.getDocumentElement()
0250: && attr.startsWith("xmlns:")) {
0251: String prefix = attr.substring(6);
0252: namespaces.put(value, prefix);
0253: namespaceUriMap.put(prefix, value);
0254: }
0255: }
0256: }
0257:
0258: /**
0259: * Get the first descendant element with a given tag
0260: */
0261: public static Element getFirstDescendantElement(Element parent,
0262: String tag) {
0263: NodeList childs = parent.getElementsByTagName(tag);
0264: if (childs.getLength() >= 1)
0265: return (Element) childs.item(0);
0266: return null;
0267: }
0268:
0269: /**
0270: * Return the child element with a given value for a given attribute
0271: */
0272: public static Element getDescendantElementByAttr(Element parent,
0273: String tag, String attrName, String attrValue) {
0274: NodeList elems = parent.getElementsByTagName(tag);
0275: int elemcount = elems.getLength();
0276: for (int i = 0; i < elemcount; i++) {
0277: Element e = (Element) elems.item(i);
0278: if (e.getAttribute(attrName).equals(attrValue))
0279: return e;
0280: }
0281: return null;
0282: }
0283:
0284: /**
0285: * Return the child element with a given value for a given attribute
0286: */
0287: public static boolean isDescendent(Node parent, Node descendent) {
0288: while (descendent != null) {
0289: if (descendent == parent)
0290: return true;
0291: else
0292: descendent = descendent.getParentNode();
0293: }
0294: return false;
0295: }
0296:
0297: /**
0298: * Get the first child node that is an element
0299: */
0300: public static Element getFirstChildElement(Node parent) {
0301: for (Node child = parent.getFirstChild(); child != null; child = child
0302: .getNextSibling()) {
0303: if (child instanceof Element)
0304: return (Element) child;
0305: }
0306: return null;
0307: }
0308:
0309: /**
0310: * Get the next sibling node that is an element
0311: */
0312: public static Element getNextSiblingElement(Node elem) {
0313: for (Node sib = elem.getNextSibling(); sib != null; sib = sib
0314: .getNextSibling()) {
0315: if (sib instanceof Element)
0316: return (Element) sib;
0317:
0318: }
0319: return null;
0320: }
0321:
0322: /**
0323: * Get the text body of an element. Any markup is ignored.
0324: */
0325: public static String getElementText(Element elem) {
0326: if (elem == null)
0327: return null;
0328: StringBuffer sb = new StringBuffer();
0329: for (Node child = elem.getFirstChild(); child != null; child = child
0330: .getNextSibling()) {
0331: if (child instanceof Text)
0332: sb.append(((Text) child).getData());
0333: }
0334: return sb.toString();
0335: }
0336:
0337: /**
0338: * Set the text body of an element. Any markup is lost.
0339: */
0340: public static void setElementText(Element elem, String text) {
0341: for (Node child = elem.getFirstChild(); child != null; child = child
0342: .getNextSibling()) {
0343: if (child instanceof Text)
0344: elem.removeChild(child);
0345: }
0346: if (text != null)
0347: elem.appendChild(elem.getOwnerDocument().createTextNode(
0348: text));
0349: }
0350:
0351: public String findTaglibUri(String prefix) {
0352: return namespaceUriMap.get(prefix);
0353: }
0354:
0355: //---------------------------------------------------------------------------------------- Input
0356:
0357: /**
0358: * Parse the input source into a DOM Document using the configured JAXP DOM implementation
0359: */
0360: private Document parseDom(org.xml.sax.InputSource is) {
0361: try {
0362: DocumentBuilder parser;
0363: parser = MarkupService
0364: .createRaveSourceDocumentBuilder(supportCss);
0365:
0366: parser.setErrorHandler(new ErrorHandler() {
0367:
0368: public void error(SAXParseException exception) {
0369: addError(exception, true);
0370: }
0371:
0372: public void fatalError(SAXParseException exception) {
0373: addError(exception, true);
0374: }
0375:
0376: public void warning(SAXParseException exception) {
0377: addError(exception, false);
0378: }
0379: });
0380:
0381: // TODO: only set this to empty if a first parse fails?
0382: parser.setEntityResolver(new XhtmlEntityResolver());
0383:
0384: Trace.trace("insync.markup", "DOM Parsing: " + getName());
0385: Trace.flush();
0386:
0387: // Catch empty file induced parse exceptions ahead of time & just gen a new DOM.
0388: // if (!is.getCharacterStream().ready())
0389: // return parser.newDocument();
0390:
0391: // Invoke the DOM parser
0392: Document doc = parser.parse(is);
0393: // if (doc instanceof RaveDocument) {
0394: Document xdoc = doc;
0395: // <markup_separation>
0396: // xdoc.setMarkupUnit(this);
0397: // ====
0398: setMarkupUnitForDocument(xdoc, this );
0399: // </markup_separation>
0400:
0401: // Figure out the best source encoding & use it. Either the .nbattr or the ?xml
0402: String encoding = getEncoding();
0403: // String xencoding = xdoc.getEncoding();
0404: String xencoding = xdoc.getXmlEncoding();
0405: if (encoding != null && encoding.length() > 0) {
0406: if (xencoding == null || xencoding.length() == 0) {
0407: // ((RaveDocument)xdoc).setInputEncoding(encoding);
0408: MarkupService.setInputEncodingForDocument(xdoc,
0409: encoding);
0410: }
0411: } else {
0412: if (xencoding != null && xencoding.length() > 0) {
0413: encoding = xencoding;
0414: }
0415: }
0416: // }
0417:
0418: return doc;
0419: } catch (ParserConfigurationException e) {
0420: Trace.trace("insync.markup", "DOM Parsing: " + getName());
0421: Trace.trace("insync.markup", e);
0422: setBusted();
0423: } catch (SAXException e) {
0424: Trace.trace("insync.markup", "DOM Parsing: " + getName());
0425: Trace.trace("insync.markup", e);
0426: setBusted();
0427: } catch (IOException e) {
0428: Trace.trace("insync.markup", "DOM Parsing: " + getName());
0429: Trace.trace("insync.markup", e);
0430: setBusted();
0431: }
0432:
0433: return null;
0434: }
0435:
0436: // TODO: only set this to empty if a first parse fails?
0437: public static class XhtmlEntityResolver implements EntityResolver {
0438: public org.xml.sax.InputSource resolveEntity(String pubid,
0439: String sysid) {
0440: return new org.xml.sax.InputSource(
0441: new ByteArrayInputStream(new byte[0]));
0442: }
0443: }
0444:
0445: /**
0446: * The parser always bails after the first error, so we don't have to keep a "list" of errors
0447: * (and chain them when they're on the same line) - we can just point directly to our one error.
0448: * Null when there is no error.
0449: *
0450: * @todo Is this true? Will it bail if it sees a warning (as opposed to an error) ? What
0451: * constitutes a warning from Xerces)
0452: */
0453: private ParserAnnotation error;
0454:
0455: /**
0456: * A new parse is about to begin - clear out existing errors
0457: */
0458: private void resetErrors() {
0459: error = null;
0460: }
0461:
0462: /**
0463: * Add a parser error to the hashmap for the given sax error
0464: */
0465: private void addError(SAXParseException exception, boolean isError) {
0466: String message = exception.getMessage();
0467: int line = exception.getLineNumber();
0468: // If file is empty then saved, we get -1
0469: if (line < 0)
0470: line = 1;
0471: int column = exception.getColumnNumber();
0472: if (column < 0)
0473: column = 0;
0474: error = new ParserAnnotation(message, fobj, line, column);
0475: if (isError)
0476: setBusted();
0477: }
0478:
0479: /**
0480: * Return the list of errors if this unit does not compile. If there are no errors it returns an
0481: * empty array - never null.
0482: *
0483: * @return An array of ParserAnnotations.
0484: */
0485: public ParserAnnotation[] getErrors() {
0486: if (error != null)
0487: return new ParserAnnotation[] { error };
0488: else
0489: return ParserAnnotation.EMPTY_ARRAY;
0490: }
0491:
0492: /**
0493: * Read the actual characters from the source document's content and into our DOM source document
0494: * object. Try an XML and/or an HTML parser depending on our flags setting.
0495: */
0496: protected void read(char[] buf, int len) {
0497: Trace.trace("insync.markup", "MU.read"); // \"" + new String(buf, 0, len) + "\"");
0498: //long start = System.currentTimeMillis();
0499:
0500: // cleanup listeners on old document
0501: Document oldDocument = sourceDocument;
0502: Document newDocument = null;
0503:
0504: InSyncServiceProvider.get().getRaveErrorHandler().clearErrors(
0505: true);
0506: resetErrors();
0507:
0508: // force reinitialization!!!
0509: base = null;
0510: baseURI = null;
0511:
0512: // Input structure for JAXP SAX and DOM
0513: org.xml.sax.InputSource is = new org.xml.sax.InputSource(
0514: new CharArrayReader(buf, 0, len));
0515: is.setSystemId(getName());
0516:
0517: // Try an XML parse first if it is allowed
0518: if ((flags & ALLOW_XML) != 0) {
0519: newDocument = parseDom(is);
0520: if (newDocument == null) { // reset input source if parse aborted
0521: is = new org.xml.sax.InputSource(new CharArrayReader(
0522: buf, 0, len));
0523: is.setSystemId(getName());
0524: }
0525: }
0526:
0527: if (getState().isBusted())
0528: return;
0529:
0530: // Force pre-init of style sheets, such that errors in the <style> tag section etc. will
0531: // get triggered. This doesn't initialize per-tag/local styles, only the <head> section
0532: // style sheets and the head <style> tag.
0533: sourceDocument = newDocument; // for initializeStyleSheet
0534:
0535: if (sourceDocument.getDocumentElement() != null) {
0536: MarkupService.markJspxSource(sourceDocument
0537: .getDocumentElement());
0538: }
0539:
0540: // Attempt to initialize stylesheets via Batik' CSS parser
0541: if (supportCss)
0542: syncEngine();// Initialize default stylesheet
0543:
0544: // style sheet parsing can cause errors too
0545: if (getState().isBusted()) {
0546: sourceDocument = oldDocument;
0547: return;
0548: }
0549:
0550: // register change listener & notify listeners regarding replaced doc
0551: unregisterDomListeners();
0552:
0553: sourceDocument = newDocument;
0554:
0555: // // XXX Reinit also the rendered doc?
0556: // synchronized (LOCK_RENDERED_DOCUMENT) {
0557: // renderedDocument = null;
0558: // }
0559: // XXX Is this the correct place (and thing to do)?
0560: setMarkupUnitForDocument(getRenderedDom(), this );
0561:
0562: if (sourceDocument != null) {
0563: registerDomListeners(sourceDocument);
0564: Element root = sourceDocument.getDocumentElement();
0565: NamedNodeMap attrs = root.getAttributes();
0566: for (int i = 0; i < attrs.getLength(); i++) {
0567: Attr attr = (Attr) attrs.item(i);
0568: if (attr.getName().startsWith("xmlns:")) {
0569: String prefix = attr.getName().substring(6);
0570: namespaces.put(attr.getValue(), prefix);
0571: namespaceUriMap.put(prefix, attr.getValue());
0572: }
0573: }
0574: }
0575:
0576: //long duration = System.currentTimeMillis() - start;
0577: //System.err.println("MU.read: XML parse time: " + duration + "ms for " + getName());
0578:
0579: if (oldDocument != null)
0580: fireDocumentReplaced(oldDocument);
0581: }
0582:
0583: // private XhtmlCssEngine engine;
0584:
0585: // XXX Not used, removing.
0586: // private static boolean reuseEngine = System.getProperty("designer.reuseCssEngine") != null;
0587: private void syncEngine() {
0588: // Document doc = sourceDocument;
0589: // XXX It seems we need to parse styles for both documents (source and rendered).
0590: Document sourceDom = sourceDocument;
0591: // Document renderedDom = getRenderedDom();
0592:
0593: // doc.setUrl(getBase()); // needed by engine
0594: // setUrlForDocument(doc, getBase());
0595: setUrlForDocument(sourceDom, getBase());
0596: // setUrlForDocument(renderedDom, getBase());
0597:
0598: // if (reuseEngine && engine != null) {
0599: // engine.setDocument(doc);
0600: //// <moving RaveDoc refs outside> engine is not interested in registering itself somewhere.
0601: // doc.setCssEngine(engine);
0602: //// </moving RaveDoc refs outside>
0603: // return;
0604: // }
0605: // <markup_separation>
0606: // engine = XhtmlCssEngine.create(doc, this, doc.getUrl());
0607: // ====
0608: // engine = XhtmlCssEngine.create(doc, doc.getUrl());
0609: // XhtmlCssEngine engine = XhtmlCssEngine.create(doc, getUrlForDocument(doc));
0610: //// <moved from engine impl> it doesn't (shoudn't) know about RaveDocument.
0611: // if (doc != null) {
0612: //// doc.setCssEngine(engine);
0613: // CssEngineServiceProvider.getDefault().setCssEngine(doc, engine);
0614: // }
0615: //// </moved from engine impl>
0616: // CssProvider.getEngineService().createCssEngineForDocument(doc, getUrlForDocument(doc));
0617: CssProvider.getEngineService().createCssEngineForDocument(
0618: sourceDom, getUrlForDocument(sourceDom));
0619: // CssProvider.getEngineService().createCssEngineForDocument(renderedDom, getUrlForDocument(renderedDom));
0620: // </markup_separation>
0621: }
0622:
0623: // /** Return CSS engine associated with this unit */
0624: // public XhtmlCssEngine getCssEngine() {
0625: //// return engine;
0626: // return CssEngineServiceProvider.getDefault().getCssEngine(sourceDocument);
0627: // }
0628:
0629: //--------------------------------------------------------------------------------------- Output
0630:
0631: /**
0632: * Return a Xerces-compatible IANA encoding given a Java or IANA encoding
0633: * @param encoding Java or IANA encoding
0634: * @return Xerces-compatible IANA encoding
0635: */
0636: public static String getIanaEncoding(String encoding) {
0637: if (encoding != null) {
0638: // need to convert it to upper case:
0639: String upEncoding = encoding.toUpperCase(Locale.ENGLISH);
0640:
0641: // if it's a known IANA encoding, that's good enough for us
0642: if (EncodingMap.getIANA2JavaMapping(upEncoding) != null)
0643: return encoding;
0644:
0645: // if it is a java encoding try mapping it to IANA
0646: String ianaEncoding = EncodingMap
0647: .getJava2IANAMapping(upEncoding);
0648: if (ianaEncoding != null)
0649: return ianaEncoding;
0650:
0651: // if it's a known java encoding alias, that needs to be normalized...
0652: /*
0653: if (Charset.isSupported(encoding)) {
0654: Charset charset = Charset.forName(encoding);
0655: if (charset != null) {
0656: ianaEncoding = charset.name().toUpperCase(Locale.ENGLISH);
0657: EncodingMap.putIANA2JavaMapping(ianaEncoding, upEncoding);
0658: EncodingMap.putJava2IANAMapping(upEncoding, ianaEncoding);
0659:
0660: OutputFormat format = new OutputFormat(Method.XML, charset.name(), true); // do-indent==true
0661: format.setAllowJavaNames(true);
0662: EncodingInfo ei = null;
0663: try {
0664: ei = format.getEncodingInfo();
0665: }
0666: catch (UnsupportedEncodingException e) {
0667: System.err.println(e);
0668: return "UTF-8";
0669: }
0670:
0671: return charset.name();
0672: }
0673: }*/
0674: }
0675: return "UTF-8"; // Not an IANA or Xerces encoding, or was null for UTF8 default
0676: }
0677:
0678: /**
0679: * @return the current encoding in IANA form, or null for default (UTF8)
0680: */
0681: public String getEncoding() {
0682: Charset encodingCharset = FileEncodingQuery.getEncoding(fobj);
0683: return (encodingCharset == null ? null : encodingCharset.name());
0684: }
0685:
0686: static final String BLANK = " ";
0687:
0688: public void indent(PrintWriter w, int level) {
0689: w.print(BLANK.substring(0, level * 4));
0690: }
0691:
0692: public void dump(org.w3c.dom.Node node, PrintWriter w, int level) {
0693: indent(w, level);
0694: if (node instanceof Element)
0695: w.println("E lname:" + node.getLocalName());
0696: else
0697: w.println(node.getClass().getName() + " lname:"
0698: + node.getLocalName());
0699: org.w3c.dom.Node child = node.getFirstChild();
0700: while (child != null) {
0701: dump(child, w, level + 1);
0702: child = child.getNextSibling();
0703: }
0704: }
0705:
0706: public void dumpTo(PrintWriter w) {
0707: if (sourceDocument != null) {
0708: dump(sourceDocument, w, 0);
0709: }
0710: }
0711:
0712: /** Get the output format to be used when serializing this buffer */
0713: public OutputFormat getOutputFormat() {
0714: String xencoding = getIanaEncoding(getEncoding());
0715: OutputFormat format = new OutputFormat(sourceDocument,
0716: xencoding, true); // do-indent==true
0717: format.setLineWidth(160);
0718: format.setIndent(4);
0719: format.setAllowJavaNames(true);
0720: return format;
0721: }
0722:
0723: public void writeTo(Writer w) throws java.io.IOException {
0724: OutputFormat format = getOutputFormat();
0725: JspxSerializer serializer = new JspxSerializer(w, format);
0726: serializer.serialize(sourceDocument);
0727: }
0728:
0729: //------------------------------------------------------------------------------------ Accessors
0730:
0731: public Document getSourceDom() {
0732: return sourceDocument;
0733: }
0734:
0735: /** Lock for sync creating of rendered document. */
0736: private final Object LOCK_RENDERED_DOCUMENT = new Object();
0737:
0738: public Document getRenderedDom() {
0739: boolean created = false;
0740: synchronized (LOCK_RENDERED_DOCUMENT) {
0741: if (renderedDocument == null) {
0742: renderedDocument = createEmptyRenderedDocument();
0743: created = true;
0744: }
0745: }
0746: if (created) {
0747: initRenderedDocument();
0748: }
0749:
0750: return renderedDocument;
0751: }
0752:
0753: private void initRenderedDocument() {
0754: Document renderedDom;
0755: synchronized (LOCK_RENDERED_DOCUMENT) {
0756: renderedDom = renderedDocument;
0757: }
0758: setUrlForDocument(renderedDom, getBase());
0759: CssProvider.getEngineService().createCssEngineForDocument(
0760: renderedDom, getUrlForDocument(renderedDom));
0761: }
0762:
0763: private static Document createEmptyRenderedDocument() {
0764: try {
0765: org.xml.sax.InputSource is = new org.xml.sax.InputSource(
0766: new StringReader("<html></html>")); // TEMP
0767: DocumentBuilder parser = MarkupService
0768: .createRaveRenderedDocumentBuilder(true);
0769: Document doc = parser.parse(is);
0770: return doc;
0771: } catch (java.io.IOException ex) {
0772: ex.printStackTrace();
0773: } catch (org.xml.sax.SAXException ex) {
0774: ex.printStackTrace();
0775: } catch (javax.xml.parsers.ParserConfigurationException ex) {
0776: ex.printStackTrace();
0777: }
0778: return null;
0779: }
0780:
0781: /**
0782: * Gets the actual prefix in this document for a given namespace. Registers the default if it
0783: * does not yet exist.
0784: *
0785: * @param namespaceUri
0786: * @param suggPrefix
0787: * @return the existing or newly registered prefix
0788: */
0789: public String getNamespacePrefix(String namespaceUri,
0790: String suggPrefix) {
0791: String prefix = (String) namespaces.get(namespaceUri);
0792: if (prefix != null)
0793: return prefix;
0794:
0795: Collection prefixes = namespaces.values();
0796:
0797: // try out the suggested prefix first if one was given
0798: if (suggPrefix != null && !prefixes.contains(suggPrefix)) {
0799: prefix = suggPrefix;
0800: } else {
0801: // first, try to make up a prefix from the last part of the URI
0802: int slash = namespaceUri.lastIndexOf('/');
0803: if (slash >= 0) {
0804: for (int l = 1; slash + 1 + l < namespaceUri.length(); l++) {
0805: String p = namespaceUri.substring(slash + 1, slash
0806: + 1 + l);
0807: if (!prefixes.contains(p)) {
0808: prefix = p;
0809: break;
0810: }
0811: }
0812: }
0813: // if (prefix == null) {
0814: // // try something else...
0815: // }
0816: }
0817:
0818: if (prefix != null) {
0819: Element root = sourceDocument.getDocumentElement();
0820: ensureAttributeValue(root, "xmlns:" + prefix, namespaceUri);
0821: }
0822:
0823: return prefix;
0824: }
0825:
0826: public URI getBaseURI() {
0827: if (baseURI == null) {
0828: if (base == null) {
0829: base = getBase();
0830: } else {
0831: try {
0832: baseURI = new URI(base.toExternalForm());
0833: } catch (URISyntaxException use) {
0834: baseURI = null;
0835: }
0836: }
0837: }
0838: return baseURI;
0839: }
0840:
0841: /**
0842: * Returns the location to resolve relative URLs against. By default this will be the document's
0843: * URL if the document was loaded from a URL. If a base tag is found and can be parsed, it will
0844: * be used as the base location.
0845: *
0846: * @return the base location
0847: */
0848: public URL getBase() {
0849: if (base == null) {
0850: // First see if we have a <base> tag within the <head>
0851:
0852: // TODO - gather ALL <base> elements within the head
0853: // and process them
0854: Element root = sourceDocument.getDocumentElement();
0855: Element html = findHtmlTag(root);
0856: if (html != null) {
0857: Element head = Util.findChild("head", html, false);
0858: if (head != null) {
0859: Element baseElement = Util.findChild("base", head,
0860: false);
0861: if (baseElement != null) {
0862: String href = baseElement.getAttribute("href");
0863: if (href != null && href.length() > 0) {
0864: try {
0865: try {
0866: baseURI = new URI(href);
0867: //base = new URL(href);
0868: base = baseURI.toURL();
0869: } catch (URISyntaxException ex) {
0870: base = new URL(href);
0871: }
0872: } catch (MalformedURLException mue) {
0873: ErrorManager
0874: .getDefault()
0875: .notify(
0876: ErrorManager.INFORMATIONAL,
0877: mue);
0878: }
0879: if (base != null) {
0880: return base;
0881: }
0882: }
0883: }
0884: }
0885: }
0886:
0887: // No <base>, so get the URL of the document file itself
0888: // and use that to resolve relative URLs.
0889:
0890: // Compute base
0891: /* These URLs don't seem to work - they become
0892: nbfs://<whatever> which Swing is not handling. So we've
0893: gotta do the file:// thing. This means for now, opening
0894: a web form inside a jar file with relative URLs won't work.
0895: try {
0896: base = dobj.getPrimaryFile().getParent().getURL();
0897: }
0898: catch (org.openide.filesystems.FileStateInvalidException e) {
0899: ErrorManager.getDefault().notify(e);
0900: }
0901: */
0902: if (fobj == null) // Testsuite
0903: return null;
0904:
0905: try {
0906: FileObject fp = fobj.getParent();
0907: baseURI = FileUtil.toFile(fp).toURI();
0908: base = baseURI.toURL();
0909: } catch (MalformedURLException e) {
0910: ErrorManager.getDefault().notify(e);
0911: }
0912: }
0913: return base;
0914: }
0915:
0916: /** Convert the given path to a URL: encode spaces to %20's, use
0917: * only forward slashes, etc.
0918: * @todo Find a better home for this method
0919: */
0920: public static String toURL(String path) {
0921: // The URL encoder doesn't seem to do this - surprising
0922: path = path.replace('\\', '/');
0923: // This was also surprising - it makes spaces into +'es instead
0924: path = path.replaceAll(" ", "%20");
0925: StringWriter sw = new StringWriter();
0926: try {
0927: // WriteURL signature changed in JSF1.2-02-b04
0928: com.sun.faces.util.HtmlUtils.writeURL(sw, path, null, null);
0929: } catch (java.io.IOException ex) {
0930: ErrorManager.getDefault().notify(ex);
0931: return path;
0932: }
0933: return sw.toString();
0934: }
0935:
0936: // <markup_separation> moved to designer/markup/XhtmlCssEngine
0937: // /** Convert the given URL to a path: decode spaces from %20's, etc.
0938: // * If the url does not begin with "file:" it will not do anything.
0939: // * @todo Find a better home for this method
0940: // */
0941: // public static String fromURL(String url) {
0942: // if (url.startsWith("file:")) { // NOI18N
0943: // int n = url.length();
0944: // StringBuffer sb = new StringBuffer(n);
0945: // for (int i = 5; i < n; i++) {
0946: // char c = url.charAt(i);
0947: // // TODO -- any File.separatorChar manipulation perhaps?
0948: // if (c == '%' && i < n-3) {
0949: // char d1 = url.charAt(i+1);
0950: // char d2 = url.charAt(i+2);
0951: // if (Character.isDigit(d1) && Character.isDigit(d2)) {
0952: // String numString = ""+d1+d2;
0953: // try {
0954: // int num = Integer.parseInt(numString, 16);
0955: // if (num >= 0 && num <= 255) {
0956: // sb.append((char)num);
0957: // i += 2;
0958: // continue;
0959: // }
0960: // } catch (NumberFormatException nex) {
0961: // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, nex);
0962: // }
0963: // }
0964: // sb.append(c);
0965: // } else {
0966: // sb.append(c);
0967: // }
0968: // }
0969: // return sb.toString();
0970: // }
0971: // return url;
0972: // }
0973: // </markup_separation>
0974:
0975: /**
0976: * Return true iff this web page should not be converted to a web form if it's missing a backing
0977: * file. Web forms can be marked in this way if the user answers no to the conversion dialog.
0978: */
0979: public boolean isHtmlOnly() {
0980: if (sourceDocument == null) {
0981: return false;
0982: }
0983: Element root = sourceDocument.getDocumentElement();
0984: Element html = findHtmlTag(root);
0985: if (html == null) { // We're hosed!!! This shouldn't happen
0986: Thread.dumpStack();
0987: return false;
0988: }
0989: Element head = Util.findChild("head", html, true);
0990: if (head == null) {
0991: return false;
0992: }
0993: NodeList list = head.getChildNodes();
0994: int len = list.getLength();
0995: for (int i = 0; i < len; i++) {
0996: org.w3c.dom.Node child = list.item(i);
0997: if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
0998: Element meta = (Element) child;
0999: if ("meta".equals(meta.getTagName())) {
1000: if (!"creator.webform".equalsIgnoreCase(meta
1001: .getAttribute("name"))) {
1002: continue;
1003: }
1004: String isForm = meta.getAttribute("content");
1005: return isForm != null && isForm.equals("no");
1006: }
1007: }
1008: }
1009: return false;
1010: }
1011:
1012: /**
1013: * Mark this web form in such a way that in the future, isHtmlOnly will return false. In other
1014: * words, if this web page should not be converted to a web form, call this method such that the
1015: * conversion dialog is not shown next time this markup document is opened. This method should
1016: * NOT be called on a file which already returns isHtmlOnly (it may add additional meta tags,
1017: * not look for existing ones first).
1018: */
1019: public void markHtmlOnly() { // not called setHtmlOnly since we only
1020: // support setting the property to
1021: // true, not false
1022: assert !isHtmlOnly();
1023:
1024: Element root = sourceDocument.getDocumentElement();
1025: Element html = findHtmlTag(root);
1026: if (html == null) { // We're hosed!!! This shouldn't happen
1027: Thread.dumpStack();
1028: return;
1029: }
1030: Element head = ensureElement(html, "head", null);
1031: Element meta = ensureElement(head, "meta", null);
1032: meta.setAttribute("content", "no");
1033: meta.setAttribute("name", "creator.webform");
1034: }
1035:
1036: /**
1037: * Locate the <html> tag. In a normal xhtml/html document, it's the same as the root tag
1038: * for the DOM, but in our JSF files, it might be nested within <jsp:root>,
1039: * <f:view>, etc.
1040: *
1041: * @param root The root tag
1042: * @todo Just pass in the Document node instead?
1043: * @return The html tag Element
1044: */
1045: public Element findHtmlTag(Node root) {
1046: if (root.getNodeType() == Node.ELEMENT_NODE) {
1047: Element element = (Element) root;
1048: if ("html".equals(element.getTagName())) // Don't allow "HTML"
1049: return element;
1050: }
1051: NodeList list = root.getChildNodes();
1052: int len = list.getLength();
1053: for (int i = 0; i < len; i++) {
1054: Node child = list.item(i);
1055: Element match = findHtmlTag(child);
1056: if (match != null)
1057: return match;
1058: }
1059: return null;
1060: }
1061:
1062: //---------------------------------------------------------------------------------- Public CSS2
1063:
1064: /** Add a parser error to the hashmap for the given CSS/SAC error */
1065: /*
1066: private void addError(CSSParseException exception, boolean isError) {
1067: String message = exception.getMessage();
1068: int line = exception.getLineNumber();
1069: int column = exception.getColumnNumber();
1070: //String uri = exception.getURI();
1071:
1072: // XXX What is the file here? Gotta track that somehow, so
1073: // I can either point to a particular stylesheet file, or
1074: // a local style attribute, or a <style> tag section...
1075:
1076: error = new ParserAnnotation(message, fobj, line, column);
1077:
1078: if (isError)
1079: setInvalid();
1080: }
1081: */
1082:
1083: //----------------------------------------------------------------------------------- Public DOM
1084: /**
1085: * Create a new document, from an xhtml fragment source string
1086: */
1087: private Document createDocument(Document ownerDom,
1088: final String source) {
1089: try {
1090: // just a plain ol' XML DOM Document, use the regular XML DOM parser
1091: // wrap source with a fake root since it may contain more than one element
1092: // XXX Note: designer knows about the fake-root element
1093: // name. Don't change arbitrarily!
1094: String fullSource = "<!DOCTYPE html \nPUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"\"><fake-root>"
1095: + source + "</fake-root>";
1096: org.xml.sax.InputSource is = new org.xml.sax.InputSource(
1097: new StringReader(fullSource));
1098: ClassLoader oldClassLoader = Thread.currentThread()
1099: .getContextClassLoader();
1100: try {
1101: Thread.currentThread().setContextClassLoader(
1102: (ClassLoader) Lookup.getDefault().lookup(
1103: ClassLoader.class));
1104: DocumentBuilder parser = MarkupService
1105: .createRaveSourceDocumentBuilder(supportCss);
1106: parser.setEntityResolver(new XhtmlEntityResolver());
1107:
1108: parser.setErrorHandler(new ErrorHandler() {
1109: // ---- Implements org.xml.sax.ErrorHandler ----------
1110:
1111: public void error(SAXParseException exception) {
1112: fragmentError(exception, true, source);
1113: }
1114:
1115: public void fatalError(SAXParseException exception) {
1116: fragmentError(exception, true, source);
1117: }
1118:
1119: public void warning(SAXParseException exception) {
1120: fragmentError(exception, false, source);
1121: }
1122: });
1123:
1124: Document fragdoc = parser.parse(is);
1125: // if (fragdoc instanceof RaveDocument) {
1126: // <markup_separation>
1127: // ((RaveDocument)fragdoc).setMarkupUnit(this);
1128: // ====
1129: setMarkupUnitForDocument(fragdoc, this );
1130: // </markup_separation>
1131: // ((RaveDocument)fragdoc).setCssEngine(((RaveDocument)sourceDocument).getCssEngine());
1132: // CssProvider.getEngineService().reuseCssEngineForDocument(fragdoc, sourceDocument);
1133: // XXX Not fragdoc but its rendered doc?!
1134: CssProvider.getEngineService()
1135: .reuseCssEngineForDocument(fragdoc, ownerDom);
1136: // }
1137:
1138: return fragdoc;
1139: } finally {
1140: Thread.currentThread().setContextClassLoader(
1141: oldClassLoader);
1142: }
1143: } catch (java.io.IOException e) {
1144: // should not happen reading from a string!
1145: // Trace.trace("insync.markup", "Error in createDocumentFragment");
1146: // Trace.trace("insync.markup", e);
1147: e.printStackTrace();
1148: } catch (org.xml.sax.SAXException e) {
1149: // Trace.trace("insync.markup", "Error in createDocumentFragment");
1150: // Trace.trace("insync.markup", e);
1151: e.printStackTrace();
1152: } catch (javax.xml.parsers.ParserConfigurationException e) {
1153: // Trace.trace("insync.markup", "Error in createDocumentFragment");
1154: // Trace.trace("insync.markup", e);
1155: e.printStackTrace();
1156: }
1157: return null;
1158: }
1159:
1160: // /**
1161: // * Create a new source document fragment, from a source string, that will live in the current
1162: // * source document. The fragment needs to be added as a child to a specific node before it will really
1163: // * exist in the source document tree.
1164: // */
1165: // private DocumentFragment createDocumentFragment(Document ownerDom, String source) {
1166: // Document fragdoc = createDocument(ownerDom, source);
1167: // if (fragdoc != null) {
1168: //// DocumentFragment fragment = sourceDocument.createDocumentFragment();
1169: // DocumentFragment fragment = ownerDom.createDocumentFragment();
1170: //
1171: // NodeList elems = fragdoc.getDocumentElement().getChildNodes();
1172: // // get the elems from our fake root
1173: // // import them into this sourceDocument, and add the copies to the fragment
1174: // int elemCount = elems.getLength();
1175: // for (int i = 0; i < elemCount; i++) {
1176: //// Node e = sourceDocument.importNode(elems.item(i), true);
1177: // Node e = ownerDom.importNode(elems.item(i), true);
1178: //
1179: // fragment.appendChild(e);
1180: // }
1181: // return fragment;
1182: // }
1183: // return null;
1184: // }
1185:
1186: private void fragmentError(SAXParseException exception,
1187: boolean isError, String source) {
1188: String message = exception.getMessage();
1189: int line = exception.getLineNumber();
1190: int column = exception.getColumnNumber();
1191: //String publicId = exception.getPublicId();
1192: //String systemId = exception.getSystemId();
1193: if (source.length() > 80) {
1194: source = source.substring(0, 80) + "...";
1195: }
1196:
1197: // TODO: i18n.
1198: String error = line + ":" + column + ": " + message + ": "
1199: + source;
1200:
1201: InputOutput io = IOProvider.getDefault().getIO(
1202: NbBundle.getMessage(MarkupUnit.class, "WindowTitle"),
1203: false);
1204: OutputWriter out = io.getOut();
1205: try {
1206: out.reset();
1207: // XXX #102988 There can't be null listener now.
1208: out.println(error, new OutputListener() {
1209: public void outputLineSelected(OutputEvent evt) {
1210: // No op.
1211: }
1212:
1213: public void outputLineAction(OutputEvent evt) {
1214: // No op.
1215: }
1216:
1217: public void outputLineCleared(OutputEvent evt) {
1218: // No op.
1219: }
1220: });
1221: } catch (IOException ex) {
1222: // This is lame - our own output window shouldn't throw
1223: // IO exceptions!
1224: ErrorManager.getDefault().notify(ex);
1225: }
1226: }
1227:
1228: // private Document createEmptyDocument() {
1229: // Document doc = createEmptyDocument(supportCss);
1230: //// if (doc instanceof RaveDocument)
1231: //// // <markup_separation>
1232: ////// ((RaveDocument)doc).setMarkupUnit(this);
1233: //// // ====
1234: //// {
1235: // setMarkupUnitForDocument(doc, this);
1236: //// }
1237: // // </markup_separation>
1238: // return doc;
1239: // }
1240: //
1241: // private static Document createEmptyDocument(boolean supportCss) {
1242: // try {
1243: // org.xml.sax.InputSource is =
1244: // new org.xml.sax.InputSource(new StringReader("<html><body><p/></body></html>"));
1245: // DocumentBuilder parser = MarkupService.createRaveSourceDocumentBuilder(supportCss);
1246: // Document doc = parser.parse(is);
1247: // return doc;
1248: // }
1249: // catch (java.io.IOException e) {
1250: // // should not happen reading from a string!
1251: // Trace.trace("insync.markup", "Error in createEmptyDocument");
1252: // Trace.trace("insync.markup", e);
1253: // }
1254: // catch (org.xml.sax.SAXException e) {
1255: // Trace.trace("insync.markup", "Error in createEmptyDocument");
1256: // Trace.trace("insync.markup", e);
1257: // }
1258: // catch (javax.xml.parsers.ParserConfigurationException e) {
1259: // Trace.trace("insync.markup", "Error in createEmptyDocument");
1260: // Trace.trace("insync.markup", e);
1261: // }
1262: // return null;
1263: // }
1264:
1265: public void appendParsedString(Node parent, String xhtml,
1266: MarkupDesignBean bean) {
1267: // assert parent.getOwnerDocument() == sourceDocument;
1268: Document ownerDom = parent.getOwnerDocument();
1269:
1270: if (xhtml.startsWith("<?") || xhtml.startsWith("<!DOCTYPE")) {
1271: // Skip it -- we can't parse this.
1272: // Braveheart likes to throw in (literally)
1273: // <?xml version="1.0" encoding="UTF-8"?>
1274: // for example
1275: return;
1276: }
1277: Document fragdoc = createDocument(ownerDom, xhtml);
1278: // NOTE - we don't mark this source jspx as is done in read();
1279: // this source is part of rendered html from a component, and
1280: // has already had entities expanded.
1281: if (fragdoc == null) {
1282: // Parse error - for now just insert the xhtml directly as
1283: // text - e.g. the user may see something like "<b>Hello</b>"
1284: // instead of Hello in a bold font
1285: // Node textNode = sourceDocument.createTextNode(xhtml);
1286: Node textNode = ownerDom.createTextNode(xhtml);
1287: parent.appendChild(textNode);
1288: } else {
1289: NodeList elems = fragdoc.getDocumentElement()
1290: .getChildNodes();
1291: // get the elems from our fake root
1292: // import them into this source document, and add the copies to the fragment
1293:
1294: // Find Element containing the import location
1295: Element parentElement = null;
1296: Node curr = parent;
1297: while (curr != null) {
1298: if (curr instanceof Element) {
1299: parentElement = (Element) curr;
1300: break;
1301: }
1302: curr = curr.getParentNode();
1303: }
1304:
1305: int elemCount = elems.getLength();
1306: for (int i = 0; i < elemCount; i++) {
1307: // XXX Can't I just bang them into my other documenet fragment?
1308: // Node e = sourceDocument.importNode(elems.item(i), true);
1309: Node e = ownerDom.importNode(elems.item(i), true);
1310:
1311: // The xhtml fragment imported should participate in
1312: // the CSS context at the imported location
1313: if (parentElement != null && e instanceof Element) {
1314: // RaveElement.setStyleParent((Element)e, parentElement);
1315: CssProvider.getEngineService()
1316: .setStyleParentForElement((Element) e,
1317: parentElement);
1318: // I have to set it on the Document too, because import node
1319: // will set up source pointers back to the derived document even
1320: // though it's not technically the jspx source (and the source
1321: // pointers are followed by the designer when computing text)
1322: // Consider fixing this later.
1323: // RaveElement.setStyleParent((Element)elems.item(i), parentElement);
1324: CssProvider.getEngineService()
1325: .setStyleParentForElement(
1326: (Element) elems.item(i),
1327: parentElement);
1328: }
1329: parent.appendChild(e);
1330: setBean(e, bean);
1331: }
1332: }
1333: }
1334:
1335: /** Set the DesignBean references recursively on the given node tree */
1336: private void setBean(Node node, MarkupDesignBean bean) {
1337: // if (node instanceof RaveElement) {
1338: // ((RaveElement)node).setDesignBean(bean);
1339: // }
1340: if (node instanceof Element) {
1341: setMarkupDesignBeanForElement((Element) node, bean);
1342: }
1343:
1344: NodeList nl = node.getChildNodes();
1345: for (int i = 0; i < nl.getLength(); i++) {
1346: setBean(nl.item(i), bean);
1347: }
1348: }
1349:
1350: // XXX #123995 The apache impl of Document userData leaks, so we
1351: // need to avoid using it for now. Returning to previous, but also
1352: // using weak refs (because there is a link from MarkupDesignBean to Element).
1353: /** Map <code>Element</code> to <code>MarkupDesignBean</code>. */
1354: private static final Map element2markupDesignBean = new WeakHashMap(
1355: 200);
1356:
1357: // private static final String KEY_MARKUP_DESIGN_BEAN = "vwpMarkupDesignBean"; // NOI18N
1358:
1359: public static void setMarkupDesignBeanForElement(Element element,
1360: MarkupDesignBean markupDesignBean) {
1361: synchronized (element2markupDesignBean) {
1362: element2markupDesignBean.put(element, new WeakReference(
1363: markupDesignBean));
1364: }
1365: // if (element == null) {
1366: // // XXX Log problem?
1367: // return;
1368: // }
1369: // element.setUserData(KEY_MARKUP_DESIGN_BEAN, markupDesignBean, MarkupDesignBeanDataHandler.getDefault());
1370: }
1371:
1372: public static MarkupDesignBean getMarkupDesignBeanForElement(
1373: Element element) {
1374: synchronized (element2markupDesignBean) {
1375: WeakReference ret = (WeakReference) element2markupDesignBean
1376: .get(element);
1377: return ret == null ? null : (MarkupDesignBean) ret.get();
1378: }
1379: // if (element == null) {
1380: // // XXX Log problem?
1381: // return null;
1382: // }
1383: // return (MarkupDesignBean)element.getUserData(KEY_MARKUP_DESIGN_BEAN);
1384: }
1385:
1386: // private static class MarkupDesignBeanDataHandler implements UserDataHandler {
1387: // private static final MarkupDesignBeanDataHandler INSTANCE = new MarkupDesignBeanDataHandler();
1388: // public static MarkupDesignBeanDataHandler getDefault() {
1389: // return INSTANCE;
1390: // }
1391: // public void handle(short operation, String key, Object data, Node src, Node dst) {
1392: // // No op.
1393: // // TODO Provide the copying (after remove the copying in the AbstractRaveElement).
1394: // }
1395: // } // End of MarkupDesignBeanUserData.
1396:
1397: //------------------------------------------------------------------------ Change Event Handling
1398:
1399: // <refactoring> why copy when one depends on it directly
1400: // // This section of constants copied from Xerces 2.5.0's MutationEventImpl class
1401: // // NON-DOM CONSTANTS: Storage efficiency, avoid risk of typos.
1402: // public static final String DOM_SUBTREE_MODIFIED = "DOMSubtreeModified";
1403: // public static final String DOM_NODE_INSERTED = "DOMNodeInserted";
1404: // public static final String DOM_NODE_REMOVED = "DOMNodeRemoved";
1405: // public static final String DOM_NODE_REMOVED_FROM_DOCUMENT = "DOMNodeRemovedFromDocument";
1406: // public static final String DOM_NODE_INSERTED_INTO_DOCUMENT = "DOMNodeInsertedIntoDocument";
1407: // public static final String DOM_ATTR_MODIFIED = "DOMAttrModified";
1408: // public static final String DOM_CHARACTER_DATA_MODIFIED = "DOMCharacterDataModified";
1409: //
1410: // public static final String DOM_DOCUMENT_REPLACED = "DOMDocumentReplaced";
1411: // ====
1412: public static final String DOM_SUBTREE_MODIFIED = MutationEventImpl.DOM_SUBTREE_MODIFIED;
1413: public static final String DOM_NODE_INSERTED = MutationEventImpl.DOM_NODE_INSERTED;
1414: public static final String DOM_NODE_REMOVED = MutationEventImpl.DOM_NODE_REMOVED;
1415: public static final String DOM_NODE_REMOVED_FROM_DOCUMENT = MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT;
1416: public static final String DOM_NODE_INSERTED_INTO_DOCUMENT = MutationEventImpl.DOM_NODE_INSERTED_INTO_DOCUMENT;
1417: public static final String DOM_ATTR_MODIFIED = MutationEventImpl.DOM_ATTR_MODIFIED;
1418: public static final String DOM_CHARACTER_DATA_MODIFIED = MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED;
1419:
1420: /** Insync extention of MutationEventImpl type (see insyc/MarkupUnit and xerces/MuationEventImpl). */
1421: public static final String DOM_DOCUMENT_REPLACED = "DOMDocumentReplaced"; // NOI18N
1422: // </refactoring>
1423:
1424: private void registerDomListeners(Document document) {
1425: Trace.trace("insync.markup", "MU.registerDomListeners "
1426: + document);
1427: EventTarget target = (EventTarget) document;
1428: target.addEventListener(DOM_ATTR_MODIFIED, this , false);
1429: target.addEventListener(DOM_SUBTREE_MODIFIED, this , false);
1430: registeredAsEventListenerOn = target;
1431: /*
1432: target.addEventListener(DOM_NODE_INSERTED, this, false);
1433: target.addEventListener(DOM_NODE_INSERTED_INTO_DOCUMENT, this, false);
1434: target.addEventListener(DOM_NODE_REMOVED, this, false);
1435: target.addEventListener(DOM_NODE_REMOVED_FROM_DOCUMENT, this, false);
1436: target.addEventListener(DOM_CHARACTER_DATA_MODIFIED, this, false);
1437: */
1438: }
1439:
1440: private void fireDocumentReplaced(Document document) {
1441: Trace.trace("insync.markup", "MU.fireDomListeners " + document);
1442: DocumentEvent doc = (DocumentEvent) document;
1443: MutationEvent me = (MutationEvent) doc
1444: .createEvent("MutationEvents");
1445: me.initMutationEvent(DOM_DOCUMENT_REPLACED, false, false,
1446: document, null, null, null, MutationEvent.REMOVAL);
1447:
1448: EventTarget target = (EventTarget) document;
1449: target.dispatchEvent(me);
1450: }
1451:
1452: protected void unregisterDomListeners() {
1453: if (registeredAsEventListenerOn == null)
1454: return;
1455: registeredAsEventListenerOn.removeEventListener(
1456: DOM_ATTR_MODIFIED, this , false);
1457: registeredAsEventListenerOn.removeEventListener(
1458: DOM_SUBTREE_MODIFIED, this , false);
1459: registeredAsEventListenerOn = null;
1460: }
1461:
1462: public void handleEvent(org.w3c.dom.events.Event e) {
1463: setModelDirty();
1464: }
1465:
1466: //------------------------------------------------------------------------ Compute Buffer Positions
1467:
1468: /**
1469: * Return the buffer start position of a given node. Not currently very efficient or accurate
1470: * @return the char offset of the node, -1 if not found
1471: */
1472: public int computeLine(Element element) {
1473: LineCountingWriter w = new LineCountingWriter();
1474: OutputFormat format = getOutputFormat();
1475: TargetXMLSerializer serializer = new TargetXMLSerializer(w,
1476: format, element);
1477: int pos = 0;
1478: try {
1479: serializer.serialize(sourceDocument);
1480: } catch (java.io.EOFException e) {
1481: // normal stop...
1482: pos = w.pos;
1483: if (!serializer.isAdjusted()) {
1484: pos++;
1485: }
1486: } catch (java.io.IOException e) {
1487: assert Trace.trace("insync.java",
1488: "Error scanning for node position: " + e);
1489: }
1490:
1491: // It always includes an extra newline
1492: return pos - 1;
1493: }
1494:
1495: /**
1496: * Return the buffer start offset of a given node. Not currently very efficient or accurate
1497: * @return the char offset of the node, -1 if not found
1498: */
1499: public int getOffset(Element element) {
1500: LineCountingWriter w = new LineCountingWriter();
1501: OutputFormat format = getOutputFormat();
1502: TargetXMLSerializer serializer = new TargetXMLSerializer(w,
1503: format, element);
1504: int offset = -1;
1505: try {
1506: serializer.serialize(sourceDocument);
1507: } catch (java.io.EOFException e) {
1508: // normal stop...
1509: offset = w.offset;
1510: } catch (java.io.IOException e) {
1511: assert Trace.trace("insync.java",
1512: "Error scanning for node position: " + e);
1513: }
1514:
1515: return offset;
1516: }
1517:
1518: private class TargetXMLSerializer extends JspxSerializer {
1519: private Element target;
1520: private boolean adjusted = true;
1521:
1522: private TargetXMLSerializer(java.io.Writer writer,
1523: OutputFormat format, Element target) {
1524: super (writer, format);
1525: this .target = target;
1526: }
1527:
1528: boolean isAdjusted() {
1529: return adjusted;
1530: }
1531:
1532: public void serializeElement(Element elem) throws IOException,
1533: EOFException {
1534: if (elem == target) {
1535: if (getElementState().empty) {
1536: adjusted = false;
1537: }
1538: //_printer.breakLine();
1539: _printer.flush();
1540: throw new EOFException();
1541: }
1542: super .serializeElement(elem);
1543: }
1544: }
1545:
1546: public class LineCountingWriter extends Writer {
1547: public int pos;
1548: public int offset;
1549:
1550: public void close() {
1551: }
1552:
1553: public void flush() {
1554: }
1555:
1556: public void write(char[] buf) {
1557: offset += buf.length;
1558: for (int i = 0; i < buf.length; i++) {
1559: if (buf[i] == '\n') {
1560: pos++;
1561: }
1562: }
1563: }
1564:
1565: public void write(char[] buf, int off, int len) {
1566: offset += len;
1567: for (int i = 0; i < len; i++) {
1568: if (buf[off + i] == '\n') {
1569: pos++;
1570: }
1571: }
1572: }
1573:
1574: public void write(int c) {
1575: offset++;
1576: if (c == '\n') {
1577: pos++;
1578: }
1579: }
1580:
1581: public void write(String str) {
1582: offset += str.length();
1583: for (int i = 0, n = str.length(); i < n; i++) {
1584: if (str.charAt(i) == '\n') {
1585: pos++;
1586: }
1587: }
1588: }
1589:
1590: public void write(String str, int off, int len) {
1591: offset += len;
1592: for (int i = 0; i < len; i++) {
1593: if (str.charAt(off + i) == '\n') {
1594: pos++;
1595: }
1596: }
1597: }
1598: }
1599:
1600: // // <markup_separation> Maintain the map between doc and unit,
1601: // // and css engine and unit.
1602: // // Do not pass it directly into the doc or engine, it is not needed there.
1603: // /** Map between <code>org.w3c.dom.Document</code> and <code>MarkupUnit</code> */
1604: // private final static Map doc2markupUnit = new WeakHashMap();
1605: // private static final String KEY_MARKUP_UNIT = "vwpMarkupUnit"; // NOI18N
1606:
1607: private static final Map<Document, MarkupUnit> doc2markupUnit = new WeakHashMap<Document, MarkupUnit>();
1608:
1609: private static void setMarkupUnitForDocument(Document doc,
1610: MarkupUnit markupUnit) {
1611: // synchronized (doc2markupUnit) {
1612: // doc2markupUnit.put(doc, markupUnit);
1613: // }
1614: if (doc == null) {
1615: return;
1616: }
1617: // doc.setUserData(KEY_MARKUP_UNIT, markupUnit, MarkupUnitDataHandler.getDefault());
1618: doc2markupUnit.put(doc, markupUnit);
1619: }
1620:
1621: public static MarkupUnit getMarkupUnitForDocument(Document doc) {
1622: // synchronized (doc2markupUnit) {
1623: // return (MarkupUnit)doc2markupUnit.get(doc);
1624: // }
1625: if (doc == null) {
1626: return null;
1627: }
1628: // return (MarkupUnit)doc.getUserData(KEY_MARKUP_UNIT);
1629: return doc2markupUnit.get(doc);
1630: }
1631:
1632: // private static class MarkupUnitDataHandler implements UserDataHandler {
1633: // private static final MarkupUnitDataHandler INSTANCE = new MarkupUnitDataHandler();
1634: //
1635: // public static MarkupUnitDataHandler getDefault() {
1636: // return INSTANCE;
1637: // }
1638: //
1639: // public void handle(short operation, String key, Object data, Node src, Node dst) {
1640: // // No op.
1641: // }
1642: // } // End of MarkupUnitDataHandler.
1643:
1644: // /** Map between <code>XhtmlCssEngine</code> and <code>MarkupUnit</code>. */
1645: // private final static Map cssEngine2markupUnit = new WeakHashMap();
1646: //
1647: // private void setMarkupUnitForCssEngine(XhtmlCssEngine cssEngine, MarkupUnit markupUnit) {
1648: // synchronized (cssEngine2markupUnit) {
1649: // cssEngine2markupUnit.put(cssEngine, markupUnit);
1650: // }
1651: // }
1652: //
1653: // public MarkupUnit getMarkupUnitForCssEngine(XhtmlCssEngine cssEngine) {
1654: // synchronized (cssEngine2MarkupUnit) {
1655: // return (MarkupUnit)cssEngine2markupUnit;
1656: // }
1657: // }
1658: // </markup_separation>
1659:
1660: // /** Map between <code>org.w3c.dom.Document</code> and <code>URL</code> */
1661: // private final static Map doc2url = new WeakHashMap();
1662: // private static final String KEY_URL = "vwpUrl"; // NOI18N
1663:
1664: private static final Map<Document, URL> doc2url = new WeakHashMap<Document, URL>();
1665:
1666: public/*private*/static void setUrlForDocument(Document doc,
1667: URL url) {
1668: // synchronized (doc2url) {
1669: // doc2url.put(doc, url);
1670: // }
1671: if (doc == null) {
1672: return;
1673: }
1674: // doc.setUserData(KEY_URL, url, UrlDataHandler.getDefault());
1675: doc2url.put(doc, url);
1676: }
1677:
1678: public static URL getUrlForDocument(Document doc) {
1679: // synchronized (doc2url) {
1680: // return (URL)doc2url.get(doc);
1681: // }
1682: if (doc == null) {
1683: return null;
1684: }
1685: // return (URL)doc.getUserData(KEY_URL);
1686: return doc2url.get(doc);
1687: }
1688:
1689: // private static class UrlDataHandler implements UserDataHandler {
1690: // private static final UrlDataHandler INSTANCE = new UrlDataHandler();
1691: //
1692: // public static UrlDataHandler getDefault() {
1693: // return INSTANCE;
1694: // }
1695: //
1696: // public void handle(short operation, String key, Object data, Node src, Node dst) {
1697: // // No op.
1698: // }
1699: // } // End of UrlDataHandler.
1700: }
|